summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.clang-tidy21
-rw-r--r--.git-blame-ignore-revs3
-rw-r--r--.github/workflows/linux_builds.yml2
-rw-r--r--.github/workflows/web_builds.yml4
-rw-r--r--.pre-commit-config.yaml8
-rw-r--r--COPYRIGHT.txt2
-rw-r--r--core/input/input.cpp2
-rw-r--r--core/io/resource_importer.h2
-rw-r--r--core/object/class_db.cpp104
-rw-r--r--core/object/message_queue.h2
-rw-r--r--core/templates/list.h6
-rw-r--r--core/typedefs.h2
-rw-r--r--core/variant/variant.cpp12
-rw-r--r--core/variant/variant.h3
-rw-r--r--doc/classes/AnimationNodeStateMachineTransition.xml4
-rw-r--r--doc/classes/AudioStreamPlaybackPolyphonic.xml2
-rw-r--r--doc/classes/CameraFeed.xml18
-rw-r--r--doc/classes/Control.xml3
-rw-r--r--doc/classes/DisplayServer.xml2
-rw-r--r--doc/classes/EditorFileSystemImportFormatSupportQuery.xml2
-rw-r--r--doc/classes/EditorImportPlugin.xml3
-rw-r--r--doc/classes/EditorResourcePreview.xml4
-rw-r--r--doc/classes/EditorScenePostImportPlugin.xml6
-rw-r--r--doc/classes/EditorSettings.xml4
-rw-r--r--doc/classes/EditorUndoRedoManager.xml2
-rw-r--r--doc/classes/GraphNode.xml2
-rw-r--r--doc/classes/Image.xml4
-rw-r--r--doc/classes/MultiplayerPeer.xml2
-rw-r--r--doc/classes/NavigationServer2D.xml8
-rw-r--r--doc/classes/NavigationServer3D.xml12
-rw-r--r--doc/classes/Object.xml2
-rw-r--r--doc/classes/ProjectSettings.xml10
-rw-r--r--doc/classes/RDPipelineDepthStencilState.xml6
-rw-r--r--doc/classes/RenderingServer.xml12
-rw-r--r--doc/classes/ResourceLoader.xml2
-rw-r--r--doc/classes/Shader.xml4
-rw-r--r--doc/classes/SkeletonIK3D.xml2
-rw-r--r--doc/classes/Slider.xml4
-rw-r--r--doc/classes/SpringArm3D.xml1
-rw-r--r--doc/classes/TileMap.xml4
-rw-r--r--doc/classes/TileMapLayer.xml4
-rw-r--r--doc/classes/TreeItem.xml6
-rw-r--r--doc/classes/VideoStreamPlayback.xml6
-rw-r--r--doc/classes/Viewport.xml6
-rw-r--r--doc/classes/VisualShaderNodeCompare.xml4
-rw-r--r--drivers/d3d12/rendering_device_driver_d3d12.cpp2
-rw-r--r--drivers/gles3/effects/feed_effects.cpp128
-rw-r--r--drivers/gles3/effects/feed_effects.h68
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.cpp45
-rw-r--r--drivers/gles3/rasterizer_gles3.cpp2
-rw-r--r--drivers/gles3/rasterizer_gles3.h2
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.cpp39
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.h1
-rw-r--r--drivers/gles3/shaders/SCsub1
-rw-r--r--drivers/gles3/shaders/feed.glsl39
-rw-r--r--drivers/gles3/storage/particles_storage.h2
-rw-r--r--drivers/png/SCsub16
-rw-r--r--drivers/vulkan/rendering_device_driver_vulkan.cpp52
-rw-r--r--drivers/vulkan/rendering_device_driver_vulkan.h5
-rw-r--r--drivers/windows/dir_access_windows.cpp2
-rw-r--r--drivers/windows/file_access_windows.cpp2
-rw-r--r--drivers/windows/file_access_windows_pipe.cpp20
-rw-r--r--drivers/windows/file_access_windows_pipe.h2
-rw-r--r--editor/animation_track_editor.cpp3
-rw-r--r--editor/debugger/script_editor_debugger.cpp7
-rw-r--r--editor/debugger/script_editor_debugger.h2
-rw-r--r--editor/editor_audio_buses.cpp20
-rw-r--r--editor/editor_feature_profile.cpp6
-rw-r--r--editor/editor_file_system.cpp4
-rw-r--r--editor/editor_node.h2
-rw-r--r--editor/gui/editor_quick_open_dialog.cpp7
-rw-r--r--editor/icons/MaterialPreviewQuad.svg1
-rw-r--r--editor/import/3d/resource_importer_obj.cpp4
-rw-r--r--editor/import/3d/resource_importer_obj.h3
-rw-r--r--editor/import/3d/resource_importer_scene.cpp5
-rw-r--r--editor/import/3d/resource_importer_scene.h2
-rw-r--r--editor/import/3d/scene_import_settings.cpp1
-rw-r--r--editor/import/resource_importer_bitmask.h2
-rw-r--r--editor/import/resource_importer_bmfont.h2
-rw-r--r--editor/import/resource_importer_csv_translation.h2
-rw-r--r--editor/import/resource_importer_dynamic_font.h2
-rw-r--r--editor/import/resource_importer_image.h2
-rw-r--r--editor/import/resource_importer_imagefont.h2
-rw-r--r--editor/import/resource_importer_layered_texture.h2
-rw-r--r--editor/import/resource_importer_shader_file.h2
-rw-r--r--editor/import/resource_importer_texture.h2
-rw-r--r--editor/import/resource_importer_texture_atlas.h2
-rw-r--r--editor/import/resource_importer_wav.cpp4
-rw-r--r--editor/import/resource_importer_wav.h2
-rw-r--r--editor/plugins/bone_map_editor_plugin.cpp4
-rw-r--r--editor/plugins/font_config_plugin.cpp2
-rw-r--r--editor/plugins/material_editor_plugin.cpp93
-rw-r--r--editor/plugins/material_editor_plugin.h7
-rw-r--r--editor/plugins/mesh_instance_3d_editor_plugin.cpp44
-rw-r--r--editor/plugins/mesh_instance_3d_editor_plugin.h2
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/tiles/tile_map_layer_editor.cpp2
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.h2
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp2
-rw-r--r--editor/project_converter_3_to_4.cpp6
-rw-r--r--editor/themes/editor_color_map.cpp2
-rw-r--r--methods.py120
-rw-r--r--modules/betsy/image_compress_betsy.h4
-rw-r--r--modules/camera/camera_feed_linux.cpp2
-rw-r--r--modules/fbx/fbx_document.cpp18
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp48
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp6
-rw-r--r--modules/gdscript/gdscript_warning.cpp7
-rw-r--r--modules/gdscript/gdscript_warning.h4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/shadowing_base.notest.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out32
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out4
-rw-r--r--modules/gltf/config.py1
-rw-r--r--modules/gltf/doc_classes/GLTFAccessor.xml38
-rw-r--r--modules/gltf/doc_classes/GLTFAnimation.xml2
-rw-r--r--modules/gltf/doc_classes/GLTFBufferView.xml4
-rw-r--r--modules/gltf/doc_classes/GLTFCamera.xml6
-rw-r--r--modules/gltf/doc_classes/GLTFDocument.xml20
-rw-r--r--modules/gltf/doc_classes/GLTFDocumentExtension.xml29
-rw-r--r--modules/gltf/doc_classes/GLTFMesh.xml2
-rw-r--r--modules/gltf/doc_classes/GLTFNode.xml11
-rw-r--r--modules/gltf/doc_classes/GLTFObjectModelProperty.xml114
-rw-r--r--modules/gltf/doc_classes/GLTFState.xml8
-rw-r--r--modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp2
-rw-r--r--modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp2
-rw-r--r--modules/gltf/editor/editor_scene_exporter_gltf_settings.h2
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp2
-rw-r--r--modules/gltf/extensions/gltf_document_extension.cpp18
-rw-r--r--modules/gltf/extensions/gltf_document_extension.h4
-rw-r--r--modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp1
-rw-r--r--modules/gltf/extensions/gltf_light.cpp16
-rw-r--r--modules/gltf/extensions/gltf_light.h3
-rw-r--r--modules/gltf/extensions/physics/gltf_document_extension_physics.cpp278
-rw-r--r--modules/gltf/extensions/physics/gltf_document_extension_physics.h3
-rw-r--r--modules/gltf/extensions/physics/gltf_physics_body.cpp3
-rw-r--r--modules/gltf/gltf_defines.h1
-rw-r--r--modules/gltf/gltf_document.cpp2310
-rw-r--r--modules/gltf/gltf_document.h53
-rw-r--r--modules/gltf/gltf_state.cpp24
-rw-r--r--modules/gltf/gltf_state.h13
-rw-r--r--modules/gltf/register_types.cpp2
-rw-r--r--modules/gltf/structures/gltf_accessor.cpp17
-rw-r--r--modules/gltf/structures/gltf_accessor.h20
-rw-r--r--modules/gltf/structures/gltf_animation.cpp40
-rw-r--r--modules/gltf/structures/gltf_animation.h26
-rw-r--r--modules/gltf/structures/gltf_camera.cpp16
-rw-r--r--modules/gltf/structures/gltf_camera.h3
-rw-r--r--modules/gltf/structures/gltf_node.cpp45
-rw-r--r--modules/gltf/structures/gltf_node.h3
-rw-r--r--modules/gltf/structures/gltf_object_model_property.cpp173
-rw-r--r--modules/gltf/structures/gltf_object_model_property.h104
-rw-r--r--modules/godot_physics_2d/godot_joints_2d.cpp2
-rw-r--r--modules/godot_physics_2d/godot_physics_server_2d.cpp4
-rw-r--r--modules/godot_physics_2d/godot_space_2d.cpp4
-rw-r--r--modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp11
-rw-r--r--modules/godot_physics_3d/godot_physics_server_3d.cpp2
-rw-r--r--modules/godot_physics_3d/godot_shape_3d.cpp3
-rw-r--r--modules/godot_physics_3d/godot_space_3d.cpp4
-rw-r--r--modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp2
-rw-r--r--modules/interactive_music/doc_classes/AudioStreamInteractive.xml2
-rw-r--r--modules/meshoptimizer/register_types.cpp4
-rw-r--r--modules/minimp3/resource_importer_mp3.h2
-rw-r--r--modules/navigation/nav_agent.h2
-rw-r--r--modules/navigation/nav_map.cpp61
-rw-r--r--modules/openxr/doc_classes/OpenXRCompositionLayer.xml2
-rw-r--r--modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml15
-rw-r--r--modules/openxr/extensions/openxr_composition_layer_extension.cpp2
-rw-r--r--modules/openxr/extensions/openxr_debug_utils_extension.cpp4
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper_extension.cpp13
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper_extension.h4
-rw-r--r--modules/openxr/extensions/platform/openxr_opengl_extension.cpp2
-rw-r--r--modules/openxr/openxr_api.cpp3
-rw-r--r--modules/text_server_adv/text_server_adv.cpp62
-rw-r--r--modules/text_server_fb/text_server_fb.cpp62
-rw-r--r--modules/theora/video_stream_theora.cpp2
-rw-r--r--modules/vorbis/resource_importer_ogg_vorbis.h2
-rw-r--r--modules/webp/webp_common.cpp2
-rw-r--r--platform/SCsub2
-rw-r--r--platform/android/display_server_android.cpp14
-rw-r--r--platform/android/display_server_android.h3
-rw-r--r--platform/android/export/export.cpp4
-rw-r--r--platform/android/export/export_plugin.cpp16
-rw-r--r--platform/android/java/editor/src/main/AndroidManifest.xml1
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt20
-rw-r--r--platform/android/java/editor/src/main/res/values/strings.xml1
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt6
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotIO.java22
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java16
-rw-r--r--platform/android/java/nativeSrcsConfigs/CMakeLists.txt2
-rw-r--r--platform/android/java_godot_io_wrapper.cpp11
-rw-r--r--platform/android/java_godot_io_wrapper.h2
-rw-r--r--platform/android/java_godot_wrapper.cpp18
-rw-r--r--platform/android/java_godot_wrapper.h3
-rw-r--r--platform/linuxbsd/os_linuxbsd.cpp8
-rw-r--r--platform/windows/SCsub1
-rw-r--r--platform/windows/crash_handler_windows_signal.cpp2
-rw-r--r--platform/windows/detect.py124
-rw-r--r--platform/windows/display_server_windows.cpp66
-rw-r--r--platform/windows/display_server_windows.h7
-rw-r--r--platform/windows/drop_target_windows.cpp375
-rw-r--r--platform/windows/drop_target_windows.h77
-rw-r--r--platform/windows/godot.natvis20
-rw-r--r--platform/windows/joypad_windows.cpp3
-rw-r--r--platform/windows/native_menu_windows.cpp6
-rw-r--r--platform/windows/os_windows.cpp14
-rw-r--r--platform/windows/platform_windows_builders.py18
-rw-r--r--platform/windows/windows_utils.cpp4
-rw-r--r--scene/3d/skeleton_3d.cpp18
-rw-r--r--scene/3d/skeleton_3d.h2
-rw-r--r--scene/3d/skeleton_ik_3d.h2
-rw-r--r--scene/debugger/scene_debugger.cpp17
-rw-r--r--scene/debugger/scene_debugger.h1
-rw-r--r--scene/gui/code_edit.h6
-rw-r--r--scene/gui/file_dialog.cpp1
-rw-r--r--scene/gui/graph_edit.cpp2
-rw-r--r--scene/gui/line_edit.cpp2
-rw-r--r--scene/gui/text_edit.cpp6
-rw-r--r--scene/gui/text_edit.h2
-rw-r--r--scene/gui/tree.cpp10
-rw-r--r--scene/register_scene_types.cpp3
-rw-r--r--scene/resources/2d/skeleton/skeleton_modification_stack_2d.h2
-rw-r--r--scene/resources/2d/tile_set.cpp18
-rw-r--r--scene/resources/2d/tile_set.h2
-rw-r--r--scene/resources/3d/importer_mesh.cpp103
-rw-r--r--scene/resources/3d/importer_mesh.h2
-rw-r--r--scene/resources/audio_stream_wav.cpp2
-rw-r--r--scene/resources/environment.cpp5
-rw-r--r--scene/resources/font.cpp34
-rw-r--r--scene/resources/surface_tool.cpp2
-rw-r--r--scene/resources/surface_tool.h8
-rw-r--r--scene/resources/visual_shader.cpp6
-rw-r--r--scu_builders.py4
-rw-r--r--servers/audio/audio_stream.cpp2
-rw-r--r--servers/audio_server.cpp73
-rw-r--r--servers/audio_server.h8
-rw-r--r--servers/camera/camera_feed.cpp20
-rw-r--r--servers/camera/camera_feed.h5
-rw-r--r--servers/display_server.h7
-rw-r--r--servers/rendering/renderer_canvas_cull.cpp3
-rw-r--r--servers/rendering/renderer_rd/effects/copy_effects.cpp18
-rw-r--r--servers/rendering/renderer_rd/effects/copy_effects.h1
-rw-r--r--servers/rendering/renderer_rd/environment/gi.cpp2
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp12
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp4
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h12
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp41
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp1
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h26
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp47
-rw-r--r--servers/rendering/renderer_rd/renderer_compositor_rd.cpp4
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_render_rd.cpp1
-rw-r--r--servers/rendering/renderer_rd/shaders/effects/cube_to_dp.glsl5
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl44
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl29
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl328
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl54
-rw-r--r--servers/rendering/renderer_rd/storage_rd/particles_storage.cpp35
-rw-r--r--servers/rendering/renderer_rd/storage_rd/particles_storage.h8
-rw-r--r--servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h1
-rw-r--r--servers/rendering/renderer_rd/storage_rd/texture_storage.h6
-rw-r--r--servers/rendering/renderer_scene_cull.cpp12
-rw-r--r--servers/rendering/renderer_scene_cull.h1
-rw-r--r--servers/rendering/renderer_scene_render.cpp14
-rw-r--r--servers/rendering/renderer_scene_render.h9
-rw-r--r--servers/rendering/rendering_device.cpp9
-rw-r--r--servers/rendering/rendering_device.h1
-rw-r--r--servers/rendering/rendering_device_driver.cpp2
-rw-r--r--servers/rendering/rendering_device_driver.h4
-rw-r--r--servers/rendering/rendering_device_graph.cpp42
-rw-r--r--servers/rendering/rendering_device_graph.h2
-rw-r--r--servers/rendering/rendering_method.h1
-rw-r--r--servers/rendering/rendering_server_default.h4
-rw-r--r--servers/rendering/shader_language.h2
-rw-r--r--servers/rendering/storage/environment_storage.cpp12
-rw-r--r--servers/rendering/storage/environment_storage.h5
-rw-r--r--servers/rendering_server.cpp3
-rw-r--r--servers/rendering_server.h1
-rw-r--r--tests/scene/test_primitives.h18
-rw-r--r--tests/servers/test_navigation_server_3d.h2
-rw-r--r--tests/test_macros.h2
-rw-r--r--thirdparty/README.md9
-rw-r--r--thirdparty/certs/ca-certificates.crt149
-rw-r--r--thirdparty/misc/smolv.cpp8
-rw-r--r--thirdparty/misc/smolv.h4
-rw-r--r--thirdparty/openxr/patches/use-egl-from-glad.diff27
-rw-r--r--thirdparty/openxr/src/common/xr_dependencies.h8
294 files changed, 5308 insertions, 1947 deletions
diff --git a/.clang-tidy b/.clang-tidy
index 366781cc82..1eb974f3f8 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -1,14 +1,12 @@
----
-Checks: >-
- -*,
- cppcoreguidelines-pro-type-member-init,
- modernize-redundant-void-arg,
- modernize-use-bool-literals,
- modernize-use-default-member-init,
- modernize-use-nullptr,
- readability-braces-around-statements,
- readability-redundant-member-init
-WarningsAsErrors: ''
+Checks:
+ - -*
+ - cppcoreguidelines-pro-type-member-init
+ - modernize-redundant-void-arg
+ - modernize-use-bool-literals
+ - modernize-use-default-member-init
+ - modernize-use-nullptr
+ - readability-braces-around-statements
+ - readability-redundant-member-init
HeaderFileExtensions: ['', h, hh, hpp, hxx, inc, glsl]
ImplementationFileExtensions: [c, cc, cpp, cxx, m, mm, java]
HeaderFilterRegex: (core|doc|drivers|editor|main|modules|platform|scene|servers|tests)/
@@ -19,4 +17,3 @@ CheckOptions:
modernize-use-bool-literals.IgnoreMacros: false
modernize-use-default-member-init.IgnoreMacros: false
modernize-use-default-member-init.UseAssignment: true
-...
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index df5fd1beed..f149078d5d 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -60,3 +60,6 @@ b37fc1014abf7adda70dc30b0822d775b3a4433f
# Set clang-format `RemoveSemicolon` rule to `true`
0d350e71086fffce0553811739aae9f6ad66136c
+
+# Style: Apply clang-tidy fixes (superficial)
+bb5f390fb9b466be35a5df7651323d7e66afca31
diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml
index 348e2bb317..dc3d9f3786 100644
--- a/.github/workflows/linux_builds.yml
+++ b/.github/workflows/linux_builds.yml
@@ -106,7 +106,7 @@ jobs:
# TODO: Figure out somehow how to embed this one.
- name: wayland-scanner dependency
run: |
- sudo apt-get install libwayland-bin libegl-dev
+ sudo apt-get install libwayland-bin
- name: Free disk space on runner
run: |
diff --git a/.github/workflows/web_builds.yml b/.github/workflows/web_builds.yml
index 8e30c99fbc..ec57fa2f0d 100644
--- a/.github/workflows/web_builds.yml
+++ b/.github/workflows/web_builds.yml
@@ -8,7 +8,6 @@ env:
GODOT_BASE_BRANCH: master
SCONSFLAGS: verbose=yes warnings=extra werror=yes debug_symbols=no use_closure_compiler=yes strict_checks=yes
EM_VERSION: 3.1.64
- EM_CACHE_FOLDER: emsdk-cache
concurrency:
group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-web
@@ -46,8 +45,7 @@ jobs:
uses: mymindstorm/setup-emsdk@v14
with:
version: ${{ env.EM_VERSION }}
- actions-cache-folder: ${{ env.EM_CACHE_FOLDER }}
- cache-key: emsdk-${{ matrix.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }}-${{ github.sha }}
+ no-cache: true
- name: Verify Emscripten setup
run: |
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 29263f07ee..d81c1043a7 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -9,7 +9,7 @@ exclude: |
repos:
- repo: https://github.com/pre-commit/mirrors-clang-format
- rev: v19.1.0
+ rev: v19.1.3
hooks:
- id: clang-format
files: \.(c|h|cpp|hpp|cc|hh|cxx|hxx|m|mm|inc|java)$
@@ -22,7 +22,7 @@ repos:
)
- id: clang-format
name: clang-format-glsl
- files: \.(glsl)$
+ files: \.glsl$
types_or: [text]
exclude: |
(?x)^(
@@ -30,7 +30,7 @@ repos:
platform/android/java/editor/src/main/java/com/android/.*|
platform/android/java/lib/src/com/.*
)
- args: ["-style=file:misc/utility/.clang-format-glsl"]
+ args: ['-style=file:misc/utility/.clang-format-glsl']
- repo: https://github.com/pocc/pre-commit-hooks
rev: v1.3.5
@@ -45,7 +45,7 @@ repos:
platform/android/java/editor/src/main/java/com/android/.*|
platform/android/java/lib/src/com/.*
)
- additional_dependencies: [clang-tidy==18.1.1]
+ additional_dependencies: [clang-tidy==19.1.0]
require_serial: true
stages: [manual] # Not automatically triggered, invoked via `pre-commit run --hook-stage manual clang-tidy`
diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt
index 5555e86541..2ece06eebb 100644
--- a/COPYRIGHT.txt
+++ b/COPYRIGHT.txt
@@ -432,7 +432,7 @@ License: BSD-3-clause
Files: ./thirdparty/misc/smolv.cpp
./thirdparty/misc/smolv.h
Comment: SMOL-V
-Copyright: 2016-2020, Aras Pranckevicius
+Copyright: 2016-2024, Aras Pranckevicius
License: public-domain or Unlicense or Expat
Files: ./thirdparty/misc/stb_rect_pack.h
diff --git a/core/input/input.cpp b/core/input/input.cpp
index 4117193c8b..7e2227c729 100644
--- a/core/input/input.cpp
+++ b/core/input/input.cpp
@@ -1034,7 +1034,7 @@ void Input::action_release(const StringName &p_action) {
// Create or retrieve existing action.
ActionState &action_state = action_states[p_action];
- action_state.cache.pressed = 0;
+ action_state.cache.pressed = false;
action_state.cache.strength = 0.0;
action_state.cache.raw_strength = 0.0;
// As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick.
diff --git a/core/io/resource_importer.h b/core/io/resource_importer.h
index 221f38494b..3ca8f7c05d 100644
--- a/core/io/resource_importer.h
+++ b/core/io/resource_importer.h
@@ -148,7 +148,7 @@ public:
virtual String get_option_group_file() const { return String(); }
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) = 0;
- virtual bool can_import_threaded() const { return true; }
+ virtual bool can_import_threaded() const { return false; }
virtual void import_threaded_begin() {}
virtual void import_threaded_end() {}
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index bad224eff4..d48e1a3622 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -750,69 +750,87 @@ void ClassDB::set_object_extension_instance(Object *p_object, const StringName &
}
bool ClassDB::can_instantiate(const StringName &p_class) {
- OBJTYPE_RLOCK;
+ String script_path;
+ {
+ OBJTYPE_RLOCK;
- ClassInfo *ti = classes.getptr(p_class);
- if (!ti) {
- if (!ScriptServer::is_global_class(p_class)) {
- ERR_FAIL_V_MSG(false, vformat("Cannot get class '%s'.", String(p_class)));
+ ClassInfo *ti = classes.getptr(p_class);
+ if (!ti) {
+ if (!ScriptServer::is_global_class(p_class)) {
+ ERR_FAIL_V_MSG(false, vformat("Cannot get class '%s'.", String(p_class)));
+ }
+ script_path = ScriptServer::get_global_class_path(p_class);
+ goto use_script; // Open the lock for resource loading.
}
- String path = ScriptServer::get_global_class_path(p_class);
- Ref<Script> scr = ResourceLoader::load(path);
- return scr.is_valid() && scr->is_valid() && !scr->is_abstract();
- }
#ifdef TOOLS_ENABLED
- if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) {
- return false;
- }
+ if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) {
+ return false;
+ }
#endif
- return _can_instantiate(ti);
+ return _can_instantiate(ti);
+ }
+
+use_script:
+ Ref<Script> scr = ResourceLoader::load(script_path);
+ return scr.is_valid() && scr->is_valid() && !scr->is_abstract();
}
bool ClassDB::is_abstract(const StringName &p_class) {
- OBJTYPE_RLOCK;
+ String script_path;
+ {
+ OBJTYPE_RLOCK;
- ClassInfo *ti = classes.getptr(p_class);
- if (!ti) {
- if (!ScriptServer::is_global_class(p_class)) {
- ERR_FAIL_V_MSG(false, vformat("Cannot get class '%s'.", String(p_class)));
+ ClassInfo *ti = classes.getptr(p_class);
+ if (!ti) {
+ if (!ScriptServer::is_global_class(p_class)) {
+ ERR_FAIL_V_MSG(false, vformat("Cannot get class '%s'.", String(p_class)));
+ }
+ script_path = ScriptServer::get_global_class_path(p_class);
+ goto use_script; // Open the lock for resource loading.
}
- String path = ScriptServer::get_global_class_path(p_class);
- Ref<Script> scr = ResourceLoader::load(path);
- return scr.is_valid() && scr->is_valid() && scr->is_abstract();
- }
- if (ti->creation_func != nullptr) {
- return false;
- }
- if (!ti->gdextension) {
- return true;
- }
+ if (ti->creation_func != nullptr) {
+ return false;
+ }
+ if (!ti->gdextension) {
+ return true;
+ }
#ifndef DISABLE_DEPRECATED
- return ti->gdextension->create_instance2 == nullptr && ti->gdextension->create_instance == nullptr;
+ return ti->gdextension->create_instance2 == nullptr && ti->gdextension->create_instance == nullptr;
#else
- return ti->gdextension->create_instance2 == nullptr;
+ return ti->gdextension->create_instance2 == nullptr;
#endif // DISABLE_DEPRECATED
+ }
+
+use_script:
+ Ref<Script> scr = ResourceLoader::load(script_path);
+ return scr.is_valid() && scr->is_valid() && scr->is_abstract();
}
bool ClassDB::is_virtual(const StringName &p_class) {
- OBJTYPE_RLOCK;
+ String script_path;
+ {
+ OBJTYPE_RLOCK;
- ClassInfo *ti = classes.getptr(p_class);
- if (!ti) {
- if (!ScriptServer::is_global_class(p_class)) {
- ERR_FAIL_V_MSG(false, vformat("Cannot get class '%s'.", String(p_class)));
+ ClassInfo *ti = classes.getptr(p_class);
+ if (!ti) {
+ if (!ScriptServer::is_global_class(p_class)) {
+ ERR_FAIL_V_MSG(false, vformat("Cannot get class '%s'.", String(p_class)));
+ }
+ script_path = ScriptServer::get_global_class_path(p_class);
+ goto use_script; // Open the lock for resource loading.
}
- String path = ScriptServer::get_global_class_path(p_class);
- Ref<Script> scr = ResourceLoader::load(path);
- return scr.is_valid() && scr->is_valid() && scr->is_abstract();
- }
#ifdef TOOLS_ENABLED
- if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) {
- return false;
- }
+ if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) {
+ return false;
+ }
#endif
- return (_can_instantiate(ti) && ti->is_virtual);
+ return (_can_instantiate(ti) && ti->is_virtual);
+ }
+
+use_script:
+ Ref<Script> scr = ResourceLoader::load(script_path);
+ return scr.is_valid() && scr->is_valid() && scr->is_abstract();
}
void ClassDB::_add_class2(const StringName &p_class, const StringName &p_inherits) {
diff --git a/core/object/message_queue.h b/core/object/message_queue.h
index 673eb3845b..64e244bda8 100644
--- a/core/object/message_queue.h
+++ b/core/object/message_queue.h
@@ -153,7 +153,7 @@ public:
bool is_flushing() const;
int get_max_buffer_usage() const;
- CallQueue(Allocator *p_custom_allocator = 0, uint32_t p_max_pages = 8192, const String &p_error_text = String());
+ CallQueue(Allocator *p_custom_allocator = nullptr, uint32_t p_max_pages = 8192, const String &p_error_text = String());
virtual ~CallQueue();
};
diff --git a/core/templates/list.h b/core/templates/list.h
index 6663f06c30..02afeec74d 100644
--- a/core/templates/list.h
+++ b/core/templates/list.h
@@ -224,7 +224,7 @@ private:
Element *last = nullptr;
int size_cache = 0;
- bool erase(const Element *p_I) {
+ bool erase(Element *p_I) {
ERR_FAIL_NULL_V(p_I, false);
ERR_FAIL_COND_V(p_I->data != this, false);
@@ -244,7 +244,7 @@ private:
p_I->next_ptr->prev_ptr = p_I->prev_ptr;
}
- memdelete_allocator<Element, A>(const_cast<Element *>(p_I));
+ memdelete_allocator<Element, A>(p_I);
size_cache--;
return true;
@@ -430,7 +430,7 @@ public:
/**
* erase an element in the list, by iterator pointing to it. Return true if it was found/erased.
*/
- bool erase(const Element *p_I) {
+ bool erase(Element *p_I) {
if (_data && p_I) {
bool ret = _data->erase(p_I);
diff --git a/core/typedefs.h b/core/typedefs.h
index 85d62df96b..35c4668581 100644
--- a/core/typedefs.h
+++ b/core/typedefs.h
@@ -315,6 +315,4 @@ struct BuildIndexSequence<0, Is...> : IndexSequence<Is...> {};
#define ___gd_is_defined(val) ____gd_is_defined(__GDARG_PLACEHOLDER_##val)
#define GD_IS_DEFINED(x) ___gd_is_defined(x)
-#define FORCE_SEMICOLON ;
-
#endif // TYPEDEFS_H
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index 7550477d70..65bfc29a55 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -2719,8 +2719,7 @@ Variant::Variant(const Vector<Plane> &p_array) :
}
}
-Variant::Variant(const Vector<Face3> &p_face_array) :
- type(NIL) {
+Variant::Variant(const Vector<Face3> &p_face_array) {
PackedVector3Array vertices;
int face_count = p_face_array.size();
vertices.resize(face_count * 3);
@@ -2739,8 +2738,7 @@ Variant::Variant(const Vector<Face3> &p_face_array) :
*this = vertices;
}
-Variant::Variant(const Vector<Variant> &p_array) :
- type(NIL) {
+Variant::Variant(const Vector<Variant> &p_array) {
Array arr;
arr.resize(p_array.size());
for (int i = 0; i < p_array.size(); i++) {
@@ -2749,8 +2747,7 @@ Variant::Variant(const Vector<Variant> &p_array) :
*this = arr;
}
-Variant::Variant(const Vector<StringName> &p_array) :
- type(NIL) {
+Variant::Variant(const Vector<StringName> &p_array) {
PackedStringArray v;
int len = p_array.size();
v.resize(len);
@@ -2908,8 +2905,7 @@ Variant::Variant(const IPAddress &p_address) :
memnew_placement(_data._mem, String(p_address));
}
-Variant::Variant(const Variant &p_variant) :
- type(NIL) {
+Variant::Variant(const Variant &p_variant) {
reference(p_variant);
}
diff --git a/core/variant/variant.h b/core/variant/variant.h
index 3b1924e8ea..9702c67a37 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -814,8 +814,7 @@ public:
static void unregister_types();
Variant(const Variant &p_variant);
- _FORCE_INLINE_ Variant() :
- type(NIL) {}
+ _FORCE_INLINE_ Variant() {}
_FORCE_INLINE_ ~Variant() {
clear();
}
diff --git a/doc/classes/AnimationNodeStateMachineTransition.xml b/doc/classes/AnimationNodeStateMachineTransition.xml
index 7bd0bd7e7e..c729eeebba 100644
--- a/doc/classes/AnimationNodeStateMachineTransition.xml
+++ b/doc/classes/AnimationNodeStateMachineTransition.xml
@@ -26,7 +26,7 @@
Use an expression as a condition for state machine transitions. It is possible to create complex animation advance conditions for switching between states and gives much greater flexibility for creating complex state machines by directly interfacing with the script code.
</member>
<member name="advance_mode" type="int" setter="set_advance_mode" getter="get_advance_mode" enum="AnimationNodeStateMachineTransition.AdvanceMode" default="1">
- Determines whether the transition should disabled, enabled when using [method AnimationNodeStateMachinePlayback.travel], or traversed automatically if the [member advance_condition] and [member advance_expression] checks are true (if assigned).
+ Determines whether the transition should be disabled, enabled when using [method AnimationNodeStateMachinePlayback.travel], or traversed automatically if the [member advance_condition] and [member advance_expression] checks are [code]true[/code] (if assigned).
</member>
<member name="break_loop_at_end" type="bool" setter="set_break_loop_at_end" getter="is_loop_broken_at_end" default="false">
If [code]true[/code], breaks the loop at the end of the loop cycle for transition, even if the animation is looping.
@@ -72,7 +72,7 @@
Only use this transition during [method AnimationNodeStateMachinePlayback.travel].
</constant>
<constant name="ADVANCE_MODE_AUTO" value="2" enum="AdvanceMode">
- Automatically use this transition if the [member advance_condition] and [member advance_expression] checks are true (if assigned).
+ Automatically use this transition if the [member advance_condition] and [member advance_expression] checks are [code]true[/code] (if assigned).
</constant>
</constants>
</class>
diff --git a/doc/classes/AudioStreamPlaybackPolyphonic.xml b/doc/classes/AudioStreamPlaybackPolyphonic.xml
index f71762d6a5..894c9b2262 100644
--- a/doc/classes/AudioStreamPlaybackPolyphonic.xml
+++ b/doc/classes/AudioStreamPlaybackPolyphonic.xml
@@ -13,7 +13,7 @@
<return type="bool" />
<param index="0" name="stream" type="int" />
<description>
- Return true whether the stream associated with an integer ID is still playing. Check [method play_stream] for information on when this ID becomes invalid.
+ Returns [code]true[/code] if the stream associated with the given integer ID is still playing. Check [method play_stream] for information on when this ID becomes invalid.
</description>
</method>
<method name="play_stream">
diff --git a/doc/classes/CameraFeed.xml b/doc/classes/CameraFeed.xml
index 2b6db13906..6748fdb95b 100644
--- a/doc/classes/CameraFeed.xml
+++ b/doc/classes/CameraFeed.xml
@@ -34,6 +34,21 @@
Returns the position of camera on the device.
</description>
</method>
+ <method name="get_texture_tex_id">
+ <return type="int" />
+ <param index="0" name="feed_image_type" type="int" enum="CameraServer.FeedImage" />
+ <description>
+ Returns the texture backend ID (usable by some external libraries that need a handle to a texture to write data).
+ </description>
+ </method>
+ <method name="set_external">
+ <return type="void" />
+ <param index="0" name="width" type="int" />
+ <param index="1" name="height" type="int" />
+ <description>
+ Sets the feed as external feed provided by another library.
+ </description>
+ </method>
<method name="set_format">
<return type="bool" />
<param index="0" name="index" type="int" />
@@ -110,6 +125,9 @@
<constant name="FEED_YCBCR_SEP" value="3" enum="FeedDataType">
Feed supplies separate Y and CbCr images that need to be combined and converted to RGB.
</constant>
+ <constant name="FEED_EXTERNAL" value="4" enum="FeedDataType">
+ Feed supplies external image.
+ </constant>
<constant name="FEED_UNSPECIFIED" value="0" enum="FeedPosition">
Unspecified position.
</constant>
diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml
index d455bd7d68..0131f8f4af 100644
--- a/doc/classes/Control.xml
+++ b/doc/classes/Control.xml
@@ -993,8 +993,9 @@
Controls whether the control will be able to receive mouse button input events through [method _gui_input] and how these events should be handled. Also controls whether the control can receive the [signal mouse_entered], and [signal mouse_exited] signals. See the constants to learn what each does.
</member>
<member name="mouse_force_pass_scroll_events" type="bool" setter="set_force_pass_scroll_events" getter="is_force_pass_scroll_events" default="true">
- When enabled, scroll wheel events processed by [method _gui_input] will be passed to the parent control even if [member mouse_filter] is set to [constant MOUSE_FILTER_STOP]. As it defaults to true, this allows nested scrollable containers to work out of the box.
+ When enabled, scroll wheel events processed by [method _gui_input] will be passed to the parent control even if [member mouse_filter] is set to [constant MOUSE_FILTER_STOP].
You should disable it on the root of your UI if you do not want scroll events to go to the [method Node._unhandled_input] processing.
+ [b]Note:[/b] Because this property defaults to [code]true[/code], this allows nested scrollable containers to work out of the box.
</member>
<member name="offset_bottom" type="float" setter="set_offset" getter="get_offset" default="0.0">
Distance between the node's bottom edge and its parent control, based on [member anchor_bottom].
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 3142c60e3b..dafa86d42e 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -185,7 +185,7 @@
<return type="Color" />
<description>
Returns OS theme accent color. Returns [code]Color(0, 0, 0, 0)[/code], if accent color is unknown.
- [b]Note:[/b] This method is implemented on macOS and Windows.
+ [b]Note:[/b] This method is implemented on macOS, Windows, and Android.
</description>
</method>
<method name="get_base_color" qualifiers="const">
diff --git a/doc/classes/EditorFileSystemImportFormatSupportQuery.xml b/doc/classes/EditorFileSystemImportFormatSupportQuery.xml
index b1a810fe6e..fc2feca911 100644
--- a/doc/classes/EditorFileSystemImportFormatSupportQuery.xml
+++ b/doc/classes/EditorFileSystemImportFormatSupportQuery.xml
@@ -24,7 +24,7 @@
<method name="_query" qualifiers="virtual const">
<return type="bool" />
<description>
- Query support. Return false if import must not continue.
+ Query support. Return [code]false[/code] if import must not continue.
</description>
</method>
</methods>
diff --git a/doc/classes/EditorImportPlugin.xml b/doc/classes/EditorImportPlugin.xml
index 28614b6631..e5f3010366 100644
--- a/doc/classes/EditorImportPlugin.xml
+++ b/doc/classes/EditorImportPlugin.xml
@@ -124,7 +124,8 @@
<return type="bool" />
<description>
Tells whether this importer can be run in parallel on threads, or, on the contrary, it's only safe for the editor to call it from the main thread, for one file at a time.
- If this method is not overridden, it will return [code]true[/code] by default (i.e., safe for parallel importing).
+ If this method is not overridden, it will return [code]false[/code] by default.
+ If this importer's implementation is thread-safe and can be run in parallel, override this with [code]true[/code] to optimize for concurrency.
</description>
</method>
<method name="_get_import_options" qualifiers="virtual const">
diff --git a/doc/classes/EditorResourcePreview.xml b/doc/classes/EditorResourcePreview.xml
index ed3fdae352..a0c1a43fb2 100644
--- a/doc/classes/EditorResourcePreview.xml
+++ b/doc/classes/EditorResourcePreview.xml
@@ -32,7 +32,7 @@
<param index="3" name="userdata" type="Variant" />
<description>
Queue the [param resource] being edited for preview. Once the preview is ready, the [param receiver]'s [param receiver_func] will be called. The [param receiver_func] must take the following four arguments: [String] path, [Texture2D] preview, [Texture2D] thumbnail_preview, [Variant] userdata. [param userdata] can be anything, and will be returned when [param receiver_func] is called.
- [b]Note:[/b] If it was not possible to create the preview the [param receiver_func] will still be called, but the preview will be null.
+ [b]Note:[/b] If it was not possible to create the preview the [param receiver_func] will still be called, but the preview will be [code]null[/code].
</description>
</method>
<method name="queue_resource_preview">
@@ -43,7 +43,7 @@
<param index="3" name="userdata" type="Variant" />
<description>
Queue a resource file located at [param path] for preview. Once the preview is ready, the [param receiver]'s [param receiver_func] will be called. The [param receiver_func] must take the following four arguments: [String] path, [Texture2D] preview, [Texture2D] thumbnail_preview, [Variant] userdata. [param userdata] can be anything, and will be returned when [param receiver_func] is called.
- [b]Note:[/b] If it was not possible to create the preview the [param receiver_func] will still be called, but the preview will be null.
+ [b]Note:[/b] If it was not possible to create the preview the [param receiver_func] will still be called, but the preview will be [code]null[/code].
</description>
</method>
<method name="remove_preview_generator">
diff --git a/doc/classes/EditorScenePostImportPlugin.xml b/doc/classes/EditorScenePostImportPlugin.xml
index 5c1c898c9d..0004ec65c6 100644
--- a/doc/classes/EditorScenePostImportPlugin.xml
+++ b/doc/classes/EditorScenePostImportPlugin.xml
@@ -28,7 +28,7 @@
<param index="0" name="category" type="int" />
<param index="1" name="option" type="String" />
<description>
- Return true whether updating the 3D view of the import dialog needs to be updated if an option has changed.
+ Should return [code]true[/code] if the 3D view of the import dialog needs to update when changing the given option.
</description>
</method>
<method name="_get_internal_option_visibility" qualifiers="virtual const">
@@ -37,7 +37,7 @@
<param index="1" name="for_animation" type="bool" />
<param index="2" name="option" type="String" />
<description>
- Return true or false whether a given option should be visible. Return null to ignore.
+ Should return [code]true[/code] to show the given option, [code]false[/code] to hide the given option, or [code]null[/code] to ignore.
</description>
</method>
<method name="_get_option_visibility" qualifiers="virtual const">
@@ -46,7 +46,7 @@
<param index="1" name="for_animation" type="bool" />
<param index="2" name="option" type="String" />
<description>
- Return true or false whether a given option should be visible. Return null to ignore.
+ Should return [code]true[/code] to show the given option, [code]false[/code] to hide the given option, or [code]null[/code] to ignore.
</description>
</method>
<method name="_internal_process" qualifiers="virtual">
diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml
index a5097521dc..8287671578 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -152,7 +152,7 @@
<param index="1" name="value" type="Variant" />
<param index="2" name="update_current" type="bool" />
<description>
- Sets the initial value of the setting specified by [param name] to [param value]. This is used to provide a value for the Revert button in the Editor Settings. If [param update_current] is true, the current value of the setting will be set to [param value] as well.
+ Sets the initial value of the setting specified by [param name] to [param value]. This is used to provide a value for the Revert button in the Editor Settings. If [param update_current] is [code]true[/code], the setting is reset to [param value] as well.
</description>
</method>
<method name="set_project_metadata">
@@ -1395,7 +1395,7 @@
The script editor's documentation comment color. In GDScript, this is used for comments starting with [code]##[/code]. In C#, this is used for comments starting with [code]///[/code] or [code]/**[/code].
</member>
<member name="text_editor/theme/highlighting/engine_type_color" type="Color" setter="" getter="">
- The script editor's engine type color ([Vector2], [Vector3], [Color], ...).
+ The script editor's engine type color ([Object], [Mesh], [Node], ...).
</member>
<member name="text_editor/theme/highlighting/executing_line_color" type="Color" setter="" getter="">
The script editor's color for the debugger's executing line icon (displayed in the gutter).
diff --git a/doc/classes/EditorUndoRedoManager.xml b/doc/classes/EditorUndoRedoManager.xml
index 0f8c69a1bb..6a66a35ce8 100644
--- a/doc/classes/EditorUndoRedoManager.xml
+++ b/doc/classes/EditorUndoRedoManager.xml
@@ -87,7 +87,7 @@
<return type="void" />
<param index="0" name="execute" type="bool" default="true" />
<description>
- Commit the action. If [param execute] is true (default), all "do" methods/properties are called/set when this function is called.
+ Commits the action. If [param execute] is [code]true[/code] (default), all "do" methods/properties are called/set when this function is called.
</description>
</method>
<method name="create_action">
diff --git a/doc/classes/GraphNode.xml b/doc/classes/GraphNode.xml
index cc3acad6d6..fc8bc15b5c 100644
--- a/doc/classes/GraphNode.xml
+++ b/doc/classes/GraphNode.xml
@@ -154,7 +154,7 @@
<return type="bool" />
<param index="0" name="slot_index" type="int" />
<description>
- Returns true if the background [StyleBox] of the slot with the given [param slot_index] is drawn.
+ Returns [code]true[/code] if the background [StyleBox] of the slot with the given [param slot_index] is drawn.
</description>
</method>
<method name="is_slot_enabled_left" qualifiers="const">
diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml
index 0fd84fb452..9b71a8c37f 100644
--- a/doc/classes/Image.xml
+++ b/doc/classes/Image.xml
@@ -510,7 +510,7 @@
<param index="1" name="lossy" type="bool" default="false" />
<param index="2" name="quality" type="float" default="0.75" />
<description>
- Saves the image as a WebP (Web Picture) file to the file at [param path]. By default it will save lossless. If [param lossy] is true, the image will be saved lossy, using the [param quality] setting between 0.0 and 1.0 (inclusive). Lossless WebP offers more efficient compression than PNG.
+ Saves the image as a WebP (Web Picture) file to the file at [param path]. By default it will save lossless. If [param lossy] is [code]true[/code], the image will be saved lossy, using the [param quality] setting between [code]0.0[/code] and [code]1.0[/code] (inclusive). Lossless WebP offers more efficient compression than PNG.
[b]Note:[/b] The WebP format is limited to a size of 16383×16383 pixels, while PNG can save larger images.
</description>
</method>
@@ -519,7 +519,7 @@
<param index="0" name="lossy" type="bool" default="false" />
<param index="1" name="quality" type="float" default="0.75" />
<description>
- Saves the image as a WebP (Web Picture) file to a byte array. By default it will save lossless. If [param lossy] is true, the image will be saved lossy, using the [param quality] setting between 0.0 and 1.0 (inclusive). Lossless WebP offers more efficient compression than PNG.
+ Saves the image as a WebP (Web Picture) file to a byte array. By default it will save lossless. If [param lossy] is [code]true[/code], the image will be saved lossy, using the [param quality] setting between [code]0.0[/code] and [code]1.0[/code] (inclusive). Lossless WebP offers more efficient compression than PNG.
[b]Note:[/b] The WebP format is limited to a size of 16383×16383 pixels, while PNG can save larger images.
</description>
</method>
diff --git a/doc/classes/MultiplayerPeer.xml b/doc/classes/MultiplayerPeer.xml
index edb2c39e24..cec9464102 100644
--- a/doc/classes/MultiplayerPeer.xml
+++ b/doc/classes/MultiplayerPeer.xml
@@ -65,7 +65,7 @@
<method name="is_server_relay_supported" qualifiers="const">
<return type="bool" />
<description>
- Returns true if the server can act as a relay in the current configuration (i.e. if the higher level [MultiplayerAPI] should notify connected clients of other peers, and implement a relay protocol to allow communication between them).
+ Returns [code]true[/code] if the server can act as a relay in the current configuration. That is, if the higher level [MultiplayerAPI] should notify connected clients of other peers, and implement a relay protocol to allow communication between them.
</description>
</method>
<method name="poll">
diff --git a/doc/classes/NavigationServer2D.xml b/doc/classes/NavigationServer2D.xml
index 7e78006240..5c19a6b355 100644
--- a/doc/classes/NavigationServer2D.xml
+++ b/doc/classes/NavigationServer2D.xml
@@ -133,7 +133,7 @@
<return type="bool" />
<param index="0" name="agent" type="RID" />
<description>
- Returns true if the map got changed the previous frame.
+ Returns [code]true[/code] if the map got changed the previous frame.
</description>
</method>
<method name="agent_set_avoidance_callback">
@@ -215,7 +215,7 @@
<param index="0" name="agent" type="RID" />
<param index="1" name="paused" type="bool" />
<description>
- If [param paused] is true the specified [param agent] will not be processed, e.g. calculate avoidance velocities or receive avoidance callbacks.
+ If [param paused] is [code]true[/code] the specified [param agent] will not be processed, e.g. calculate avoidance velocities or receive avoidance callbacks.
</description>
</method>
<method name="agent_set_position">
@@ -573,7 +573,7 @@
<return type="bool" />
<param index="0" name="map" type="RID" />
<description>
- Returns true if the map is active.
+ Returns [code]true[/code] if the map is active.
</description>
</method>
<method name="map_set_active">
@@ -707,7 +707,7 @@
<param index="0" name="obstacle" type="RID" />
<param index="1" name="paused" type="bool" />
<description>
- If [param paused] is true the specified [param obstacle] will not be processed, e.g. affect avoidance velocities.
+ If [param paused] is [code]true[/code] the specified [param obstacle] will not be processed, e.g. affect avoidance velocities.
</description>
</method>
<method name="obstacle_set_position">
diff --git a/doc/classes/NavigationServer3D.xml b/doc/classes/NavigationServer3D.xml
index 7e206046d6..66a286758b 100644
--- a/doc/classes/NavigationServer3D.xml
+++ b/doc/classes/NavigationServer3D.xml
@@ -147,7 +147,7 @@
<return type="bool" />
<param index="0" name="agent" type="RID" />
<description>
- Returns true if the map got changed the previous frame.
+ Returns [code]true[/code] if the map got changed the previous frame.
</description>
</method>
<method name="agent_set_avoidance_callback">
@@ -237,7 +237,7 @@
<param index="0" name="agent" type="RID" />
<param index="1" name="paused" type="bool" />
<description>
- If [param paused] is true the specified [param agent] will not be processed, e.g. calculate avoidance velocities or receive avoidance callbacks.
+ If [param paused] is [code]true[/code] the specified [param agent] will not be processed, e.g. calculate avoidance velocities or receive avoidance callbacks.
</description>
</method>
<method name="agent_set_position">
@@ -645,14 +645,14 @@
<return type="bool" />
<param index="0" name="map" type="RID" />
<description>
- Returns true if the navigation [param map] allows navigation regions to use edge connections to connect with other navigation regions within proximity of the navigation map edge connection margin.
+ Returns [code]true[/code] if the navigation [param map] allows navigation regions to use edge connections to connect with other navigation regions within proximity of the navigation map edge connection margin.
</description>
</method>
<method name="map_is_active" qualifiers="const">
<return type="bool" />
<param index="0" name="map" type="RID" />
<description>
- Returns true if the map is active.
+ Returns [code]true[/code] if the map is active.
</description>
</method>
<method name="map_set_active">
@@ -832,7 +832,7 @@
<param index="0" name="obstacle" type="RID" />
<param index="1" name="paused" type="bool" />
<description>
- If [param paused] is true the specified [param obstacle] will not be processed, e.g. affect avoidance velocities.
+ If [param paused] is [code]true[/code] the specified [param obstacle] will not be processed, e.g. affect avoidance velocities.
</description>
</method>
<method name="obstacle_set_position">
@@ -1023,7 +1023,7 @@
<return type="bool" />
<param index="0" name="region" type="RID" />
<description>
- Returns true if the navigation [param region] is set to use edge connections to connect with other navigation regions within proximity of the navigation map edge connection margin.
+ Returns [code]true[/code] if the navigation [param region] is set to use edge connections to connect with other navigation regions within proximity of the navigation map edge connection margin.
</description>
</method>
<method name="region_owns_point" qualifiers="const">
diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml
index 2767a11e80..a130a71826 100644
--- a/doc/classes/Object.xml
+++ b/doc/classes/Object.xml
@@ -454,7 +454,7 @@
<return type="Variant" />
<param index="0" name="method" type="StringName" />
<description>
- Calls the [param method] on the object during idle time. Always returns null, [b]not[/b] the method's result.
+ Calls the [param method] on the object during idle time. Always returns [code]null[/code], [b]not[/b] the method's result.
Idle time happens mainly at the end of process and physics frames. In it, deferred calls will be run until there are none left, which means you can defer calls from other deferred calls and they'll still be run in the current idle time cycle. This means you should not call a method deferred from itself (or from a method called by it), as this causes infinite recursion the same way as if you had called the method directly.
This method supports a variable number of arguments, so parameters can be passed as a comma separated list.
[codeblocks]
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 3358e6838c..fff6c8d3bb 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -469,10 +469,10 @@
If the [code]--log-file &lt;file&gt;[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url] is used, log rotation is always disabled.
</member>
<member name="debug/gdscript/warnings/assert_always_false" type="int" setter="" getter="" default="1">
- When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an [code]assert[/code] call always evaluates to false.
+ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an [code]assert[/code] call always evaluates to [code]false[/code].
</member>
<member name="debug/gdscript/warnings/assert_always_true" type="int" setter="" getter="" default="1">
- When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an [code]assert[/code] call always evaluates to true.
+ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an [code]assert[/code] call always evaluates to [code]true[/code].
</member>
<member name="debug/gdscript/warnings/confusable_capture_reassignment" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a local variable captured by a lambda is reassigned, since this does not modify the outer local variable.
@@ -561,10 +561,10 @@
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when defining a local or member variable, signal, or enum that would have the same name as a built-in function or global class name, thus shadowing it.
</member>
<member name="debug/gdscript/warnings/shadowed_variable" type="int" setter="" getter="" default="1">
- When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when defining a local or member variable that would shadow a member variable that the class defines.
+ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a local variable or local constant shadows a member declared in the current class.
</member>
<member name="debug/gdscript/warnings/shadowed_variable_base_class" type="int" setter="" getter="" default="1">
- When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when defining a local or subclass member variable that would shadow a variable that is inherited from a parent class.
+ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a local variable or local constant shadows a member declared in a base class.
</member>
<member name="debug/gdscript/warnings/standalone_expression" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when calling an expression that may have no effect on the surrounding code, such as writing [code]2 + 2[/code] as a statement.
@@ -3014,7 +3014,7 @@
Specify whether OpenXR should be configured for an HMD or a hand held device.
</member>
<member name="xr/openxr/foveation_dynamic" type="bool" setter="" getter="" default="false">
- If true and foveation is supported, will automatically adjust foveation level based on framerate up to the level set on [member xr/openxr/foveation_level].
+ If [code]true[/code] and foveation is supported, will automatically adjust foveation level based on framerate up to the level set on [member xr/openxr/foveation_level].
[b]Note:[/b] Only works on the Compatibility rendering method.
</member>
<member name="xr/openxr/foveation_level" type="int" setter="" getter="" default="&quot;0&quot;">
diff --git a/doc/classes/RDPipelineDepthStencilState.xml b/doc/classes/RDPipelineDepthStencilState.xml
index dc1e70eb55..425890bb8d 100644
--- a/doc/classes/RDPipelineDepthStencilState.xml
+++ b/doc/classes/RDPipelineDepthStencilState.xml
@@ -34,10 +34,10 @@
The method used for comparing the previous and current depth values.
</member>
<member name="depth_range_max" type="float" setter="set_depth_range_max" getter="get_depth_range_max" default="0.0">
- The maximum depth that returns true for [member enable_depth_range].
+ The maximum depth that returns [code]true[/code] for [member enable_depth_range].
</member>
<member name="depth_range_min" type="float" setter="set_depth_range_min" getter="get_depth_range_min" default="0.0">
- The minimum depth that returns true for [member enable_depth_range].
+ The minimum depth that returns [code]true[/code] for [member enable_depth_range].
</member>
<member name="enable_depth_range" type="bool" setter="set_enable_depth_range" getter="get_enable_depth_range" default="false">
If [code]true[/code], each depth value will be tested to see if it is between [member depth_range_min] and [member depth_range_max]. If it is outside of these values, it is discarded.
@@ -46,7 +46,7 @@
If [code]true[/code], enables depth testing which allows objects to be automatically occluded by other objects based on their depth. This also allows objects to be partially occluded by other objects. If [code]false[/code], objects will appear in the order they were drawn (like in Godot's 2D renderer).
</member>
<member name="enable_depth_write" type="bool" setter="set_enable_depth_write" getter="get_enable_depth_write" default="false">
- If [code]true[/code], writes to the depth buffer whenever the depth test returns true. Only works when enable_depth_test is also true.
+ If [code]true[/code], writes to the depth buffer whenever the depth test returns [code]true[/code]. Only works when enable_depth_test is also [code]true[/code].
</member>
<member name="enable_stencil" type="bool" setter="set_enable_stencil" getter="get_enable_stencil" default="false">
If [code]true[/code], enables stencil testing. There are separate stencil buffers for front-facing triangles and back-facing triangles. See properties that begin with "front_op" and properties with "back_op" for each.
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index 16a554eded..284dc07fd4 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -224,7 +224,7 @@
<param index="0" name="item" type="RID" />
<param index="1" name="ignore" type="bool" />
<description>
- If [param ignore] is [code]true[/code], ignore clipping on items drawn with this canvas item until this is called again with [param ignore] set to false.
+ If [param ignore] is [code]true[/code], ignore clipping on items drawn with this canvas item until this is called again with [param ignore] set to [code]false[/code].
</description>
</method>
<method name="canvas_item_add_lcd_texture_rect_region">
@@ -1278,6 +1278,14 @@
Sets the intensity of the background color.
</description>
</method>
+ <method name="environment_set_camera_id">
+ <return type="void" />
+ <param index="0" name="env" type="RID" />
+ <param index="1" name="id" type="int" />
+ <description>
+ Sets the camera ID to be used as environment background.
+ </description>
+ </method>
<method name="environment_set_canvas_max_layer">
<return type="void" />
<param index="0" name="env" type="RID" />
@@ -2949,7 +2957,7 @@
<param index="0" name="particles" type="RID" />
<param index="1" name="emitting" type="bool" />
<description>
- If [code]true[/code], particles will emit over time. Setting to false does not reset the particles, but only stops their emission. Equivalent to [member GPUParticles3D.emitting].
+ If [code]true[/code], particles will emit over time. Setting to [code]false[/code] does not reset the particles, but only stops their emission. Equivalent to [member GPUParticles3D.emitting].
</description>
</method>
<method name="particles_set_explosiveness_ratio">
diff --git a/doc/classes/ResourceLoader.xml b/doc/classes/ResourceLoader.xml
index 56c3208fc3..f718ad15d8 100644
--- a/doc/classes/ResourceLoader.xml
+++ b/doc/classes/ResourceLoader.xml
@@ -104,7 +104,7 @@
<param index="1" name="progress" type="Array" default="[]" />
<description>
Returns the status of a threaded loading operation started with [method load_threaded_request] for the resource at [param path]. See [enum ThreadLoadStatus] for possible return values.
- An array variable can optionally be passed via [param progress], and will return a one-element array containing the percentage of completion of the threaded loading.
+ An array variable can optionally be passed via [param progress], and will return a one-element array containing the ratio of completion of the threaded loading (between [code]0.0[/code] and [code]1.0[/code]).
[b]Note:[/b] The recommended way of using this method is to call it during different frames (e.g., in [method Node._process], instead of a loop).
</description>
</method>
diff --git a/doc/classes/Shader.xml b/doc/classes/Shader.xml
index 1e7f9e5ee5..3fbe1da687 100644
--- a/doc/classes/Shader.xml
+++ b/doc/classes/Shader.xml
@@ -31,8 +31,8 @@
<return type="Array" />
<param index="0" name="get_groups" type="bool" default="false" />
<description>
- Get the list of shader uniforms that can be assigned to a [ShaderMaterial], for use with [method ShaderMaterial.set_shader_parameter] and [method ShaderMaterial.get_shader_parameter]. The parameters returned are contained in dictionaries in a similar format to the ones returned by [method Object.get_property_list].
- If argument [param get_groups] is true, parameter grouping hints will be provided.
+ Returns the list of shader uniforms that can be assigned to a [ShaderMaterial], for use with [method ShaderMaterial.set_shader_parameter] and [method ShaderMaterial.get_shader_parameter]. The parameters returned are contained in dictionaries in a similar format to the ones returned by [method Object.get_property_list].
+ If argument [param get_groups] is [code]true[/code], parameter grouping hints are also included in the list.
</description>
</method>
<method name="inspect_native_shader_code">
diff --git a/doc/classes/SkeletonIK3D.xml b/doc/classes/SkeletonIK3D.xml
index 4858a6ce22..dbb099a22b 100644
--- a/doc/classes/SkeletonIK3D.xml
+++ b/doc/classes/SkeletonIK3D.xml
@@ -31,7 +31,7 @@
<method name="get_parent_skeleton" qualifiers="const">
<return type="Skeleton3D" />
<description>
- Returns the parent [Skeleton3D] Node that was present when SkeletonIK entered the [SceneTree]. Returns null if the parent node was not a [Skeleton3D] Node when SkeletonIK3D entered the [SceneTree].
+ Returns the parent [Skeleton3D] node that was present when SkeletonIK entered the scene tree. Returns [code]null[/code] if the parent node was not a [Skeleton3D] node when SkeletonIK3D entered the scene tree.
</description>
</method>
<method name="is_running">
diff --git a/doc/classes/Slider.xml b/doc/classes/Slider.xml
index a4ffa5c1e7..b1e78e8987 100644
--- a/doc/classes/Slider.xml
+++ b/doc/classes/Slider.xml
@@ -28,12 +28,12 @@
<signal name="drag_ended">
<param index="0" name="value_changed" type="bool" />
<description>
- Emitted when dragging stops. If [param value_changed] is true, [member Range.value] is different from the value when you started the dragging.
+ Emitted when the grabber stops being dragged. If [param value_changed] is [code]true[/code], [member Range.value] is different from the value when the dragging was started.
</description>
</signal>
<signal name="drag_started">
<description>
- Emitted when dragging is started. This is emitted before the corresponding [signal Range.value_changed] signal.
+ Emitted when the grabber starts being dragged. This is emitted before the corresponding [signal Range.value_changed] signal.
</description>
</signal>
</signals>
diff --git a/doc/classes/SpringArm3D.xml b/doc/classes/SpringArm3D.xml
index 99389fb78d..330317f352 100644
--- a/doc/classes/SpringArm3D.xml
+++ b/doc/classes/SpringArm3D.xml
@@ -7,6 +7,7 @@
[SpringArm3D] casts a ray or a shape along its Z axis and moves all its direct children to the collision point, with an optional margin. This is useful for 3rd person cameras that move closer to the player when inside a tight space (you may need to exclude the player's collider from the [SpringArm3D]'s collision check).
</description>
<tutorials>
+ <link title="Third-person camera with spring arm">$DOCS_URL/tutorials/3d/spring_arm.html</link>
</tutorials>
<methods>
<method name="add_excluded_object">
diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml
index 0995a5a672..4683f063f1 100644
--- a/doc/classes/TileMap.xml
+++ b/doc/classes/TileMap.xml
@@ -380,7 +380,7 @@
<param index="4" name="ignore_empty_terrains" type="bool" default="true" />
<description>
Update all the cells in the [param cells] coordinates array so that they use the given [param terrain] for the given [param terrain_set]. If an updated cell has the same terrain as one of its neighboring cells, this function tries to join the two. This function might update neighboring tiles if needed to create correct terrain transitions.
- If [param ignore_empty_terrains] is true, empty terrains will be ignored when trying to find the best fitting tile for the given terrain constraints.
+ If [param ignore_empty_terrains] is [code]true[/code], empty terrains will be ignored when trying to find the best fitting tile for the given terrain constraints.
If [param layer] is negative, the layers are accessed from the last one.
[b]Note:[/b] To work correctly, this method requires the TileMap's TileSet to have terrains set up with all required terrain combinations. Otherwise, it may produce unexpected results.
</description>
@@ -394,7 +394,7 @@
<param index="4" name="ignore_empty_terrains" type="bool" default="true" />
<description>
Update all the cells in the [param path] coordinates array so that they use the given [param terrain] for the given [param terrain_set]. The function will also connect two successive cell in the path with the same terrain. This function might update neighboring tiles if needed to create correct terrain transitions.
- If [param ignore_empty_terrains] is true, empty terrains will be ignored when trying to find the best fitting tile for the given terrain constraints.
+ If [param ignore_empty_terrains] is [code]true[/code], empty terrains will be ignored when trying to find the best fitting tile for the given terrain constraints.
If [param layer] is negative, the layers are accessed from the last one.
[b]Note:[/b] To work correctly, this method requires the TileMap's TileSet to have terrains set up with all required terrain combinations. Otherwise, it may produce unexpected results.
</description>
diff --git a/doc/classes/TileMapLayer.xml b/doc/classes/TileMapLayer.xml
index 135f85de69..6cbec9c2aa 100644
--- a/doc/classes/TileMapLayer.xml
+++ b/doc/classes/TileMapLayer.xml
@@ -228,7 +228,7 @@
<param index="3" name="ignore_empty_terrains" type="bool" default="true" />
<description>
Update all the cells in the [param cells] coordinates array so that they use the given [param terrain] for the given [param terrain_set]. If an updated cell has the same terrain as one of its neighboring cells, this function tries to join the two. This function might update neighboring tiles if needed to create correct terrain transitions.
- If [param ignore_empty_terrains] is true, empty terrains will be ignored when trying to find the best fitting tile for the given terrain constraints.
+ If [param ignore_empty_terrains] is [code]true[/code], empty terrains will be ignored when trying to find the best fitting tile for the given terrain constraints.
[b]Note:[/b] To work correctly, this method requires the [TileMapLayer]'s TileSet to have terrains set up with all required terrain combinations. Otherwise, it may produce unexpected results.
</description>
</method>
@@ -240,7 +240,7 @@
<param index="3" name="ignore_empty_terrains" type="bool" default="true" />
<description>
Update all the cells in the [param path] coordinates array so that they use the given [param terrain] for the given [param terrain_set]. The function will also connect two successive cell in the path with the same terrain. This function might update neighboring tiles if needed to create correct terrain transitions.
- If [param ignore_empty_terrains] is true, empty terrains will be ignored when trying to find the best fitting tile for the given terrain constraints.
+ If [param ignore_empty_terrains] is [code]true[/code], empty terrains will be ignored when trying to find the best fitting tile for the given terrain constraints.
[b]Note:[/b] To work correctly, this method requires the [TileMapLayer]'s TileSet to have terrains set up with all required terrain combinations. Otherwise, it may produce unexpected results.
</description>
</method>
diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml
index 132ecc3f92..d04a6f7316 100644
--- a/doc/classes/TreeItem.xml
+++ b/doc/classes/TreeItem.xml
@@ -267,7 +267,7 @@
<method name="get_next" qualifiers="const">
<return type="TreeItem" />
<description>
- Returns the next sibling TreeItem in the tree or a null object if there is none.
+ Returns the next sibling TreeItem in the tree or a [code]null[/code] object if there is none.
</description>
</method>
<method name="get_next_in_tree">
@@ -289,13 +289,13 @@
<method name="get_parent" qualifiers="const">
<return type="TreeItem" />
<description>
- Returns the parent TreeItem or a null object if there is none.
+ Returns the parent TreeItem or a [code]null[/code] object if there is none.
</description>
</method>
<method name="get_prev">
<return type="TreeItem" />
<description>
- Returns the previous sibling TreeItem in the tree or a null object if there is none.
+ Returns the previous sibling TreeItem in the tree or a [code]null[/code] object if there is none.
</description>
</method>
<method name="get_prev_in_tree">
diff --git a/doc/classes/VideoStreamPlayback.xml b/doc/classes/VideoStreamPlayback.xml
index 8b4a7f8d29..17e29ae337 100644
--- a/doc/classes/VideoStreamPlayback.xml
+++ b/doc/classes/VideoStreamPlayback.xml
@@ -54,7 +54,7 @@
<method name="_play" qualifiers="virtual">
<return type="void" />
<description>
- Called in response to [member VideoStreamPlayer.autoplay] or [method VideoStreamPlayer.play]. Note that manual playback may also invoke [method _stop] multiple times before this method is called. [method _is_playing] should return true once playing.
+ Called in response to [member VideoStreamPlayer.autoplay] or [method VideoStreamPlayer.play]. Note that manual playback may also invoke [method _stop] multiple times before this method is called. [method _is_playing] should return [code]true[/code] once playing.
</description>
</method>
<method name="_seek" qualifiers="virtual">
@@ -81,14 +81,14 @@
<method name="_stop" qualifiers="virtual">
<return type="void" />
<description>
- Stops playback. May be called multiple times before [method _play], or in response to [method VideoStreamPlayer.stop]. [method _is_playing] should return false once stopped.
+ Stops playback. May be called multiple times before [method _play], or in response to [method VideoStreamPlayer.stop]. [method _is_playing] should return [code]false[/code] once stopped.
</description>
</method>
<method name="_update" qualifiers="virtual">
<return type="void" />
<param index="0" name="delta" type="float" />
<description>
- Ticks video playback for [param delta] seconds. Called every frame as long as [method _is_paused] and [method _is_playing] return true.
+ Ticks video playback for [param delta] seconds. Called every frame as long as both [method _is_paused] and [method _is_playing] return [code]true[/code].
</description>
</method>
<method name="mix_audio">
diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml
index 333e61d03f..c4319fd360 100644
--- a/doc/classes/Viewport.xml
+++ b/doc/classes/Viewport.xml
@@ -48,7 +48,7 @@
<method name="get_camera_2d" qualifiers="const">
<return type="Camera2D" />
<description>
- Returns the currently active 2D camera. Returns null if there are no active cameras.
+ Returns the currently active 2D camera. Returns [code]null[/code] if there are no active cameras.
</description>
</method>
<method name="get_camera_3d" qualifiers="const">
@@ -144,13 +144,13 @@
<method name="gui_get_focus_owner" qualifiers="const">
<return type="Control" />
<description>
- Returns the [Control] having the focus within this viewport. If no [Control] has the focus, returns null.
+ Returns the currently focused [Control] within this viewport. If no [Control] is focused, returns [code]null[/code].
</description>
</method>
<method name="gui_get_hovered_control" qualifiers="const">
<return type="Control" />
<description>
- Returns the [Control] that the mouse is currently hovering over in this viewport. If no [Control] has the cursor, returns null.
+ Returns the [Control] that the mouse is currently hovering over in this viewport. If no [Control] has the cursor, returns [code]null[/code].
Typically the leaf [Control] node or deepest level of the subtree which claims hover. This is very useful when used together with [method Node.is_ancestor_of] to find if the mouse is within a control tree.
</description>
</method>
diff --git a/doc/classes/VisualShaderNodeCompare.xml b/doc/classes/VisualShaderNodeCompare.xml
index b566ca62f3..9f554dacb4 100644
--- a/doc/classes/VisualShaderNodeCompare.xml
+++ b/doc/classes/VisualShaderNodeCompare.xml
@@ -69,10 +69,10 @@
Represents the size of the [enum Function] enum.
</constant>
<constant name="COND_ALL" value="0" enum="Condition">
- The result will be true if all of component in vector satisfy the comparison condition.
+ The result will be [code]true[/code] if all components in the vector satisfy the comparison condition.
</constant>
<constant name="COND_ANY" value="1" enum="Condition">
- The result will be true if any of component in vector satisfy the comparison condition.
+ The result will be [code]true[/code] if any component in the vector satisfies the comparison condition.
</constant>
<constant name="COND_MAX" value="2" enum="Condition">
Represents the size of the [enum Condition] enum.
diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp
index ab270e5e82..0cc89dfaca 100644
--- a/drivers/d3d12/rendering_device_driver_d3d12.cpp
+++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp
@@ -6177,6 +6177,8 @@ uint64_t RenderingDeviceDriverD3D12::api_trait_get(ApiTrait p_trait) {
return false;
case API_TRAIT_USE_GENERAL_IN_COPY_QUEUES:
return true;
+ case API_TRAIT_BUFFERS_REQUIRE_TRANSITIONS:
+ return !barrier_capabilities.enhanced_barriers_supported;
default:
return RenderingDeviceDriver::api_trait_get(p_trait);
}
diff --git a/drivers/gles3/effects/feed_effects.cpp b/drivers/gles3/effects/feed_effects.cpp
new file mode 100644
index 0000000000..8ca88da662
--- /dev/null
+++ b/drivers/gles3/effects/feed_effects.cpp
@@ -0,0 +1,128 @@
+/**************************************************************************/
+/* feed_effects.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. */
+/**************************************************************************/
+
+#ifdef GLES3_ENABLED
+
+#include "feed_effects.h"
+
+#ifdef ANDROID_ENABLED
+#include <GLES3/gl3ext.h>
+#endif
+
+#define GL_PROGRAM_POINT_SIZE 0x8642
+
+using namespace GLES3;
+
+FeedEffects *FeedEffects::singleton = nullptr;
+
+FeedEffects *FeedEffects::get_singleton() {
+ return singleton;
+}
+
+FeedEffects::FeedEffects() {
+ singleton = this;
+
+ feed.shader.initialize();
+ feed.shader_version = feed.shader.version_create();
+ feed.shader.version_bind_shader(feed.shader_version, FeedShaderGLES3::MODE_DEFAULT);
+
+ { // Screen Triangle.
+ glGenBuffers(1, &screen_triangle);
+ glBindBuffer(GL_ARRAY_BUFFER, screen_triangle);
+
+ const float qv[6] = {
+ -1.0f,
+ -1.0f,
+ 3.0f,
+ -1.0f,
+ -1.0f,
+ 3.0f,
+ };
+
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6, qv, GL_STATIC_DRAW);
+ glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind
+
+ glGenVertexArrays(1, &screen_triangle_array);
+ glBindVertexArray(screen_triangle_array);
+ glBindBuffer(GL_ARRAY_BUFFER, screen_triangle);
+ glVertexAttribPointer(RS::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, nullptr);
+ glEnableVertexAttribArray(RS::ARRAY_VERTEX);
+ glBindVertexArray(0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind
+ }
+}
+
+FeedEffects::~FeedEffects() {
+ singleton = nullptr;
+ glDeleteBuffers(1, &screen_triangle);
+ glDeleteVertexArrays(1, &screen_triangle_array);
+ feed.shader.version_free(feed.shader_version);
+}
+
+Transform3D transform3D_from_mat4(const float *p_mat4) {
+ Transform3D res;
+
+ res.basis.rows[0][0] = p_mat4[0];
+ res.basis.rows[1][0] = p_mat4[1];
+ res.basis.rows[2][0] = p_mat4[2];
+ // p_mat4[3] = 0;
+ res.basis.rows[0][1] = p_mat4[4];
+ res.basis.rows[1][1] = p_mat4[5];
+ res.basis.rows[2][1] = p_mat4[6];
+ // p_mat4[7] = 0;
+ res.basis.rows[0][2] = p_mat4[8];
+ res.basis.rows[1][2] = p_mat4[9];
+ res.basis.rows[2][2] = p_mat4[10];
+ // p_mat4[11] = 0;
+ res.origin.x = p_mat4[12];
+ res.origin.y = p_mat4[13];
+ res.origin.z = p_mat4[14];
+ // p_mat4[15] = 1;
+
+ return res;
+}
+
+void FeedEffects::draw() {
+ bool success = feed.shader.version_bind_shader(feed.shader_version, FeedShaderGLES3::MODE_DEFAULT, FeedShaderGLES3::USE_EXTERNAL_SAMPLER);
+ if (!success) {
+ OS::get_singleton()->print("Godot : FeedShaderGLES3 Could not bind version_bind_shader USE_EXTERNAL_SAMPLER");
+ return;
+ }
+
+ draw_screen_triangle();
+}
+
+void FeedEffects::draw_screen_triangle() {
+ glBindVertexArray(screen_triangle_array);
+ glDrawArrays(GL_TRIANGLES, 0, 3);
+ glBindVertexArray(0);
+}
+
+#endif // GLES3_ENABLED
diff --git a/drivers/gles3/effects/feed_effects.h b/drivers/gles3/effects/feed_effects.h
new file mode 100644
index 0000000000..5856a3e04b
--- /dev/null
+++ b/drivers/gles3/effects/feed_effects.h
@@ -0,0 +1,68 @@
+/**************************************************************************/
+/* feed_effects.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 FEED_EFFECTS_GLES3_H
+#define FEED_EFFECTS_GLES3_H
+
+#ifdef GLES3_ENABLED
+
+#include "drivers/gles3/shaders/feed.glsl.gen.h"
+
+namespace GLES3 {
+
+class FeedEffects {
+private:
+ struct Feed {
+ FeedShaderGLES3 shader;
+ RID shader_version;
+ } feed;
+
+ static FeedEffects *singleton;
+
+ GLuint screen_triangle = 0;
+ GLuint screen_triangle_array = 0;
+
+public:
+ static FeedEffects *get_singleton();
+
+ FeedEffects();
+ ~FeedEffects();
+
+ void draw();
+
+private:
+ void draw_screen_triangle();
+};
+
+} // namespace GLES3
+
+#endif // GLES3_ENABLED
+
+#endif // FEED_EFFECTS_GLES3_H
diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp
index 3c959f0143..0138f99d50 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.cpp
+++ b/drivers/gles3/rasterizer_canvas_gles3.cpp
@@ -1654,28 +1654,39 @@ void RasterizerCanvasGLES3::light_update_shadow(RID p_rid, int p_shadow_index, c
return;
}
- for (int i = 0; i < 4; i++) {
- glViewport((state.shadow_texture_size / 4) * i, p_shadow_index * 2, (state.shadow_texture_size / 4), 2);
+ Projection projection;
+ {
+ real_t fov = 90;
+ real_t nearp = p_near;
+ real_t farp = p_far;
+ real_t aspect = 1.0;
- Projection projection;
- {
- real_t fov = 90;
- real_t nearp = p_near;
- real_t farp = p_far;
- real_t aspect = 1.0;
+ real_t ymax = nearp * Math::tan(Math::deg_to_rad(fov * 0.5));
+ real_t ymin = -ymax;
+ real_t xmin = ymin * aspect;
+ real_t xmax = ymax * aspect;
- real_t ymax = nearp * Math::tan(Math::deg_to_rad(fov * 0.5));
- real_t ymin = -ymax;
- real_t xmin = ymin * aspect;
- real_t xmax = ymax * aspect;
+ projection.set_frustum(xmin, xmax, ymin, ymax, nearp, farp);
+ }
- projection.set_frustum(xmin, xmax, ymin, ymax, nearp, farp);
- }
+ // Precomputed:
+ // Vector3 cam_target = Basis::from_euler(Vector3(0, 0, Math_TAU * ((i + 3) / 4.0))).xform(Vector3(0, 1, 0));
+ // projection = projection * Projection(Transform3D().looking_at(cam_targets[i], Vector3(0, 0, -1)).affine_inverse());
+ const Projection projections[4] = {
+ projection * Projection(Vector4(0, 0, -1, 0), Vector4(1, 0, 0, 0), Vector4(0, -1, 0, 0), Vector4(0, 0, 0, 1)),
- Vector3 cam_target = Basis::from_euler(Vector3(0, 0, Math_TAU * ((i + 3) / 4.0))).xform(Vector3(0, 1, 0));
+ projection * Projection(Vector4(-1, 0, 0, 0), Vector4(0, 0, -1, 0), Vector4(0, -1, 0, 0), Vector4(0, 0, 0, 1)),
+
+ projection * Projection(Vector4(0, 0, 1, 0), Vector4(-1, 0, 0, 0), Vector4(0, -1, 0, 0), Vector4(0, 0, 0, 1)),
+
+ projection * Projection(Vector4(1, 0, 0, 0), Vector4(0, 0, 1, 0), Vector4(0, -1, 0, 0), Vector4(0, 0, 0, 1))
+
+ };
+
+ for (int i = 0; i < 4; i++) {
+ glViewport((state.shadow_texture_size / 4) * i, p_shadow_index * 2, (state.shadow_texture_size / 4), 2);
- projection = projection * Projection(Transform3D().looking_at(cam_target, Vector3(0, 0, -1)).affine_inverse());
- shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::PROJECTION, projection, shadow_render.shader_version, variant);
+ shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::PROJECTION, projections[i], shadow_render.shader_version, variant);
static const Vector2 directions[4] = { Vector2(1, 0), Vector2(0, 1), Vector2(-1, 0), Vector2(0, -1) };
shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::DIRECTION, directions[i].x, directions[i].y, shadow_render.shader_version, variant);
diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp
index 843b6eac05..0fda42979f 100644
--- a/drivers/gles3/rasterizer_gles3.cpp
+++ b/drivers/gles3/rasterizer_gles3.cpp
@@ -218,6 +218,7 @@ void RasterizerGLES3::finalize() {
memdelete(glow);
memdelete(cubemap_filter);
memdelete(copy_effects);
+ memdelete(feed_effects);
memdelete(light_storage);
memdelete(particles_storage);
memdelete(mesh_storage);
@@ -366,6 +367,7 @@ RasterizerGLES3::RasterizerGLES3() {
cubemap_filter = memnew(GLES3::CubemapFilter);
glow = memnew(GLES3::Glow);
post_effects = memnew(GLES3::PostEffects);
+ feed_effects = memnew(GLES3::FeedEffects);
gi = memnew(GLES3::GI);
fog = memnew(GLES3::Fog);
canvas = memnew(RasterizerCanvasGLES3());
diff --git a/drivers/gles3/rasterizer_gles3.h b/drivers/gles3/rasterizer_gles3.h
index 6765d8b4d5..abda2a5e06 100644
--- a/drivers/gles3/rasterizer_gles3.h
+++ b/drivers/gles3/rasterizer_gles3.h
@@ -35,6 +35,7 @@
#include "effects/copy_effects.h"
#include "effects/cubemap_filter.h"
+#include "effects/feed_effects.h"
#include "effects/glow.h"
#include "effects/post_effects.h"
#include "environment/fog.h"
@@ -78,6 +79,7 @@ protected:
GLES3::CubemapFilter *cubemap_filter = nullptr;
GLES3::Glow *glow = nullptr;
GLES3::PostEffects *post_effects = nullptr;
+ GLES3::FeedEffects *feed_effects = nullptr;
RasterizerCanvasGLES3 *canvas = nullptr;
RasterizerSceneGLES3 *scene = nullptr;
static RasterizerGLES3 *singleton;
diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp
index a73f14c796..537076d4ed 100644
--- a/drivers/gles3/rasterizer_scene_gles3.cpp
+++ b/drivers/gles3/rasterizer_scene_gles3.cpp
@@ -31,6 +31,7 @@
#include "rasterizer_scene_gles3.h"
#include "drivers/gles3/effects/copy_effects.h"
+#include "drivers/gles3/effects/feed_effects.h"
#include "rasterizer_gles3.h"
#include "storage/config.h"
#include "storage/mesh_storage.h"
@@ -39,6 +40,8 @@
#include "core/config/project_settings.h"
#include "core/templates/sort_array.h"
+#include "servers/camera/camera_feed.h"
+#include "servers/camera_server.h"
#include "servers/rendering/rendering_server_default.h"
#include "servers/rendering/rendering_server_globals.h"
@@ -2259,6 +2262,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
render_data.inv_cam_transform = render_data.cam_transform.affine_inverse();
render_data.cam_projection = p_camera_data->main_projection;
render_data.cam_orthogonal = p_camera_data->is_orthogonal;
+ render_data.cam_frustum = p_camera_data->is_frustum;
render_data.camera_visible_layers = p_camera_data->visible_layers;
render_data.main_cam_transform = p_camera_data->main_transform;
@@ -2382,7 +2386,9 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
bool draw_sky = false;
bool draw_sky_fog_only = false;
bool keep_color = false;
+ bool draw_feed = false;
float sky_energy_multiplier = 1.0;
+ int camera_feed_id = -1;
if (unlikely(get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_OVERDRAW)) {
clear_color = Color(0, 0, 0, 1); //in overdraw mode, BG should always be black
@@ -2427,6 +2433,8 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
keep_color = true;
} break;
case RS::ENV_BG_CAMERA_FEED: {
+ camera_feed_id = environment_get_camera_feed_id(render_data.environment);
+ draw_feed = true;
} break;
default: {
}
@@ -2538,7 +2546,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
glClear(GL_DEPTH_BUFFER_BIT);
}
- if (!keep_color) {
+ if (!keep_color && !draw_feed) {
clear_color.a = render_data.transparent_bg ? 0.0f : 1.0f;
glClearBufferfv(GL_COLOR, 0, clear_color.components);
} else if (fbo != rt->fbo) {
@@ -2578,6 +2586,29 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
spec_constant_base_flags |= SceneShaderGLES3::APPLY_TONEMAPPING;
}
}
+
+ if (draw_feed && camera_feed_id > -1) {
+ RENDER_TIMESTAMP("Render Camera feed");
+
+ scene_state.enable_gl_depth_draw(false);
+ scene_state.enable_gl_depth_test(false);
+ scene_state.enable_gl_blend(false);
+ scene_state.set_gl_cull_mode(GLES3::SceneShaderData::CULL_BACK);
+
+ Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id);
+
+ if (feed.is_valid()) {
+ RID camera_YCBCR = feed->get_texture(CameraServer::FEED_YCBCR_IMAGE);
+ GLES3::TextureStorage::get_singleton()->texture_bind(camera_YCBCR, 0);
+
+ GLES3::FeedEffects *feed_effects = GLES3::FeedEffects::get_singleton();
+ feed_effects->draw();
+ }
+ scene_state.enable_gl_depth_draw(true);
+ scene_state.enable_gl_depth_test(true);
+ scene_state.enable_gl_blend(true);
+ }
+
// Render Opaque Objects.
RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, spec_constant_base_flags, use_wireframe);
@@ -2592,14 +2623,18 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
scene_state.enable_gl_blend(false);
scene_state.set_gl_cull_mode(GLES3::SceneShaderData::CULL_BACK);
+ Transform3D transform = render_data.cam_transform;
Projection projection = render_data.cam_projection;
if (is_reflection_probe) {
Projection correction;
correction.columns[1][1] = -1.0;
projection = correction * render_data.cam_projection;
+ } else if (render_data.cam_frustum) {
+ // Sky is drawn upside down, the frustum offset doesn't know the image is upside down so needs a flip.
+ projection[2].y = -projection[2].y;
}
- _draw_sky(render_data.environment, projection, render_data.cam_transform, sky_energy_multiplier, render_data.luminance_multiplier, p_camera_data->view_count > 1, flip_y, apply_color_adjustments_in_post);
+ _draw_sky(render_data.environment, projection, transform, sky_energy_multiplier, render_data.luminance_multiplier, p_camera_data->view_count > 1, flip_y, apply_color_adjustments_in_post);
}
if (rt && (scene_state.used_screen_texture || scene_state.used_depth_texture)) {
diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h
index 06371b2b7f..4f088a0e7d 100644
--- a/drivers/gles3/rasterizer_scene_gles3.h
+++ b/drivers/gles3/rasterizer_scene_gles3.h
@@ -103,6 +103,7 @@ struct RenderDataGLES3 {
Transform3D inv_cam_transform;
Projection cam_projection;
bool cam_orthogonal = false;
+ bool cam_frustum = false;
uint32_t camera_visible_layers = 0xFFFFFFFF;
// For billboards to cast correct shadows.
diff --git a/drivers/gles3/shaders/SCsub b/drivers/gles3/shaders/SCsub
index df2c515035..0207ba12b7 100644
--- a/drivers/gles3/shaders/SCsub
+++ b/drivers/gles3/shaders/SCsub
@@ -17,6 +17,7 @@ if "GLES3_GLSL" in env["BUILDERS"]:
# as we have a few, not yet, converted files we name the ones we want to include:
env.GLES3_GLSL("canvas.glsl")
+ env.GLES3_GLSL("feed.glsl")
env.GLES3_GLSL("scene.glsl")
env.GLES3_GLSL("sky.glsl")
env.GLES3_GLSL("canvas_occlusion.glsl")
diff --git a/drivers/gles3/shaders/feed.glsl b/drivers/gles3/shaders/feed.glsl
new file mode 100644
index 0000000000..9d89fc699d
--- /dev/null
+++ b/drivers/gles3/shaders/feed.glsl
@@ -0,0 +1,39 @@
+/* clang-format off */
+#[modes]
+
+mode_default =
+
+#[specializations]
+
+USE_EXTERNAL_SAMPLER = false
+
+#[vertex]
+
+layout(location = 0) in vec2 vertex_attrib;
+
+out vec2 uv_interp;
+
+
+void main() {
+ uv_interp = vertex_attrib * 0.5 + 0.5;
+ gl_Position = vec4(vertex_attrib, 1.0, 1.0);
+}
+
+/* clang-format off */
+#[fragment]
+
+layout(location = 0) out vec4 frag_color;
+in vec2 uv_interp;
+
+/* clang-format on */
+#ifdef USE_EXTERNAL_SAMPLER
+uniform samplerExternalOES sourceFeed; // texunit:0
+#else
+uniform sampler2D sourceFeed; // texunit:0
+#endif
+
+void main() {
+ vec4 color = texture(sourceFeed, uv_interp);
+
+ frag_color = color;
+}
diff --git a/drivers/gles3/storage/particles_storage.h b/drivers/gles3/storage/particles_storage.h
index 086f5f7936..0552a5324f 100644
--- a/drivers/gles3/storage/particles_storage.h
+++ b/drivers/gles3/storage/particles_storage.h
@@ -396,7 +396,7 @@ public:
_FORCE_INLINE_ bool particles_has_collision(RID p_particles) {
Particles *particles = particles_owner.get_or_null(p_particles);
- ERR_FAIL_NULL_V(particles, 0);
+ ERR_FAIL_NULL_V(particles, false);
return particles->has_collision_cache;
}
diff --git a/drivers/png/SCsub b/drivers/png/SCsub
index fce37257b1..4268a66475 100644
--- a/drivers/png/SCsub
+++ b/drivers/png/SCsub
@@ -46,18 +46,18 @@ if env["builtin_libpng"]:
if "S_compiler" in env:
env_neon["CC"] = env["S_compiler"]
neon_sources = []
- neon_sources.append(env_neon.Object(thirdparty_dir + "/arm/arm_init.c"))
- neon_sources.append(env_neon.Object(thirdparty_dir + "/arm/filter_neon_intrinsics.c"))
- neon_sources.append(env_neon.Object(thirdparty_dir + "/arm/filter_neon.S"))
- neon_sources.append(env_neon.Object(thirdparty_dir + "/arm/palette_neon_intrinsics.c"))
+ neon_sources.append(env_neon.Object(thirdparty_dir + "arm/arm_init.c"))
+ neon_sources.append(env_neon.Object(thirdparty_dir + "arm/filter_neon_intrinsics.c"))
+ neon_sources.append(env_neon.Object(thirdparty_dir + "arm/filter_neon.S"))
+ neon_sources.append(env_neon.Object(thirdparty_dir + "arm/palette_neon_intrinsics.c"))
thirdparty_obj += neon_sources
elif env["arch"].startswith("x86"):
env_thirdparty.Append(CPPDEFINES=["PNG_INTEL_SSE"])
- env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/intel/intel_init.c")
- env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/intel/filter_sse2_intrinsics.c")
+ env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "intel/intel_init.c")
+ env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "intel/filter_sse2_intrinsics.c")
elif env["arch"] == "ppc64":
- env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/powerpc/powerpc_init.c")
- env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/powerpc/filter_vsx_intrinsics.c")
+ env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "powerpc/powerpc_init.c")
+ env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "powerpc/filter_vsx_intrinsics.c")
env.drivers_sources += thirdparty_obj
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp
index f9f1168a97..f50771ddde 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp
@@ -1519,7 +1519,7 @@ RDD::BufferID RenderingDeviceDriverVulkan::buffer_create(uint64_t p_size, BitFie
ERR_FAIL_COND_V_MSG(err, BufferID(), "Can't create buffer of size: " + itos(p_size) + ", error " + itos(err) + ".");
err = vmaAllocateMemoryForBuffer(allocator, vk_buffer, &alloc_create_info, &allocation, &alloc_info);
ERR_FAIL_COND_V_MSG(err, BufferID(), "Can't allocate memory for buffer of size: " + itos(p_size) + ", error " + itos(err) + ".");
- err = vmaBindBufferMemory2(allocator, allocation, 0, vk_buffer, NULL);
+ err = vmaBindBufferMemory2(allocator, allocation, 0, vk_buffer, nullptr);
ERR_FAIL_COND_V_MSG(err, BufferID(), "Can't bind memory to buffer of size: " + itos(p_size) + ", error " + itos(err) + ".");
// Bookkeep.
@@ -1745,7 +1745,7 @@ RDD::TextureID RenderingDeviceDriverVulkan::texture_create(const TextureFormat &
ERR_FAIL_COND_V_MSG(err, TextureID(), "vkCreateImage failed with error " + itos(err) + ".");
err = vmaAllocateMemoryForImage(allocator, vk_image, &alloc_create_info, &allocation, &alloc_info);
ERR_FAIL_COND_V_MSG(err, TextureID(), "Can't allocate memory for image, error: " + itos(err) + ".");
- err = vmaBindImageMemory2(allocator, allocation, 0, vk_image, NULL);
+ err = vmaBindImageMemory2(allocator, allocation, 0, vk_image, nullptr);
ERR_FAIL_COND_V_MSG(err, TextureID(), "Can't bind memory to image, error: " + itos(err) + ".");
// Create view.
@@ -2887,21 +2887,28 @@ Error RenderingDeviceDriverVulkan::swap_chain_resize(CommandQueueID p_cmd_queue,
// No swapchain yet, this is the first time we're creating it.
if (!swap_chain->vk_swapchain) {
- uint32_t width = surface_capabilities.currentExtent.width;
- uint32_t height = surface_capabilities.currentExtent.height;
+ if (surface_capabilities.currentExtent.width == 0xFFFFFFFF) {
+ // The current extent is currently undefined, so the current surface width and height will be clamped to the surface's capabilities.
+ // We make sure to overwrite surface_capabilities.currentExtent.width so that the same check further below
+ // does not set extent.width = CLAMP( surface->width, ... ) on the first run of this function, because
+ // that'd be potentially unswapped.
+ surface_capabilities.currentExtent.width = CLAMP(surface->width, surface_capabilities.minImageExtent.width, surface_capabilities.maxImageExtent.width);
+ surface_capabilities.currentExtent.height = CLAMP(surface->height, surface_capabilities.minImageExtent.height, surface_capabilities.maxImageExtent.height);
+ }
+
+ // We must SWAP() only once otherwise we'll keep ping-ponging between
+ // the right and wrong resolutions after multiple calls to swap_chain_resize().
if (surface_capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||
surface_capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
// Swap to get identity width and height.
- surface_capabilities.currentExtent.height = width;
- surface_capabilities.currentExtent.width = height;
+ SWAP(surface_capabilities.currentExtent.width, surface_capabilities.currentExtent.height);
}
-
- native_display_size = surface_capabilities.currentExtent;
}
VkExtent2D extent;
if (surface_capabilities.currentExtent.width == 0xFFFFFFFF) {
// The current extent is currently undefined, so the current surface width and height will be clamped to the surface's capabilities.
+ // We can only be here on the second call to swap_chain_resize(), by which time surface->width & surface->height should already be swapped if needed.
extent.width = CLAMP(surface->width, surface_capabilities.minImageExtent.width, surface_capabilities.maxImageExtent.width);
extent.height = CLAMP(surface->height, surface_capabilities.minImageExtent.height, surface_capabilities.maxImageExtent.height);
} else {
@@ -2991,11 +2998,29 @@ Error RenderingDeviceDriverVulkan::swap_chain_resize(CommandQueueID p_cmd_queue,
swap_create_info.minImageCount = desired_swapchain_images;
swap_create_info.imageFormat = swap_chain->format;
swap_create_info.imageColorSpace = swap_chain->color_space;
- swap_create_info.imageExtent = native_display_size;
+ swap_create_info.imageExtent = extent;
swap_create_info.imageArrayLayers = 1;
swap_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
swap_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swap_create_info.preTransform = surface_transform_bits;
+ switch (swap_create_info.preTransform) {
+ case VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR:
+ swap_chain->pre_transform_rotation_degrees = 0;
+ break;
+ case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
+ swap_chain->pre_transform_rotation_degrees = 90;
+ break;
+ case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
+ swap_chain->pre_transform_rotation_degrees = 180;
+ break;
+ case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
+ swap_chain->pre_transform_rotation_degrees = 270;
+ break;
+ default:
+ WARN_PRINT("Unexpected swap_create_info.preTransform = " + itos(swap_create_info.preTransform) + ".");
+ swap_chain->pre_transform_rotation_degrees = 0;
+ break;
+ }
swap_create_info.compositeAlpha = composite_alpha;
swap_create_info.presentMode = present_mode;
swap_create_info.clipped = true;
@@ -3167,6 +3192,13 @@ RDD::RenderPassID RenderingDeviceDriverVulkan::swap_chain_get_render_pass(SwapCh
return swap_chain->render_pass;
}
+int RenderingDeviceDriverVulkan::swap_chain_get_pre_rotation_degrees(SwapChainID p_swap_chain) {
+ DEV_ASSERT(p_swap_chain.id != 0);
+
+ SwapChain *swap_chain = (SwapChain *)(p_swap_chain.id);
+ return swap_chain->pre_transform_rotation_degrees;
+}
+
RDD::DataFormat RenderingDeviceDriverVulkan::swap_chain_get_format(SwapChainID p_swap_chain) {
DEV_ASSERT(p_swap_chain.id != 0);
@@ -4017,7 +4049,7 @@ RDD::UniformSetID RenderingDeviceDriverVulkan::uniform_set_create(VectorView<Bou
}
// Need a descriptor pool.
- DescriptorSetPools::Iterator pool_sets_it = {};
+ DescriptorSetPools::Iterator pool_sets_it;
VkDescriptorPool vk_pool = _descriptor_set_pool_find_or_create(pool_key, &pool_sets_it);
DEV_ASSERT(vk_pool);
pool_sets_it->value[vk_pool]++;
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.h b/drivers/vulkan/rendering_device_driver_vulkan.h
index 33cce30b34..6931015a22 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.h
+++ b/drivers/vulkan/rendering_device_driver_vulkan.h
@@ -359,6 +359,7 @@ private:
LocalVector<CommandQueue *> command_queues_acquired;
LocalVector<uint32_t> command_queues_acquired_semaphores;
RenderPassID render_pass;
+ int pre_transform_rotation_degrees = 0;
uint32_t image_index = 0;
#ifdef ANDROID_ENABLED
uint64_t refresh_duration = 0;
@@ -366,13 +367,13 @@ private:
};
void _swap_chain_release(SwapChain *p_swap_chain);
- VkExtent2D native_display_size;
public:
virtual SwapChainID swap_chain_create(RenderingContextDriver::SurfaceID p_surface) override final;
virtual Error swap_chain_resize(CommandQueueID p_cmd_queue, SwapChainID p_swap_chain, uint32_t p_desired_framebuffer_count) override final;
virtual FramebufferID swap_chain_acquire_framebuffer(CommandQueueID p_cmd_queue, SwapChainID p_swap_chain, bool &r_resize_required) override final;
virtual RenderPassID swap_chain_get_render_pass(SwapChainID p_swap_chain) override final;
+ virtual int swap_chain_get_pre_rotation_degrees(SwapChainID p_swap_chain) override final;
virtual DataFormat swap_chain_get_format(SwapChainID p_swap_chain) override final;
virtual void swap_chain_set_max_fps(SwapChainID p_swap_chain, int p_max_fps) override final;
virtual void swap_chain_free(SwapChainID p_swap_chain) override final;
@@ -487,7 +488,7 @@ private:
struct UniformSetInfo {
VkDescriptorSet vk_descriptor_set = VK_NULL_HANDLE;
VkDescriptorPool vk_descriptor_pool = VK_NULL_HANDLE;
- DescriptorSetPools::Iterator pool_sets_it = {};
+ DescriptorSetPools::Iterator pool_sets_it;
};
public:
diff --git a/drivers/windows/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp
index f7632842ed..24a26b56ef 100644
--- a/drivers/windows/dir_access_windows.cpp
+++ b/drivers/windows/dir_access_windows.cpp
@@ -282,7 +282,7 @@ Error DirAccessWindows::rename(String p_path, String p_new_path) {
uint64_t id = OS::get_singleton()->get_ticks_usec();
while (true) {
tmpfile_utf16 = (path + itos(id++) + ".tmp").utf16();
- HANDLE handle = CreateFileW((LPCWSTR)tmpfile_utf16.get_data(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
+ HANDLE handle = CreateFileW((LPCWSTR)tmpfile_utf16.get_data(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
if (handle != INVALID_HANDLE_VALUE) {
CloseHandle(handle);
break;
diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp
index a8a2ea6b5e..5c06295f14 100644
--- a/drivers/windows/file_access_windows.cpp
+++ b/drivers/windows/file_access_windows.cpp
@@ -193,7 +193,7 @@ Error FileAccessWindows::open_internal(const String &p_path, int p_mode_flags) {
uint64_t id = OS::get_singleton()->get_ticks_usec();
while (true) {
tmpfile = path + itos(id++) + ".tmp";
- HANDLE handle = CreateFileW((LPCWSTR)tmpfile.utf16().get_data(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
+ HANDLE handle = CreateFileW((LPCWSTR)tmpfile.utf16().get_data(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
if (handle != INVALID_HANDLE_VALUE) {
CloseHandle(handle);
break;
diff --git a/drivers/windows/file_access_windows_pipe.cpp b/drivers/windows/file_access_windows_pipe.cpp
index 9bf0f4d852..7348b29a92 100644
--- a/drivers/windows/file_access_windows_pipe.cpp
+++ b/drivers/windows/file_access_windows_pipe.cpp
@@ -40,7 +40,7 @@ Error FileAccessWindowsPipe::open_existing(HANDLE p_rfd, HANDLE p_wfd, bool p_bl
_close();
path_src = String();
- ERR_FAIL_COND_V_MSG(fd[0] != 0 || fd[1] != 0, ERR_ALREADY_IN_USE, "Pipe is already in use.");
+ ERR_FAIL_COND_V_MSG(fd[0] != nullptr || fd[1] != nullptr, ERR_ALREADY_IN_USE, "Pipe is already in use.");
fd[0] = p_rfd;
fd[1] = p_wfd;
@@ -58,18 +58,18 @@ Error FileAccessWindowsPipe::open_internal(const String &p_path, int p_mode_flag
_close();
path_src = p_path;
- ERR_FAIL_COND_V_MSG(fd[0] != 0 || fd[1] != 0, ERR_ALREADY_IN_USE, "Pipe is already in use.");
+ ERR_FAIL_COND_V_MSG(fd[0] != nullptr || fd[1] != nullptr, ERR_ALREADY_IN_USE, "Pipe is already in use.");
path = String("\\\\.\\pipe\\LOCAL\\") + p_path.replace("pipe://", "").replace("/", "_");
- HANDLE h = CreateFileW((LPCWSTR)path.utf16().get_data(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ HANDLE h = CreateFileW((LPCWSTR)path.utf16().get_data(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (h == INVALID_HANDLE_VALUE) {
h = CreateNamedPipeW((LPCWSTR)path.utf16().get_data(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_NOWAIT, 1, 4096, 4096, 0, nullptr);
if (h == INVALID_HANDLE_VALUE) {
last_error = ERR_FILE_CANT_OPEN;
return last_error;
}
- ConnectNamedPipe(h, NULL);
+ ConnectNamedPipe(h, nullptr);
}
fd[0] = h;
fd[1] = h;
@@ -79,19 +79,19 @@ Error FileAccessWindowsPipe::open_internal(const String &p_path, int p_mode_flag
}
void FileAccessWindowsPipe::_close() {
- if (fd[0] == 0) {
+ if (fd[0] == nullptr) {
return;
}
if (fd[1] != fd[0]) {
CloseHandle(fd[1]);
}
CloseHandle(fd[0]);
- fd[0] = 0;
- fd[1] = 0;
+ fd[0] = nullptr;
+ fd[1] = nullptr;
}
bool FileAccessWindowsPipe::is_open() const {
- return (fd[0] != 0 || fd[1] != 0);
+ return (fd[0] != nullptr || fd[1] != nullptr);
}
String FileAccessWindowsPipe::get_path() const {
@@ -103,7 +103,7 @@ String FileAccessWindowsPipe::get_path_absolute() const {
}
uint64_t FileAccessWindowsPipe::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
- ERR_FAIL_COND_V_MSG(fd[0] == 0, -1, "Pipe must be opened before use.");
+ ERR_FAIL_COND_V_MSG(fd[0] == nullptr, -1, "Pipe must be opened before use.");
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
DWORD read = 0;
@@ -120,7 +120,7 @@ Error FileAccessWindowsPipe::get_error() const {
}
void FileAccessWindowsPipe::store_buffer(const uint8_t *p_src, uint64_t p_length) {
- ERR_FAIL_COND_MSG(fd[1] == 0, "Pipe must be opened before use.");
+ ERR_FAIL_COND_MSG(fd[1] == nullptr, "Pipe must be opened before use.");
ERR_FAIL_COND(!p_src && p_length > 0);
DWORD read = -1;
diff --git a/drivers/windows/file_access_windows_pipe.h b/drivers/windows/file_access_windows_pipe.h
index 1eb3c6ef2f..5edf0500a5 100644
--- a/drivers/windows/file_access_windows_pipe.h
+++ b/drivers/windows/file_access_windows_pipe.h
@@ -39,7 +39,7 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
class FileAccessWindowsPipe : public FileAccess {
- HANDLE fd[2] = { 0, 0 };
+ HANDLE fd[2] = { nullptr, nullptr };
mutable Error last_error = OK;
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 77d21cb7b0..c210ad0761 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -6337,8 +6337,9 @@ bool AnimationTrackEditor::_is_track_compatible(int p_target_track_idx, Variant:
}
if (path_valid) {
- if (is_source_bezier)
+ if (is_source_bezier) {
p_source_value_type = Variant::FLOAT;
+ }
return property_type == p_source_value_type;
} else {
if (animation->track_get_key_count(p_target_track_idx) > 0) {
diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp
index b78aad1721..c97272a571 100644
--- a/editor/debugger/script_editor_debugger.cpp
+++ b/editor/debugger/script_editor_debugger.cpp
@@ -1170,6 +1170,12 @@ String ScriptEditorDebugger::get_var_value(const String &p_var) const {
return inspector->get_stack_variable(p_var);
}
+void ScriptEditorDebugger::_resources_reimported(const PackedStringArray &p_resources) {
+ Array msg;
+ msg.push_back(p_resources);
+ _put_msg("scene:reload_cached_files", msg);
+}
+
int ScriptEditorDebugger::_get_node_path_cache(const NodePath &p_path) {
const int *r = node_path_cache.getptr(p_path);
if (r) {
@@ -1818,6 +1824,7 @@ ScriptEditorDebugger::ScriptEditorDebugger() {
tabs->connect("tab_changed", callable_mp(this, &ScriptEditorDebugger::_tab_changed));
InspectorDock::get_inspector_singleton()->connect("object_id_selected", callable_mp(this, &ScriptEditorDebugger::_remote_object_selected));
+ EditorFileSystem::get_singleton()->connect("resources_reimported", callable_mp(this, &ScriptEditorDebugger::_resources_reimported));
{ //debugger
VBoxContainer *vbc = memnew(VBoxContainer);
diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h
index 1908b1e5a7..06a968e141 100644
--- a/editor/debugger/script_editor_debugger.h
+++ b/editor/debugger/script_editor_debugger.h
@@ -198,6 +198,8 @@ private:
void _video_mem_request();
void _video_mem_export();
+ void _resources_reimported(const PackedStringArray &p_resources);
+
int _get_node_path_cache(const NodePath &p_path);
int _get_res_path_cache(const String &p_path);
diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp
index 084ecf7229..0649272216 100644
--- a/editor/editor_audio_buses.cpp
+++ b/editor/editor_audio_buses.cpp
@@ -91,17 +91,26 @@ void EditorAudioBus::_notification(int p_what) {
Color mute_color = EditorThemeManager::is_dark_theme() ? Color(1.0, 0.16, 0.16) : Color(2.35, 1.03, 1.03);
Color bypass_color = EditorThemeManager::is_dark_theme() ? Color(0.13, 0.8, 1.0) : Color(1.03, 2.04, 2.35);
float darkening_factor = EditorThemeManager::is_dark_theme() ? 0.15 : 0.65;
+ Color solo_color_darkened = solo_color.darkened(darkening_factor);
+ Color mute_color_darkened = mute_color.darkened(darkening_factor);
+ Color bypass_color_darkened = bypass_color.darkened(darkening_factor);
- Ref<StyleBoxFlat>(solo->get_theme_stylebox(SceneStringName(pressed)))->set_border_color(solo_color.darkened(darkening_factor));
- Ref<StyleBoxFlat>(mute->get_theme_stylebox(SceneStringName(pressed)))->set_border_color(mute_color.darkened(darkening_factor));
- Ref<StyleBoxFlat>(bypass->get_theme_stylebox(SceneStringName(pressed)))->set_border_color(bypass_color.darkened(darkening_factor));
+ Ref<StyleBoxFlat>(solo->get_theme_stylebox(SceneStringName(pressed)))->set_border_color(solo_color_darkened);
+ Ref<StyleBoxFlat>(mute->get_theme_stylebox(SceneStringName(pressed)))->set_border_color(mute_color_darkened);
+ Ref<StyleBoxFlat>(bypass->get_theme_stylebox(SceneStringName(pressed)))->set_border_color(bypass_color_darkened);
+ Ref<StyleBoxFlat>(solo->get_theme_stylebox("hover_pressed"))->set_border_color(solo_color_darkened);
+ Ref<StyleBoxFlat>(mute->get_theme_stylebox("hover_pressed"))->set_border_color(mute_color_darkened);
+ Ref<StyleBoxFlat>(bypass->get_theme_stylebox("hover_pressed"))->set_border_color(bypass_color_darkened);
solo->set_button_icon(get_editor_theme_icon(SNAME("AudioBusSolo")));
solo->add_theme_color_override("icon_pressed_color", solo_color);
+ solo->add_theme_color_override("icon_hover_pressed_color", solo_color_darkened);
mute->set_button_icon(get_editor_theme_icon(SNAME("AudioBusMute")));
mute->add_theme_color_override("icon_pressed_color", mute_color);
+ mute->add_theme_color_override("icon_hover_pressed_color", mute_color_darkened);
bypass->set_button_icon(get_editor_theme_icon(SNAME("AudioBusBypass")));
bypass->add_theme_color_override("icon_pressed_color", bypass_color);
+ bypass->add_theme_color_override("icon_hover_pressed_color", bypass_color_darkened);
bus_options->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
@@ -841,13 +850,18 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) {
child->begin_bulk_theme_override();
child->add_theme_style_override(CoreStringName(normal), sbempty);
child->add_theme_style_override("hover", sbempty);
+ child->add_theme_style_override("hover_mirrored", sbempty);
child->add_theme_style_override("focus", sbempty);
+ child->add_theme_style_override("focus_mirrored", sbempty);
Ref<StyleBoxFlat> sbflat = memnew(StyleBoxFlat);
sbflat->set_content_margin_all(0);
sbflat->set_bg_color(Color(1, 1, 1, 0));
sbflat->set_border_width(Side::SIDE_BOTTOM, Math::round(3 * EDSCALE));
child->add_theme_style_override(SceneStringName(pressed), sbflat);
+ child->add_theme_style_override("pressed_mirrored", sbflat);
+ child->add_theme_style_override("hover_pressed", sbflat);
+ child->add_theme_style_override("hover_pressed_mirrored", sbflat);
child->end_bulk_theme_override();
}
diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp
index 7ffbc39d75..9cf10c0ecb 100644
--- a/editor/editor_feature_profile.cpp
+++ b/editor/editor_feature_profile.cpp
@@ -43,37 +43,37 @@
const char *EditorFeatureProfile::feature_names[FEATURE_MAX] = {
TTRC("3D Editor"),
TTRC("Script Editor"),
- TTRC("Game View"),
TTRC("Asset Library"),
TTRC("Scene Tree Editing"),
TTRC("Node Dock"),
TTRC("FileSystem Dock"),
TTRC("Import Dock"),
TTRC("History Dock"),
+ TTRC("Game View"),
};
const char *EditorFeatureProfile::feature_descriptions[FEATURE_MAX] = {
TTRC("Allows to view and edit 3D scenes."),
TTRC("Allows to edit scripts using the integrated script editor."),
- TTRC("Provides tools for selecting and debugging nodes at runtime."),
TTRC("Provides built-in access to the Asset Library."),
TTRC("Allows editing the node hierarchy in the Scene dock."),
TTRC("Allows to work with signals and groups of the node selected in the Scene dock."),
TTRC("Allows to browse the local file system via a dedicated dock."),
TTRC("Allows to configure import settings for individual assets. Requires the FileSystem dock to function."),
TTRC("Provides an overview of the editor's and each scene's undo history."),
+ TTRC("Provides tools for selecting and debugging nodes at runtime."),
};
const char *EditorFeatureProfile::feature_identifiers[FEATURE_MAX] = {
"3d",
"script",
- "game",
"asset_lib",
"scene_tree",
"node_dock",
"filesystem_dock",
"import_dock",
"history_dock",
+ "game",
};
void EditorFeatureProfile::set_disable_class(const StringName &p_class, bool p_disabled) {
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index cd02482bc7..558eed98c6 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -2581,7 +2581,7 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector
EditorFileSystemDirectory *fs = nullptr;
int cpos = -1;
bool found = _find_file(file, &fs, cpos);
- ERR_FAIL_COND_V_MSG(!found, ERR_UNCONFIGURED, "Can't find file '" + file + "'.");
+ ERR_FAIL_COND_V_MSG(!found, ERR_UNCONFIGURED, vformat("Can't find file '%s' during group reimport.", file));
//update modified times, to avoid reimport
fs->files[cpos]->modified_time = FileAccess::get_modified_time(file);
@@ -2631,7 +2631,7 @@ Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<Strin
int cpos = -1;
if (p_update_file_system) {
bool found = _find_file(p_file, &fs, cpos);
- ERR_FAIL_COND_V_MSG(!found, ERR_FILE_NOT_FOUND, "Can't find file '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(!found, ERR_FILE_NOT_FOUND, vformat("Can't find file '%s' during file reimport.", p_file));
}
//try to obtain existing params
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 39a4c7df65..49c1699c28 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -789,7 +789,7 @@ public:
struct AdditiveNodeEntry {
Node *node = nullptr;
- NodePath parent = NodePath();
+ NodePath parent;
Node *owner = nullptr;
int index = 0;
// Used if the original parent node is lost
diff --git a/editor/gui/editor_quick_open_dialog.cpp b/editor/gui/editor_quick_open_dialog.cpp
index a6ad002de7..0128f1f54c 100644
--- a/editor/gui/editor_quick_open_dialog.cpp
+++ b/editor/gui/editor_quick_open_dialog.cpp
@@ -489,6 +489,10 @@ void QuickOpenResultContainer::handle_search_box_input(const Ref<InputEvent> &p_
}
void QuickOpenResultContainer::_move_selection_index(Key p_key) {
+ // Don't move selection if there are no results.
+ if (num_visible_results <= 0) {
+ return;
+ }
const int max_index = num_visible_results - 1;
int idx = selection_index;
@@ -649,8 +653,9 @@ QuickOpenDisplayMode QuickOpenResultContainer::get_adaptive_display_mode(const V
for (const StringName &type : grid_preferred_types) {
for (const StringName &base_type : p_base_types) {
- if (base_type == type || ClassDB::is_parent_class(base_type, type))
+ if (base_type == type || ClassDB::is_parent_class(base_type, type)) {
return QuickOpenDisplayMode::GRID;
+ }
}
}
diff --git a/editor/icons/MaterialPreviewQuad.svg b/editor/icons/MaterialPreviewQuad.svg
new file mode 100644
index 0000000000..9765a15df7
--- /dev/null
+++ b/editor/icons/MaterialPreviewQuad.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" stroke="#000" stroke-linejoin="round" stroke-opacity=".8" stroke-width="2" d="m2 1 12 1v11l-12 1z"/><path fill="#f9f9f9" d="m2 14 12-1v-11l-12-1z"/></svg> \ No newline at end of file
diff --git a/editor/import/3d/resource_importer_obj.cpp b/editor/import/3d/resource_importer_obj.cpp
index e77f5ec2b1..3669844207 100644
--- a/editor/import/3d/resource_importer_obj.cpp
+++ b/editor/import/3d/resource_importer_obj.cpp
@@ -535,8 +535,6 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes,
}
}
- mesh->optimize_indices_for_cache();
-
if (p_generate_lods) {
// Use normal merge/split angles that match the defaults used for 3D scene importing.
mesh->generate_lods(60.0f, {});
@@ -546,6 +544,8 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes,
mesh->create_shadow_mesh();
}
+ mesh->optimize_indices();
+
if (p_single_mesh && mesh->get_surface_count() > 0) {
r_meshes.push_back(mesh);
}
diff --git a/editor/import/3d/resource_importer_obj.h b/editor/import/3d/resource_importer_obj.h
index faf0f336c0..9d299bc31a 100644
--- a/editor/import/3d/resource_importer_obj.h
+++ b/editor/import/3d/resource_importer_obj.h
@@ -63,9 +63,6 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
- // Threaded import can currently cause deadlocks, see GH-48265.
- virtual bool can_import_threaded() const override { return false; }
-
ResourceImporterOBJ();
};
diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp
index 58af558e7b..edf7aa66f0 100644
--- a/editor/import/3d/resource_importer_scene.cpp
+++ b/editor/import/3d/resource_importer_scene.cpp
@@ -2567,8 +2567,6 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
}
}
- src_mesh_node->get_mesh()->optimize_indices_for_cache();
-
if (generate_lods) {
Array skin_pose_transform_array = _get_skinned_pose_transforms(src_mesh_node);
src_mesh_node->get_mesh()->generate_lods(merge_angle, skin_pose_transform_array);
@@ -2578,6 +2576,8 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
src_mesh_node->get_mesh()->create_shadow_mesh();
}
+ src_mesh_node->get_mesh()->optimize_indices();
+
if (!save_to_file.is_empty()) {
Ref<Mesh> existing = ResourceCache::get_ref(save_to_file);
if (existing.is_valid()) {
@@ -2622,6 +2622,7 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
mesh_node->set_layer_mask(src_mesh_node->get_layer_mask());
mesh_node->set_cast_shadows_setting(src_mesh_node->get_cast_shadows_setting());
+ mesh_node->set_visible(src_mesh_node->is_visible());
mesh_node->set_visibility_range_begin(src_mesh_node->get_visibility_range_begin());
mesh_node->set_visibility_range_begin_margin(src_mesh_node->get_visibility_range_begin_margin());
mesh_node->set_visibility_range_end(src_mesh_node->get_visibility_range_end());
diff --git a/editor/import/3d/resource_importer_scene.h b/editor/import/3d/resource_importer_scene.h
index fe757dc2a3..daeab2ae03 100644
--- a/editor/import/3d/resource_importer_scene.h
+++ b/editor/import/3d/resource_importer_scene.h
@@ -304,8 +304,6 @@ public:
virtual bool has_advanced_options() const override;
virtual void show_advanced_options(const String &p_path) override;
- virtual bool can_import_threaded() const override { return false; }
-
ResourceImporterScene(const String &p_scene_import_type = "PackedScene", bool p_singleton = false);
~ResourceImporterScene();
diff --git a/editor/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp
index 645b7fda88..945c1811d7 100644
--- a/editor/import/3d/scene_import_settings.cpp
+++ b/editor/import/3d/scene_import_settings.cpp
@@ -368,6 +368,7 @@ void SceneImportSettingsDialog::_fill_scene(Node *p_node, TreeItem *p_parent_ite
mesh_node->set_transform(src_mesh_node->get_transform());
mesh_node->set_skin(src_mesh_node->get_skin());
mesh_node->set_skeleton_path(src_mesh_node->get_skeleton_path());
+ mesh_node->set_visible(src_mesh_node->is_visible());
if (src_mesh_node->get_mesh().is_valid()) {
Ref<ImporterMesh> editor_mesh = src_mesh_node->get_mesh();
mesh_node->set_mesh(editor_mesh->get_mesh());
diff --git a/editor/import/resource_importer_bitmask.h b/editor/import/resource_importer_bitmask.h
index 8963c8d918..30564bf0fe 100644
--- a/editor/import/resource_importer_bitmask.h
+++ b/editor/import/resource_importer_bitmask.h
@@ -50,6 +50,8 @@ public:
virtual bool get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterBitMap();
~ResourceImporterBitMap();
};
diff --git a/editor/import/resource_importer_bmfont.h b/editor/import/resource_importer_bmfont.h
index d31cd03736..48f036ff13 100644
--- a/editor/import/resource_importer_bmfont.h
+++ b/editor/import/resource_importer_bmfont.h
@@ -50,6 +50,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterBMFont();
};
diff --git a/editor/import/resource_importer_csv_translation.h b/editor/import/resource_importer_csv_translation.h
index c6b05eb043..9c83719ed1 100644
--- a/editor/import/resource_importer_csv_translation.h
+++ b/editor/import/resource_importer_csv_translation.h
@@ -51,6 +51,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterCSVTranslation();
};
diff --git a/editor/import/resource_importer_dynamic_font.h b/editor/import/resource_importer_dynamic_font.h
index de89e6b76f..7c7a16cf92 100644
--- a/editor/import/resource_importer_dynamic_font.h
+++ b/editor/import/resource_importer_dynamic_font.h
@@ -60,6 +60,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterDynamicFont();
};
diff --git a/editor/import/resource_importer_image.h b/editor/import/resource_importer_image.h
index 1490ab30d5..dd395009c1 100644
--- a/editor/import/resource_importer_image.h
+++ b/editor/import/resource_importer_image.h
@@ -52,6 +52,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterImage();
};
diff --git a/editor/import/resource_importer_imagefont.h b/editor/import/resource_importer_imagefont.h
index 065351c361..6b30a3cd6e 100644
--- a/editor/import/resource_importer_imagefont.h
+++ b/editor/import/resource_importer_imagefont.h
@@ -50,6 +50,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterImageFont();
};
diff --git a/editor/import/resource_importer_layered_texture.h b/editor/import/resource_importer_layered_texture.h
index 26495eed8d..d8b5bc2d14 100644
--- a/editor/import/resource_importer_layered_texture.h
+++ b/editor/import/resource_importer_layered_texture.h
@@ -117,6 +117,8 @@ public:
virtual bool are_import_settings_valid(const String &p_path, const Dictionary &p_meta) const override;
virtual String get_import_settings_string() const override;
+ virtual bool can_import_threaded() const override { return true; }
+
void set_mode(Mode p_mode) { mode = p_mode; }
ResourceImporterLayeredTexture(bool p_singleton = false);
diff --git a/editor/import/resource_importer_shader_file.h b/editor/import/resource_importer_shader_file.h
index aefc967989..b28dea36d6 100644
--- a/editor/import/resource_importer_shader_file.h
+++ b/editor/import/resource_importer_shader_file.h
@@ -51,6 +51,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterShaderFile();
};
diff --git a/editor/import/resource_importer_texture.h b/editor/import/resource_importer_texture.h
index 6d74c4e2f9..6c87cd0abb 100644
--- a/editor/import/resource_importer_texture.h
+++ b/editor/import/resource_importer_texture.h
@@ -102,6 +102,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
void update_imports();
virtual bool are_import_settings_valid(const String &p_path, const Dictionary &p_meta) const override;
diff --git a/editor/import/resource_importer_texture_atlas.h b/editor/import/resource_importer_texture_atlas.h
index 0f2b10424c..e4ad9ac230 100644
--- a/editor/import/resource_importer_texture_atlas.h
+++ b/editor/import/resource_importer_texture_atlas.h
@@ -67,6 +67,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
virtual Error import_group_file(const String &p_group_file, const HashMap<String, HashMap<StringName, Variant>> &p_source_file_options, const HashMap<String, String> &p_base_paths) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterTextureAtlas();
};
diff --git a/editor/import/resource_importer_wav.cpp b/editor/import/resource_importer_wav.cpp
index 339e7921b3..2654952e8a 100644
--- a/editor/import/resource_importer_wav.cpp
+++ b/editor/import/resource_importer_wav.cpp
@@ -213,9 +213,7 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
frames = remaining_bytes;
}
- if (format_channels == 0) {
- ERR_FAIL_COND_V(format_channels == 0, ERR_INVALID_DATA);
- }
+ ERR_FAIL_COND_V(format_channels == 0, ERR_INVALID_DATA);
frames /= format_channels;
frames /= (format_bits >> 3);
diff --git a/editor/import/resource_importer_wav.h b/editor/import/resource_importer_wav.h
index 47af37ba41..2253756554 100644
--- a/editor/import/resource_importer_wav.h
+++ b/editor/import/resource_importer_wav.h
@@ -142,6 +142,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterWAV();
};
diff --git a/editor/plugins/bone_map_editor_plugin.cpp b/editor/plugins/bone_map_editor_plugin.cpp
index d9f8aae7f7..8b105955e7 100644
--- a/editor/plugins/bone_map_editor_plugin.cpp
+++ b/editor/plugins/bone_map_editor_plugin.cpp
@@ -859,7 +859,7 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
// 4-1. Guess Finger
int tips_index = -1;
- bool thumb_tips_size = 0;
+ bool thumb_tips_size = false;
bool named_finger_is_found = false;
LocalVector<String> fingers;
fingers.push_back("thumb|pollex");
@@ -994,7 +994,7 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
}
tips_index = -1;
- thumb_tips_size = 0;
+ thumb_tips_size = false;
named_finger_is_found = false;
if (right_hand_or_palm != -1) {
LocalVector<LocalVector<String>> right_fingers_map;
diff --git a/editor/plugins/font_config_plugin.cpp b/editor/plugins/font_config_plugin.cpp
index d27e85495c..7cf0b2d2ac 100644
--- a/editor/plugins/font_config_plugin.cpp
+++ b/editor/plugins/font_config_plugin.cpp
@@ -470,7 +470,7 @@ void EditorPropertyOTVariation::update_property() {
Vector3i range = supported.get_value_at_index(i);
EditorPropertyInteger *prop = memnew(EditorPropertyInteger);
- prop->setup(range.x, range.y, false, 1, false, false);
+ prop->setup(range.x, range.y, false, true, false, false);
prop->set_object_and_property(object.ptr(), "keys/" + itos(name_tag));
String name = TS->tag_to_name(name_tag);
diff --git a/editor/plugins/material_editor_plugin.cpp b/editor/plugins/material_editor_plugin.cpp
index be44d57376..8bdc763ebe 100644
--- a/editor/plugins/material_editor_plugin.cpp
+++ b/editor/plugins/material_editor_plugin.cpp
@@ -56,9 +56,15 @@ void MaterialEditor::gui_input(const Ref<InputEvent> &p_event) {
if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
rot.x -= mm->get_relative().y * 0.01;
rot.y -= mm->get_relative().x * 0.01;
-
- rot.x = CLAMP(rot.x, -Math_PI / 2, Math_PI / 2);
+ if (quad_instance->is_visible()) {
+ // Clamp rotation so the quad is always visible.
+ const real_t limit = Math::deg_to_rad(80.0);
+ rot = rot.clampf(-limit, limit);
+ } else {
+ rot.x = CLAMP(rot.x, -Math_PI / 2, Math_PI / 2);
+ }
_update_rotation();
+ _store_rotation_metadata();
}
}
@@ -70,6 +76,7 @@ void MaterialEditor::_update_theme_item_cache() {
theme_cache.sphere_icon = get_editor_theme_icon(SNAME("MaterialPreviewSphere"));
theme_cache.box_icon = get_editor_theme_icon(SNAME("MaterialPreviewCube"));
+ theme_cache.quad_icon = get_editor_theme_icon(SNAME("MaterialPreviewQuad"));
theme_cache.checkerboard = get_editor_theme_icon(SNAME("Checkerboard"));
}
@@ -82,6 +89,7 @@ void MaterialEditor::_notification(int p_what) {
sphere_switch->set_button_icon(theme_cache.sphere_icon);
box_switch->set_button_icon(theme_cache.box_icon);
+ quad_switch->set_button_icon(theme_cache.quad_icon);
error_label->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
} break;
@@ -95,6 +103,18 @@ void MaterialEditor::_notification(int p_what) {
}
}
+void MaterialEditor::_set_rotation(real_t p_x_degrees, real_t p_y_degrees) {
+ rot.x = Math::deg_to_rad(p_x_degrees);
+ rot.y = Math::deg_to_rad(p_y_degrees);
+ _update_rotation();
+}
+
+// Store the rotation so it can persist when switching between materials.
+void MaterialEditor::_store_rotation_metadata() {
+ Vector2 rotation_degrees = Vector2(Math::rad_to_deg(rot.x), Math::rad_to_deg(rot.y));
+ EditorSettings::get_singleton()->set_project_metadata("inspector_options", "material_preview_rotation", rotation_degrees);
+}
+
void MaterialEditor::_update_rotation() {
Transform3D t;
t.basis.rotate(Vector3(0, 1, 0), -rot.y);
@@ -124,6 +144,7 @@ void MaterialEditor::edit(Ref<Material> p_material, const Ref<Environment> &p_en
vc->show();
sphere_instance->set_material_override(material);
box_instance->set_material_override(material);
+ quad_instance->set_material_override(material);
break;
default:
layout_error->show();
@@ -136,10 +157,6 @@ void MaterialEditor::edit(Ref<Material> p_material, const Ref<Environment> &p_en
} else {
hide();
}
-
- rot.x = Math::deg_to_rad(-15.0);
- rot.y = Math::deg_to_rad(30.0);
- _update_rotation();
}
void MaterialEditor::_on_light_1_switch_pressed() {
@@ -151,19 +168,36 @@ void MaterialEditor::_on_light_2_switch_pressed() {
}
void MaterialEditor::_on_sphere_switch_pressed() {
- box_instance->hide();
sphere_instance->show();
+ box_instance->hide();
+ quad_instance->hide();
box_switch->set_pressed(false);
- sphere_switch->set_pressed(true);
- EditorSettings::get_singleton()->set_project_metadata("inspector_options", "material_preview_on_sphere", true);
+ quad_switch->set_pressed(false);
+ _set_rotation(-15.0, 30.0);
+ _store_rotation_metadata();
+ EditorSettings::get_singleton()->set_project_metadata("inspector_options", "material_preview_mesh", "sphere");
}
void MaterialEditor::_on_box_switch_pressed() {
+ sphere_instance->hide();
box_instance->show();
+ quad_instance->hide();
+ sphere_switch->set_pressed(false);
+ quad_switch->set_pressed(false);
+ _set_rotation(-15.0, 30.0);
+ _store_rotation_metadata();
+ EditorSettings::get_singleton()->set_project_metadata("inspector_options", "material_preview_mesh", "box");
+}
+
+void MaterialEditor::_on_quad_switch_pressed() {
sphere_instance->hide();
- box_switch->set_pressed(true);
+ box_instance->hide();
+ quad_instance->show();
sphere_switch->set_pressed(false);
- EditorSettings::get_singleton()->set_project_metadata("inspector_options", "material_preview_on_sphere", false);
+ box_switch->set_pressed(false);
+ _set_rotation(0.0, 0.0);
+ _store_rotation_metadata();
+ EditorSettings::get_singleton()->set_project_metadata("inspector_options", "material_preview_mesh", "quad");
}
MaterialEditor::MaterialEditor() {
@@ -213,7 +247,7 @@ MaterialEditor::MaterialEditor() {
viewport = memnew(SubViewport);
Ref<World3D> world_3d;
world_3d.instantiate();
- viewport->set_world_3d(world_3d); //use own world
+ viewport->set_world_3d(world_3d); // Use own world.
vc->add_child(viewport);
viewport->set_disable_input(true);
viewport->set_transparent_background(true);
@@ -221,7 +255,7 @@ MaterialEditor::MaterialEditor() {
camera = memnew(Camera3D);
camera->set_transform(Transform3D(Basis(), Vector3(0, 0, 1.1)));
- // Use low field of view so the sphere/box is fully encompassed within the preview,
+ // Use low field of view so the sphere/box/quad is fully encompassed within the preview,
// without much distortion.
camera->set_perspective(20, 0.1, 10);
camera->make_current();
@@ -249,13 +283,19 @@ MaterialEditor::MaterialEditor() {
box_instance = memnew(MeshInstance3D);
rotation->add_child(box_instance);
- box_instance->set_transform(Transform3D() * 0.25);
+ quad_instance = memnew(MeshInstance3D);
+ rotation->add_child(quad_instance);
+
sphere_instance->set_transform(Transform3D() * 0.375);
+ box_instance->set_transform(Transform3D() * 0.25);
+ quad_instance->set_transform(Transform3D() * 0.375);
sphere_mesh.instantiate();
sphere_instance->set_mesh(sphere_mesh);
box_mesh.instantiate();
box_instance->set_mesh(box_mesh);
+ quad_mesh.instantiate();
+ quad_instance->set_mesh(quad_mesh);
set_custom_minimum_size(Size2(1, 150) * EDSCALE);
@@ -269,17 +309,21 @@ MaterialEditor::MaterialEditor() {
sphere_switch = memnew(Button);
sphere_switch->set_theme_type_variation("PreviewLightButton");
sphere_switch->set_toggle_mode(true);
- sphere_switch->set_pressed(true);
vb_shape->add_child(sphere_switch);
sphere_switch->connect(SceneStringName(pressed), callable_mp(this, &MaterialEditor::_on_sphere_switch_pressed));
box_switch = memnew(Button);
box_switch->set_theme_type_variation("PreviewLightButton");
box_switch->set_toggle_mode(true);
- box_switch->set_pressed(false);
vb_shape->add_child(box_switch);
box_switch->connect(SceneStringName(pressed), callable_mp(this, &MaterialEditor::_on_box_switch_pressed));
+ quad_switch = memnew(Button);
+ quad_switch->set_theme_type_variation("PreviewLightButton");
+ quad_switch->set_toggle_mode(true);
+ vb_shape->add_child(quad_switch);
+ quad_switch->connect(SceneStringName(pressed), callable_mp(this, &MaterialEditor::_on_quad_switch_pressed));
+
layout_3d->add_spacer();
VBoxContainer *vb_light = memnew(VBoxContainer);
@@ -299,14 +343,23 @@ MaterialEditor::MaterialEditor() {
vb_light->add_child(light_2_switch);
light_2_switch->connect(SceneStringName(pressed), callable_mp(this, &MaterialEditor::_on_light_2_switch_pressed));
- if (EditorSettings::get_singleton()->get_project_metadata("inspector_options", "material_preview_on_sphere", true)) {
+ String shape = EditorSettings::get_singleton()->get_project_metadata("inspector_options", "material_preview_mesh", "sphere");
+ if (shape == "sphere") {
box_instance->hide();
+ quad_instance->hide();
+ sphere_switch->set_pressed_no_signal(true);
+ } else if (shape == "box") {
+ sphere_instance->hide();
+ quad_instance->hide();
+ box_switch->set_pressed_no_signal(true);
} else {
- box_instance->show();
sphere_instance->hide();
- box_switch->set_pressed(true);
- sphere_switch->set_pressed(false);
+ box_instance->hide();
+ quad_switch->set_pressed_no_signal(true);
}
+
+ Vector2 stored_rot = EditorSettings::get_singleton()->get_project_metadata("inspector_options", "material_preview_rotation", Vector2());
+ _set_rotation(stored_rot.x, stored_rot.y);
}
///////////////////////
diff --git a/editor/plugins/material_editor_plugin.h b/editor/plugins/material_editor_plugin.h
index 28c59d27db..c1b37a5831 100644
--- a/editor/plugins/material_editor_plugin.h
+++ b/editor/plugins/material_editor_plugin.h
@@ -62,6 +62,7 @@ class MaterialEditor : public Control {
Node3D *rotation = nullptr;
MeshInstance3D *sphere_instance = nullptr;
MeshInstance3D *box_instance = nullptr;
+ MeshInstance3D *quad_instance = nullptr;
DirectionalLight3D *light1 = nullptr;
DirectionalLight3D *light2 = nullptr;
Camera3D *camera = nullptr;
@@ -69,6 +70,7 @@ class MaterialEditor : public Control {
Ref<SphereMesh> sphere_mesh;
Ref<BoxMesh> box_mesh;
+ Ref<QuadMesh> quad_mesh;
VBoxContainer *layout_error = nullptr;
Label *error_label = nullptr;
@@ -80,6 +82,7 @@ class MaterialEditor : public Control {
Button *sphere_switch = nullptr;
Button *box_switch = nullptr;
+ Button *quad_switch = nullptr;
Button *light_1_switch = nullptr;
Button *light_2_switch = nullptr;
@@ -88,6 +91,7 @@ class MaterialEditor : public Control {
Ref<Texture2D> light_2_icon;
Ref<Texture2D> sphere_icon;
Ref<Texture2D> box_icon;
+ Ref<Texture2D> quad_icon;
Ref<Texture2D> checkerboard;
} theme_cache;
@@ -95,11 +99,14 @@ class MaterialEditor : public Control {
void _on_light_2_switch_pressed();
void _on_sphere_switch_pressed();
void _on_box_switch_pressed();
+ void _on_quad_switch_pressed();
protected:
virtual void _update_theme_item_cache() override;
void _notification(int p_what);
void gui_input(const Ref<InputEvent> &p_event) override;
+ void _set_rotation(real_t p_x_degrees, real_t p_y_degrees);
+ void _store_rotation_metadata();
void _update_rotation();
public:
diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.cpp b/editor/plugins/mesh_instance_3d_editor_plugin.cpp
index 48dcd2e6fc..fdc222e64f 100644
--- a/editor/plugins/mesh_instance_3d_editor_plugin.cpp
+++ b/editor/plugins/mesh_instance_3d_editor_plugin.cpp
@@ -40,6 +40,7 @@
#include "scene/3d/physics/collision_shape_3d.h"
#include "scene/3d/physics/physics_body_3d.h"
#include "scene/3d/physics/static_body_3d.h"
+#include "scene/gui/aspect_ratio_container.h"
#include "scene/gui/box_container.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/menu_button.h"
@@ -445,10 +446,43 @@ void MeshInstance3DEditor::_debug_uv_draw() {
}
debug_uv->set_clip_contents(true);
- debug_uv->draw_rect(Rect2(Vector2(), debug_uv->get_size()), get_theme_color(SNAME("dark_color_3"), EditorStringName(Editor)));
+ debug_uv->draw_rect(
+ Rect2(Vector2(), debug_uv->get_size()),
+ get_theme_color(SNAME("dark_color_3"), EditorStringName(Editor)));
+
+ // Draw an outline to represent the UV2's beginning and end area (useful on Black OLED theme).
+ // Top-left coordinate needs to be `(1, 1)` to prevent `clip_contents` from clipping the top and left lines.
+ debug_uv->draw_rect(
+ Rect2(Vector2(1, 1), debug_uv->get_size() - Vector2(1, 1)),
+ get_theme_color(SNAME("mono_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.125),
+ false,
+ Math::round(EDSCALE));
+
+ for (int x = 1; x <= 7; x++) {
+ debug_uv->draw_line(
+ Vector2(debug_uv->get_size().x * 0.125 * x, 0),
+ Vector2(debug_uv->get_size().x * 0.125 * x, debug_uv->get_size().y),
+ get_theme_color(SNAME("mono_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.125),
+ Math::round(EDSCALE));
+ }
+
+ for (int y = 1; y <= 7; y++) {
+ debug_uv->draw_line(
+ Vector2(0, debug_uv->get_size().y * 0.125 * y),
+ Vector2(debug_uv->get_size().x, debug_uv->get_size().y * 0.125 * y),
+ get_theme_color(SNAME("mono_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.125),
+ Math::round(EDSCALE));
+ }
+
debug_uv->draw_set_transform(Vector2(), 0, debug_uv->get_size());
+
// Use a translucent color to allow overlapping triangles to be visible.
- debug_uv->draw_multiline(uv_lines, get_theme_color(SNAME("mono_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.5));
+ // Divide line width by the drawing scale set above, so that line width is consistent regardless of dialog size.
+ // Aspect ratio is preserved by the parent AspectRatioContainer, so we only need to check the X size which is always equal to Y.
+ debug_uv->draw_multiline(
+ uv_lines,
+ get_theme_color(SNAME("mono_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.5),
+ Math::round(EDSCALE) / debug_uv->get_size().x);
}
void MeshInstance3DEditor::_create_navigation_mesh() {
@@ -613,10 +647,14 @@ MeshInstance3DEditor::MeshInstance3DEditor() {
debug_uv_dialog = memnew(AcceptDialog);
debug_uv_dialog->set_title(TTR("UV Channel Debug"));
add_child(debug_uv_dialog);
+
+ debug_uv_arc = memnew(AspectRatioContainer);
+ debug_uv_dialog->add_child(debug_uv_arc);
+
debug_uv = memnew(Control);
debug_uv->set_custom_minimum_size(Size2(600, 600) * EDSCALE);
debug_uv->connect(SceneStringName(draw), callable_mp(this, &MeshInstance3DEditor::_debug_uv_draw));
- debug_uv_dialog->add_child(debug_uv);
+ debug_uv_arc->add_child(debug_uv);
navigation_mesh_dialog = memnew(ConfirmationDialog);
navigation_mesh_dialog->set_title(TTR("Create NavigationMesh"));
diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.h b/editor/plugins/mesh_instance_3d_editor_plugin.h
index c982df9c5f..569ecd4fff 100644
--- a/editor/plugins/mesh_instance_3d_editor_plugin.h
+++ b/editor/plugins/mesh_instance_3d_editor_plugin.h
@@ -36,6 +36,7 @@
#include "scene/gui/option_button.h"
class AcceptDialog;
+class AspectRatioContainer;
class ConfirmationDialog;
class MenuButton;
class SpinBox;
@@ -79,6 +80,7 @@ class MeshInstance3DEditor : public Control {
AcceptDialog *err_dialog = nullptr;
AcceptDialog *debug_uv_dialog = nullptr;
+ AspectRatioContainer *debug_uv_arc = nullptr;
Control *debug_uv = nullptr;
Vector<Vector2> uv_lines;
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index b877c0b83e..5afe01025d 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -8480,7 +8480,7 @@ void Node3DEditor::clear() {
}
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
- viewports[i]->view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(Node3DEditorViewport::VIEW_AUDIO_LISTENER), i == 0);
+ viewports[i]->view_menu->get_popup()->set_item_checked(viewports[i]->view_menu->get_popup()->get_item_index(Node3DEditorViewport::VIEW_AUDIO_LISTENER), i == 0);
viewports[i]->viewport->set_as_audio_listener_3d(i == 0);
}
diff --git a/editor/plugins/tiles/tile_map_layer_editor.cpp b/editor/plugins/tiles/tile_map_layer_editor.cpp
index 804db68f86..16d4ee6f68 100644
--- a/editor/plugins/tiles/tile_map_layer_editor.cpp
+++ b/editor/plugins/tiles/tile_map_layer_editor.cpp
@@ -1550,6 +1550,7 @@ int TileMapLayerEditorTilesPlugin::_get_transformed_alternative(int p_alternativ
case TRANSFORM_ROTATE_RIGHT: {
// A matrix with every possible flip/transpose combination, sorted by what comes next when you rotate.
const LocalVector<bool> rotation_matrix = {
+ // NOLINTBEGIN(modernize-use-bool-literals)
0, 0, 0,
0, 1, 1,
1, 1, 0,
@@ -1558,6 +1559,7 @@ int TileMapLayerEditorTilesPlugin::_get_transformed_alternative(int p_alternativ
0, 0, 1,
0, 1, 0,
1, 1, 1
+ // NOLINTEND(modernize-use-bool-literals)
};
for (int i = 0; i < 8; i++) {
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h
index 39f2f51ef3..f8b65bd675 100644
--- a/editor/plugins/tiles/tile_set_atlas_source_editor.h
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h
@@ -91,7 +91,7 @@ public:
TileSetAtlasSourceEditor *tiles_set_atlas_source_editor = nullptr;
Ref<TileSetAtlasSource> tile_set_atlas_source;
- RBSet<TileSelection> tiles = RBSet<TileSelection>();
+ RBSet<TileSelection> tiles;
protected:
bool _set(const StringName &p_name, const Variant &p_value);
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index 05929c5f0d..9c1befa144 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -1472,7 +1472,7 @@ void VisualShaderGraphPlugin::disconnect_nodes(VisualShader::Type p_type, int p_
if (visual_shader.is_valid() && visual_shader->get_shader_type() == p_type) {
graph->disconnect_node(itos(p_from_node), p_from_port, itos(p_to_node), p_to_port);
- for (const List<VisualShader::Connection>::Element *E = connections.front(); E; E = E->next()) {
+ for (List<VisualShader::Connection>::Element *E = connections.front(); E; E = E->next()) {
if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) {
connections.erase(E);
break;
diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp
index f8ceb871dc..d08610c93f 100644
--- a/editor/project_converter_3_to_4.cpp
+++ b/editor/project_converter_3_to_4.cpp
@@ -716,8 +716,9 @@ Vector<String> ProjectConverter3To4::check_for_files() {
directories_to_check.append(current_dir.path_join(file_name) + "/");
} else {
bool proper_extension = false;
- if (file_name.ends_with(".gd") || file_name.ends_with(".shader") || file_name.ends_with(".gdshader") || file_name.ends_with(".tscn") || file_name.ends_with(".tres") || file_name.ends_with(".godot") || file_name.ends_with(".cs") || file_name.ends_with(".csproj") || file_name.ends_with(".import"))
+ if (file_name.ends_with(".gd") || file_name.ends_with(".shader") || file_name.ends_with(".gdshader") || file_name.ends_with(".tscn") || file_name.ends_with(".tres") || file_name.ends_with(".godot") || file_name.ends_with(".cs") || file_name.ends_with(".csproj") || file_name.ends_with(".import")) {
proper_extension = true;
+ }
if (proper_extension) {
collected_files.append(current_dir.path_join(file_name));
@@ -1321,8 +1322,9 @@ Vector<String> ProjectConverter3To4::parse_arguments(const String &line) {
break;
};
case '"': {
- if (previous_character != '\\')
+ if (previous_character != '\\') {
is_inside_string = !is_inside_string;
+ }
}
}
previous_character = character;
diff --git a/editor/themes/editor_color_map.cpp b/editor/themes/editor_color_map.cpp
index 9046a8b688..3c3d755586 100644
--- a/editor/themes/editor_color_map.cpp
+++ b/editor/themes/editor_color_map.cpp
@@ -173,6 +173,8 @@ void EditorColorMap::create() {
add_conversion_exception("OverbrightIndicator");
add_conversion_exception("MaterialPreviewCube");
add_conversion_exception("MaterialPreviewSphere");
+ add_conversion_exception("MaterialPreviewQuad");
+
add_conversion_exception("MaterialPreviewLight1");
add_conversion_exception("MaterialPreviewLight2");
diff --git a/methods.py b/methods.py
index 65b88a5c65..64c3839718 100644
--- a/methods.py
+++ b/methods.py
@@ -73,21 +73,13 @@ def print_error(*values: object) -> None:
def add_source_files_orig(self, sources, files, allow_gen=False):
# Convert string to list of absolute paths (including expanding wildcard)
- if isinstance(files, (str, bytes)):
- # Keep SCons project-absolute path as they are (no wildcard support)
- if files.startswith("#"):
- if "*" in files:
- print_error("Wildcards can't be expanded in SCons project-absolute path: '{}'".format(files))
- return
- files = [files]
- else:
- # Exclude .gen.cpp files from globbing, to avoid including obsolete ones.
- # They should instead be added manually.
- skip_gen_cpp = "*" in files
- dir_path = self.Dir(".").abspath
- files = sorted(glob.glob(dir_path + "/" + files))
- if skip_gen_cpp and not allow_gen:
- files = [f for f in files if not f.endswith(".gen.cpp")]
+ if isinstance(files, str):
+ # Exclude .gen.cpp files from globbing, to avoid including obsolete ones.
+ # They should instead be added manually.
+ skip_gen_cpp = "*" in files
+ files = self.Glob(files)
+ if skip_gen_cpp and not allow_gen:
+ files = [f for f in files if not str(f).endswith(".gen.cpp")]
# Add each path as compiled Object following environment (self) configuration
for path in files:
@@ -98,35 +90,6 @@ def add_source_files_orig(self, sources, files, allow_gen=False):
sources.append(obj)
-# The section name is used for checking
-# the hash table to see whether the folder
-# is included in the SCU build.
-# It will be something like "core/math".
-def _find_scu_section_name(subdir):
- section_path = os.path.abspath(subdir) + "/"
-
- folders = []
- folder = ""
-
- for i in range(8):
- folder = os.path.dirname(section_path)
- folder = os.path.basename(folder)
- if folder == base_folder_only:
- break
- folders += [folder]
- section_path += "../"
- section_path = os.path.abspath(section_path) + "/"
-
- section_name = ""
- for n in range(len(folders)):
- # section_name += folders[len(folders) - n - 1] + " "
- section_name += folders[len(folders) - n - 1]
- if n != (len(folders) - 1):
- section_name += "/"
-
- return section_name
-
-
def add_source_files_scu(self, sources, files, allow_gen=False):
if self["scu_build"] and isinstance(files, str):
if "*." not in files:
@@ -135,10 +98,8 @@ def add_source_files_scu(self, sources, files, allow_gen=False):
# If the files are in a subdirectory, we want to create the scu gen
# files inside this subdirectory.
subdir = os.path.dirname(files)
- if subdir != "":
- subdir += "/"
-
- section_name = _find_scu_section_name(subdir)
+ subdir = subdir if subdir == "" else subdir + "/"
+ section_name = self.Dir(subdir).tpath
# if the section name is in the hash table?
# i.e. is it part of the SCU build?
global _scu_folders
@@ -277,34 +238,6 @@ def get_version_info(module_version_string="", silent=False):
return version_info
-def parse_cg_file(fname, uniforms, sizes, conditionals):
- with open(fname, "r", encoding="utf-8") as fs:
- line = fs.readline()
-
- while line:
- if re.match(r"^\s*uniform", line):
- res = re.match(r"uniform ([\d\w]*) ([\d\w]*)")
- type = res.groups(1)
- name = res.groups(2)
-
- uniforms.append(name)
-
- if type.find("texobj") != -1:
- sizes.append(1)
- else:
- t = re.match(r"float(\d)x(\d)", type)
- if t:
- sizes.append(int(t.groups(1)) * int(t.groups(2)))
- else:
- t = re.match(r"float(\d)", type)
- sizes.append(int(t.groups(1)))
-
- if line.find("[branch]") != -1:
- conditionals.append(name)
-
- line = fs.readline()
-
-
def get_cmdline_bool(option, default):
"""We use `ARGUMENTS.get()` to check if options were manually overridden on the command line,
and SCons' _text2bool helper to convert them to booleans, otherwise they're handled as strings.
@@ -563,40 +496,7 @@ def detect_visual_c_compiler_version(tools_env):
vc_chosen_compiler_index = -1
vc_chosen_compiler_str = ""
- # Start with Pre VS 2017 checks which uses VCINSTALLDIR:
- if "VCINSTALLDIR" in tools_env:
- # print("Checking VCINSTALLDIR")
-
- # find() works with -1 so big ifs below are needed... the simplest solution, in fact
- # First test if amd64 and amd64_x86 compilers are present in the path
- vc_amd64_compiler_detection_index = tools_env["PATH"].find(tools_env["VCINSTALLDIR"] + "BIN\\amd64;")
- if vc_amd64_compiler_detection_index > -1:
- vc_chosen_compiler_index = vc_amd64_compiler_detection_index
- vc_chosen_compiler_str = "amd64"
-
- vc_amd64_x86_compiler_detection_index = tools_env["PATH"].find(tools_env["VCINSTALLDIR"] + "BIN\\amd64_x86;")
- if vc_amd64_x86_compiler_detection_index > -1 and (
- vc_chosen_compiler_index == -1 or vc_chosen_compiler_index > vc_amd64_x86_compiler_detection_index
- ):
- vc_chosen_compiler_index = vc_amd64_x86_compiler_detection_index
- vc_chosen_compiler_str = "amd64_x86"
-
- # Now check the 32 bit compilers
- vc_x86_compiler_detection_index = tools_env["PATH"].find(tools_env["VCINSTALLDIR"] + "BIN;")
- if vc_x86_compiler_detection_index > -1 and (
- vc_chosen_compiler_index == -1 or vc_chosen_compiler_index > vc_x86_compiler_detection_index
- ):
- vc_chosen_compiler_index = vc_x86_compiler_detection_index
- vc_chosen_compiler_str = "x86"
-
- vc_x86_amd64_compiler_detection_index = tools_env["PATH"].find(tools_env["VCINSTALLDIR"] + "BIN\\x86_amd64;")
- if vc_x86_amd64_compiler_detection_index > -1 and (
- vc_chosen_compiler_index == -1 or vc_chosen_compiler_index > vc_x86_amd64_compiler_detection_index
- ):
- vc_chosen_compiler_index = vc_x86_amd64_compiler_detection_index
- vc_chosen_compiler_str = "x86_amd64"
-
- # and for VS 2017 and newer we check VCTOOLSINSTALLDIR:
+ # VS 2017 and newer should set VCTOOLSINSTALLDIR
if "VCTOOLSINSTALLDIR" in tools_env:
# Newer versions have a different path available
vc_amd64_compiler_detection_index = (
diff --git a/modules/betsy/image_compress_betsy.h b/modules/betsy/image_compress_betsy.h
index 4e0bf0538f..ab7b785803 100644
--- a/modules/betsy/image_compress_betsy.h
+++ b/modules/betsy/image_compress_betsy.h
@@ -91,10 +91,10 @@ class BetsyCompressor : public Object {
RenderingDevice *compress_rd = nullptr;
RenderingContextDriver *compress_rcd = nullptr;
HashMap<String, BetsyShader> cached_shaders;
- RID src_sampler = RID();
+ RID src_sampler;
// Format-specific resources.
- RID dxt1_encoding_table_buffer = RID();
+ RID dxt1_encoding_table_buffer;
void _init();
void _assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id);
diff --git a/modules/camera/camera_feed_linux.cpp b/modules/camera/camera_feed_linux.cpp
index 94bb2b6ad3..3ae1b70ac9 100644
--- a/modules/camera/camera_feed_linux.cpp
+++ b/modules/camera/camera_feed_linux.cpp
@@ -145,7 +145,7 @@ bool CameraFeedLinux::_request_buffers() {
}
buffers[i].length = buffer.length;
- buffers[i].start = mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, file_descriptor, buffer.m.offset);
+ buffers[i].start = mmap(nullptr, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, file_descriptor, buffer.m.offset);
if (buffers[i].start == MAP_FAILED) {
for (unsigned int b = 0; b < i; b++) {
diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp
index ce097092fb..d6c304d056 100644
--- a/modules/fbx/fbx_document.cpp
+++ b/modules/fbx/fbx_document.cpp
@@ -369,21 +369,25 @@ Error FBXDocument::_parse_nodes(Ref<FBXState> p_state) {
// all skin clusters connected to the bone.
for (const ufbx_connection &child_conn : fbx_node->element.connections_src) {
ufbx_skin_cluster *child_cluster = ufbx_as_skin_cluster(child_conn.dst);
- if (!child_cluster)
+ if (!child_cluster) {
continue;
+ }
ufbx_skin_deformer *child_deformer = _find_skin_deformer(child_cluster);
- if (!child_deformer)
+ if (!child_deformer) {
continue;
+ }
// Found a skin cluster: Now iterate through all the skin clusters of the parent and
// try to find one that used by the same deformer.
for (const ufbx_connection &parent_conn : fbx_node->parent->element.connections_src) {
ufbx_skin_cluster *parent_cluster = ufbx_as_skin_cluster(parent_conn.dst);
- if (!parent_cluster)
+ if (!parent_cluster) {
continue;
+ }
ufbx_skin_deformer *parent_deformer = _find_skin_deformer(parent_cluster);
- if (parent_deformer != child_deformer)
+ if (parent_deformer != child_deformer) {
continue;
+ }
// Success: Found two skin clusters from the same deformer, now we can resolve the
// local bind pose from the difference between the two world-space bind poses.
@@ -1389,7 +1393,7 @@ Error FBXDocument::_parse_animations(Ref<FBXState> p_state) {
for (const ufbx_baked_node &fbx_baked_node : fbx_baked_anim->nodes) {
const GLTFNodeIndex node = fbx_baked_node.typed_id;
- GLTFAnimation::Track &track = animation->get_tracks()[node];
+ GLTFAnimation::NodeTrack &track = animation->get_node_tracks()[node];
for (const ufbx_baked_vec3 &key : fbx_baked_node.translation_keys) {
track.position_track.times.push_back(float(key.time));
@@ -1779,8 +1783,8 @@ void FBXDocument::_import_animation(Ref<FBXState> p_state, AnimationPlayer *p_an
double anim_start_offset = p_trimming ? double(additional_animation_data["time_begin"]) : 0.0;
- for (const KeyValue<int, GLTFAnimation::Track> &track_i : anim->get_tracks()) {
- const GLTFAnimation::Track &track = track_i.value;
+ for (const KeyValue<int, GLTFAnimation::NodeTrack> &track_i : anim->get_node_tracks()) {
+ const GLTFAnimation::NodeTrack &track = track_i.value;
//need to find the path: for skeletons, weight tracks will affect the mesh
NodePath node_path;
//for skeletons, transform tracks always affect bones
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 93d4a512a9..6241ada06a 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -5809,8 +5809,6 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
#ifdef DEBUG_ENABLED
void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope) {
const StringName &name = p_identifier->name;
- GDScriptParser::DataType base = parser->current_class->get_datatype();
- GDScriptParser::ClassNode *base_class = base.class_type;
{
List<MethodInfo> gdscript_funcs;
@@ -5838,37 +5836,53 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier
}
}
+ const GDScriptParser::DataType current_class_type = parser->current_class->get_datatype();
if (p_in_local_scope) {
- while (base_class != nullptr) {
+ GDScriptParser::ClassNode *base_class = current_class_type.class_type;
+
+ if (base_class != nullptr) {
if (base_class->has_member(name)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE, p_context, p_identifier->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line()));
return;
}
base_class = base_class->base_type.class_type;
}
+
+ while (base_class != nullptr) {
+ if (base_class->has_member(name)) {
+ String base_class_name = base_class->get_global_name();
+ if (base_class_name.is_empty()) {
+ base_class_name = base_class->fqcn;
+ }
+
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line()), base_class_name);
+ return;
+ }
+ base_class = base_class->base_type.class_type;
+ }
}
- StringName parent = base.native_type;
- while (parent != StringName()) {
- ERR_FAIL_COND_MSG(!class_exists(parent), "Non-existent native base class.");
+ StringName native_base_class = current_class_type.native_type;
+ while (native_base_class != StringName()) {
+ ERR_FAIL_COND_MSG(!class_exists(native_base_class), "Non-existent native base class.");
- if (ClassDB::has_method(parent, name, true)) {
- parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "method", parent);
+ if (ClassDB::has_method(native_base_class, name, true)) {
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "method", native_base_class);
return;
- } else if (ClassDB::has_signal(parent, name, true)) {
- parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "signal", parent);
+ } else if (ClassDB::has_signal(native_base_class, name, true)) {
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "signal", native_base_class);
return;
- } else if (ClassDB::has_property(parent, name, true)) {
- parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "property", parent);
+ } else if (ClassDB::has_property(native_base_class, name, true)) {
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "property", native_base_class);
return;
- } else if (ClassDB::has_integer_constant(parent, name, true)) {
- parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "constant", parent);
+ } else if (ClassDB::has_integer_constant(native_base_class, name, true)) {
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "constant", native_base_class);
return;
- } else if (ClassDB::has_enum(parent, name, true)) {
- parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "enum", parent);
+ } else if (ClassDB::has_enum(native_base_class, name, true)) {
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "enum", native_base_class);
return;
}
- parent = ClassDB::get_parent_class(parent);
+ native_base_class = ClassDB::get_parent_class(native_base_class);
}
}
#endif // DEBUG_ENABLED
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index bc063693a3..d94a6dfda2 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -790,8 +790,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += method->get_name();
text += "(";
for (int i = 0; i < argc; i++) {
- if (i > 0)
+ if (i > 0) {
text += ", ";
+ }
text += DADDR(1 + i);
}
text += ")";
@@ -833,8 +834,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += method->get_name();
text += "(";
for (int i = 0; i < argc; i++) {
- if (i > 0)
+ if (i > 0) {
text += ", ";
+ }
text += DADDR(1 + i);
}
text += ")";
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index 4ffb4bd9d1..a601cc4993 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -61,10 +61,13 @@ String GDScriptWarning::get_message() const {
return vformat(R"(The signal "%s" is declared but never explicitly used in the class.)", symbols[0]);
case SHADOWED_VARIABLE:
CHECK_SYMBOLS(4);
- return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s.)", symbols[0], symbols[1], symbols[2], symbols[3]);
+ return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s in the current class.)", symbols[0], symbols[1], symbols[2], symbols[3]);
case SHADOWED_VARIABLE_BASE_CLASS:
CHECK_SYMBOLS(4);
- return vformat(R"(The local %s "%s" is shadowing an already-declared %s at the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3]);
+ if (symbols.size() > 4) {
+ return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s in the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3], symbols[4]);
+ }
+ return vformat(R"(The local %s "%s" is shadowing an already-declared %s in the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3]);
case SHADOWED_GLOBAL_IDENTIFIER:
CHECK_SYMBOLS(3);
return vformat(R"(The %s "%s" has the same name as a %s.)", symbols[0], symbols[1], symbols[2]);
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index ffcf00a830..99e9b30af5 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -53,8 +53,8 @@ public:
UNUSED_PRIVATE_CLASS_VARIABLE, // Class variable is declared private ("_" prefix) but never used in the class.
UNUSED_PARAMETER, // Function parameter is never used.
UNUSED_SIGNAL, // Signal is defined but never explicitly used in the class.
- SHADOWED_VARIABLE, // Variable name shadowed by other variable in same class.
- SHADOWED_VARIABLE_BASE_CLASS, // Variable name shadowed by other variable in some base class.
+ SHADOWED_VARIABLE, // A local variable/constant shadows a current class member.
+ SHADOWED_VARIABLE_BASE_CLASS, // A local variable/constant shadows a base class member.
SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable.
UNREACHABLE_CODE, // Code after a return statement.
UNREACHABLE_PATTERN, // Pattern in a match statement after a catch all pattern (wildcard or bind).
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out
index 0e0d607831..cfe91e00bd 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out
@@ -6,6 +6,6 @@ GDTEST_OK
>> WARNING
>> Line: 5
>> SHADOWED_VARIABLE
->> The local variable "a" is shadowing an already-declared variable at line 1.
+>> The local variable "a" is shadowing an already-declared variable at line 1 in the current class.
1
2
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out
index 228a510490..ae0f2d8b8b 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out
@@ -10,6 +10,6 @@ GDTEST_OK
>> WARNING
>> Line: 5
>> SHADOWED_VARIABLE
->> The local variable "a" is shadowing an already-declared variable at line 1.
+>> The local variable "a" is shadowing an already-declared variable at line 1 in the current class.
1
2
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out
index 0d20e9f7a0..101d27df9d 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out
@@ -6,7 +6,7 @@ GDTEST_OK
>> WARNING
>> Line: 6
>> SHADOWED_VARIABLE
->> The local variable "a" is shadowing an already-declared variable at line 1.
+>> The local variable "a" is shadowing an already-declared variable at line 1 in the current class.
1
2
1
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out
index a98d80514c..5d059b9193 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out
@@ -2,5 +2,5 @@ GDTEST_OK
>> WARNING
>> Line: 4
>> SHADOWED_VARIABLE
->> The local function parameter "shadow" is shadowing an already-declared variable at line 1.
+>> The local function parameter "shadow" is shadowing an already-declared variable at line 1 in the current class.
shadow
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowing_base.notest.gd b/modules/gdscript/tests/scripts/analyzer/warnings/shadowing_base.notest.gd
new file mode 100644
index 0000000000..5819246ded
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowing_base.notest.gd
@@ -0,0 +1,7 @@
+class_name ShadowingBase
+
+const base_const_member = 1
+var base_variable_member
+
+func base_function_member():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd
index 939e787ea5..6a16ae6bcc 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd
@@ -1,4 +1,5 @@
class_name ShadowedClass
+extends ShadowingBase
var member: int = 0
@@ -7,6 +8,7 @@ var print_debug := 'print_debug'
var print := 'print'
@warning_ignore("unused_variable")
+@warning_ignore("unused_local_constant")
func test():
var Array := 'Array'
var Node := 'Node'
@@ -15,5 +17,8 @@ func test():
var member := 'member'
var reference := 'reference'
var ShadowedClass := 'ShadowedClass'
+ var base_variable_member
+ const base_function_member = 1
+ var base_const_member
print('warn')
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out
index 8297eed4b8..075f5d3225 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out
@@ -1,34 +1,46 @@
GDTEST_OK
>> WARNING
->> Line: 5
+>> Line: 6
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "print_debug" has the same name as a built-in function.
>> WARNING
->> Line: 11
+>> Line: 13
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "Array" has the same name as a built-in type.
>> WARNING
->> Line: 12
+>> Line: 14
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "Node" has the same name as a native class.
>> WARNING
->> Line: 13
+>> Line: 15
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "is_same" has the same name as a built-in function.
>> WARNING
->> Line: 14
+>> Line: 16
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "sqrt" has the same name as a built-in function.
>> WARNING
->> Line: 15
+>> Line: 17
>> SHADOWED_VARIABLE
->> The local variable "member" is shadowing an already-declared variable at line 3.
+>> The local variable "member" is shadowing an already-declared variable at line 4 in the current class.
>> WARNING
->> Line: 16
+>> Line: 18
>> SHADOWED_VARIABLE_BASE_CLASS
->> The local variable "reference" is shadowing an already-declared method at the base class "RefCounted".
+>> The local variable "reference" is shadowing an already-declared method in the base class "RefCounted".
>> WARNING
->> Line: 17
+>> Line: 19
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "ShadowedClass" has the same name as a global class defined in "shadowning.gd".
+>> WARNING
+>> Line: 20
+>> SHADOWED_VARIABLE_BASE_CLASS
+>> The local variable "base_variable_member" is shadowing an already-declared variable at line 4 in the base class "ShadowingBase".
+>> WARNING
+>> Line: 21
+>> SHADOWED_VARIABLE_BASE_CLASS
+>> The local constant "base_function_member" is shadowing an already-declared function at line 6 in the base class "ShadowingBase".
+>> WARNING
+>> Line: 22
+>> SHADOWED_VARIABLE_BASE_CLASS
+>> The local variable "base_const_member" is shadowing an already-declared constant at line 3 in the base class "ShadowingBase".
warn
diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out
index 75fa01f928..04df229f66 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out
+++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out
@@ -6,4 +6,4 @@ GDTEST_OK
>> WARNING
>> Line: 8
>> SHADOWED_VARIABLE
->> The local constant "TEST" is shadowing an already-declared constant at line 2.
+>> The local constant "TEST" is shadowing an already-declared constant at line 2 in the current class.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out
index aab27e78e2..4a6964f503 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out
+++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out
@@ -6,4 +6,4 @@ GDTEST_OK
>> WARNING
>> Line: 8
>> SHADOWED_VARIABLE
->> The local variable "foo" is shadowing an already-declared variable at line 1.
+>> The local variable "foo" is shadowing an already-declared variable at line 1 in the current class.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out
index e3cd358126..45fb771829 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out
+++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out
@@ -6,4 +6,4 @@ GDTEST_OK
>> WARNING
>> Line: 2
>> SHADOWED_VARIABLE
->> The local variable "test" is shadowing an already-declared function at line 1.
+>> The local variable "test" is shadowing an already-declared function at line 1 in the current class.
diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out
index 5492c8f763..1650acadb5 100644
--- a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out
+++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out
@@ -2,11 +2,11 @@ GDTEST_OK
>> WARNING
>> Line: 5
>> SHADOWED_VARIABLE
->> The local function parameter "a" is shadowing an already-declared variable at line 3.
+>> The local function parameter "a" is shadowing an already-declared variable at line 3 in the current class.
>> WARNING
>> Line: 15
>> SHADOWED_VARIABLE
->> The local function parameter "v" is shadowing an already-declared variable at line 13.
+>> The local function parameter "v" is shadowing an already-declared variable at line 13 in the current class.
a
1
b
diff --git a/modules/gltf/config.py b/modules/gltf/config.py
index 67233db579..823b8dbec2 100644
--- a/modules/gltf/config.py
+++ b/modules/gltf/config.py
@@ -20,6 +20,7 @@ def get_doc_classes():
"GLTFLight",
"GLTFMesh",
"GLTFNode",
+ "GLTFObjectModelProperty",
"GLTFPhysicsBody",
"GLTFPhysicsShape",
"GLTFSkeleton",
diff --git a/modules/gltf/doc_classes/GLTFAccessor.xml b/modules/gltf/doc_classes/GLTFAccessor.xml
index bc142797a3..04fa2a9835 100644
--- a/modules/gltf/doc_classes/GLTFAccessor.xml
+++ b/modules/gltf/doc_classes/GLTFAccessor.xml
@@ -22,7 +22,7 @@
The offset relative to the start of the buffer view in bytes.
</member>
<member name="component_type" type="int" setter="set_component_type" getter="get_component_type" default="0">
- The glTF component type as an enum. Possible values are 5120 for "BYTE", 5121 for "UNSIGNED_BYTE", 5122 for "SHORT", 5123 for "UNSIGNED_SHORT", 5125 for "UNSIGNED_INT", and 5126 for "FLOAT". A value of 5125 or "UNSIGNED_INT" must not be used for any accessor that is not referenced by mesh.primitive.indices.
+ The glTF component type as an enum. See [enum GLTFComponentType] for possible values. Within the core glTF specification, a value of 5125 or "UNSIGNED_INT" must not be used for any accessor that is not referenced by mesh.primitive.indices.
</member>
<member name="count" type="int" setter="set_count" getter="get_count" default="0">
The number of elements referenced by this accessor.
@@ -80,5 +80,41 @@
<constant name="TYPE_MAT4" value="6" enum="GLTFAccessorType">
Accessor type "MAT4". For the glTF object model, this maps to "float4x4", represented in the glTF JSON as an array of sixteen floats.
</constant>
+ <constant name="COMPONENT_TYPE_NONE" value="0" enum="GLTFComponentType">
+ Component type "NONE". This is not a valid component type, and is used to indicate that the component type is not set.
+ </constant>
+ <constant name="COMPONENT_TYPE_SIGNED_BYTE" value="5120" enum="GLTFComponentType">
+ Component type "BYTE". The value is [code]0x1400[/code] which comes from OpenGL. This indicates data is stored in 1-byte or 8-bit signed integers. This is a core part of the glTF specification.
+ </constant>
+ <constant name="COMPONENT_TYPE_UNSIGNED_BYTE" value="5121" enum="GLTFComponentType">
+ Component type "UNSIGNED_BYTE". The value is [code]0x1401[/code] which comes from OpenGL. This indicates data is stored in 1-byte or 8-bit unsigned integers. This is a core part of the glTF specification.
+ </constant>
+ <constant name="COMPONENT_TYPE_SIGNED_SHORT" value="5122" enum="GLTFComponentType">
+ Component type "SHORT". The value is [code]0x1402[/code] which comes from OpenGL. This indicates data is stored in 2-byte or 16-bit signed integers. This is a core part of the glTF specification.
+ </constant>
+ <constant name="COMPONENT_TYPE_UNSIGNED_SHORT" value="5123" enum="GLTFComponentType">
+ Component type "UNSIGNED_SHORT". The value is [code]0x1403[/code] which comes from OpenGL. This indicates data is stored in 2-byte or 16-bit unsigned integers. This is a core part of the glTF specification.
+ </constant>
+ <constant name="COMPONENT_TYPE_SIGNED_INT" value="5124" enum="GLTFComponentType">
+ Component type "INT". The value is [code]0x1404[/code] which comes from OpenGL. This indicates data is stored in 4-byte or 32-bit signed integers. This is NOT a core part of the glTF specification, and may not be supported by all glTF importers. May be used by some extensions including [code]KHR_interactivity[/code].
+ </constant>
+ <constant name="COMPONENT_TYPE_UNSIGNED_INT" value="5125" enum="GLTFComponentType">
+ Component type "UNSIGNED_INT". The value is [code]0x1405[/code] which comes from OpenGL. This indicates data is stored in 4-byte or 32-bit unsigned integers. This is a core part of the glTF specification.
+ </constant>
+ <constant name="COMPONENT_TYPE_SINGLE_FLOAT" value="5126" enum="GLTFComponentType">
+ Component type "FLOAT". The value is [code]0x1406[/code] which comes from OpenGL. This indicates data is stored in 4-byte or 32-bit floating point numbers. This is a core part of the glTF specification.
+ </constant>
+ <constant name="COMPONENT_TYPE_DOUBLE_FLOAT" value="5130" enum="GLTFComponentType">
+ Component type "DOUBLE". The value is [code]0x140A[/code] which comes from OpenGL. This indicates data is stored in 8-byte or 64-bit floating point numbers. This is NOT a core part of the glTF specification, and may not be supported by all glTF importers. May be used by some extensions including [code]KHR_interactivity[/code].
+ </constant>
+ <constant name="COMPONENT_TYPE_HALF_FLOAT" value="5131" enum="GLTFComponentType">
+ Component type "HALF_FLOAT". The value is [code]0x140B[/code] which comes from OpenGL. This indicates data is stored in 2-byte or 16-bit floating point numbers. This is NOT a core part of the glTF specification, and may not be supported by all glTF importers. May be used by some extensions including [code]KHR_interactivity[/code].
+ </constant>
+ <constant name="COMPONENT_TYPE_SIGNED_LONG" value="5134" enum="GLTFComponentType">
+ Component type "LONG". The value is [code]0x140E[/code] which comes from OpenGL. This indicates data is stored in 8-byte or 64-bit signed integers. This is NOT a core part of the glTF specification, and may not be supported by all glTF importers. May be used by some extensions including [code]KHR_interactivity[/code].
+ </constant>
+ <constant name="COMPONENT_TYPE_UNSIGNED_LONG" value="5135" enum="GLTFComponentType">
+ Component type "UNSIGNED_LONG". The value is [code]0x140F[/code] which comes from OpenGL. This indicates data is stored in 8-byte or 64-bit unsigned integers. This is NOT a core part of the glTF specification, and may not be supported by all glTF importers. May be used by some extensions including [code]KHR_interactivity[/code].
+ </constant>
</constants>
</class>
diff --git a/modules/gltf/doc_classes/GLTFAnimation.xml b/modules/gltf/doc_classes/GLTFAnimation.xml
index d269145bbd..f11dea8ee8 100644
--- a/modules/gltf/doc_classes/GLTFAnimation.xml
+++ b/modules/gltf/doc_classes/GLTFAnimation.xml
@@ -13,7 +13,7 @@
<param index="0" name="extension_name" type="StringName" />
<description>
Gets additional arbitrary data in this [GLTFAnimation] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless.
- The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is null.
+ The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is [code]null[/code].
</description>
</method>
<method name="set_additional_data">
diff --git a/modules/gltf/doc_classes/GLTFBufferView.xml b/modules/gltf/doc_classes/GLTFBufferView.xml
index b7f499ad72..9c2ee3197f 100644
--- a/modules/gltf/doc_classes/GLTFBufferView.xml
+++ b/modules/gltf/doc_classes/GLTFBufferView.xml
@@ -34,10 +34,10 @@
The stride, in bytes, between interleaved data. If [code]-1[/code], this buffer view is not interleaved.
</member>
<member name="indices" type="bool" setter="set_indices" getter="get_indices" default="false">
- True if the GLTFBufferView's OpenGL GPU buffer type is an [code]ELEMENT_ARRAY_BUFFER[/code] used for vertex indices (integer constant [code]34963[/code]). False if the buffer type is any other value. See [url=https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_005_BuffersBufferViewsAccessors.md]Buffers, BufferViews, and Accessors[/url] for possible values. This property is set on import and used on export.
+ [code]true[/code] if the GLTFBufferView's OpenGL GPU buffer type is an [code]ELEMENT_ARRAY_BUFFER[/code] used for vertex indices (integer constant [code]34963[/code]). [code]false[/code] if the buffer type is any other value. See [url=https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_005_BuffersBufferViewsAccessors.md]Buffers, BufferViews, and Accessors[/url] for possible values. This property is set on import and used on export.
</member>
<member name="vertex_attributes" type="bool" setter="set_vertex_attributes" getter="get_vertex_attributes" default="false">
- True if the GLTFBufferView's OpenGL GPU buffer type is an [code]ARRAY_BUFFER[/code] used for vertex attributes (integer constant [code]34962[/code]). False if the buffer type is any other value. See [url=https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_005_BuffersBufferViewsAccessors.md]Buffers, BufferViews, and Accessors[/url] for possible values. This property is set on import and used on export.
+ [code]true[/code] if the GLTFBufferView's OpenGL GPU buffer type is an [code]ARRAY_BUFFER[/code] used for vertex attributes (integer constant [code]34962[/code]). [code]false[/code] if the buffer type is any other value. See [url=https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_005_BuffersBufferViewsAccessors.md]Buffers, BufferViews, and Accessors[/url] for possible values. This property is set on import and used on export.
</member>
</members>
</class>
diff --git a/modules/gltf/doc_classes/GLTFCamera.xml b/modules/gltf/doc_classes/GLTFCamera.xml
index 9fce21659c..12334683ba 100644
--- a/modules/gltf/doc_classes/GLTFCamera.xml
+++ b/modules/gltf/doc_classes/GLTFCamera.xml
@@ -47,13 +47,13 @@
The distance to the near culling boundary for this camera relative to its local Z axis, in meters. This maps to glTF's [code]znear[/code] property.
</member>
<member name="fov" type="float" setter="set_fov" getter="get_fov" default="1.309">
- The FOV of the camera. This class and glTF define the camera FOV in radians, while Godot uses degrees. This maps to glTF's [code]yfov[/code] property. This value is only used for perspective cameras, when [member perspective] is true.
+ The FOV of the camera. This class and glTF define the camera FOV in radians, while Godot uses degrees. This maps to glTF's [code]yfov[/code] property. This value is only used for perspective cameras, when [member perspective] is [code]true[/code].
</member>
<member name="perspective" type="bool" setter="set_perspective" getter="get_perspective" default="true">
- Whether or not the camera is in perspective mode. If false, the camera is in orthographic/orthogonal mode. This maps to glTF's camera [code]type[/code] property. See [member Camera3D.projection] and the glTF spec for more information.
+ If [code]true[/code], the camera is in perspective mode. Otherwise, the camera is in orthographic/orthogonal mode. This maps to glTF's camera [code]type[/code] property. See [member Camera3D.projection] and the glTF spec for more information.
</member>
<member name="size_mag" type="float" setter="set_size_mag" getter="get_size_mag" default="0.5">
- The size of the camera. This class and glTF define the camera size magnitude as a radius in meters, while Godot defines it as a diameter in meters. This maps to glTF's [code]ymag[/code] property. This value is only used for orthographic/orthogonal cameras, when [member perspective] is false.
+ The size of the camera. This class and glTF define the camera size magnitude as a radius in meters, while Godot defines it as a diameter in meters. This maps to glTF's [code]ymag[/code] property. This value is only used for orthographic/orthogonal cameras, when [member perspective] is [code]false[/code].
</member>
</members>
</class>
diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml
index 10534594d3..47ffc624ba 100644
--- a/modules/gltf/doc_classes/GLTFDocument.xml
+++ b/modules/gltf/doc_classes/GLTFDocument.xml
@@ -45,6 +45,16 @@
Takes a Godot Engine scene node and exports it and its descendants to the given [GLTFState] object through the [param state] parameter.
</description>
</method>
+ <method name="export_object_model_property" qualifiers="static">
+ <return type="GLTFObjectModelProperty" />
+ <param index="0" name="state" type="GLTFState" />
+ <param index="1" name="node_path" type="NodePath" />
+ <param index="2" name="godot_node" type="Node" />
+ <param index="3" name="gltf_node_index" type="int" />
+ <description>
+ Determines a mapping between the given Godot [param node_path] and the corresponding glTF Object Model JSON pointer(s) in the generated glTF file. The details of this mapping are returned in a [GLTFObjectModelProperty] object. Additional mappings can be supplied via the [method GLTFDocumentExtension._import_object_model_property] callback method.
+ </description>
+ </method>
<method name="generate_buffer">
<return type="PackedByteArray" />
<param index="0" name="state" type="GLTFState" />
@@ -70,12 +80,20 @@
[b]Note:[/b] If this method is run before a GLTFDocumentExtension is registered, its extensions won't be included in the list. Be sure to only run this method after all extensions are registered. If you run this when the engine starts, consider waiting a frame before calling this method to ensure all extensions are registered.
</description>
</method>
+ <method name="import_object_model_property" qualifiers="static">
+ <return type="GLTFObjectModelProperty" />
+ <param index="0" name="state" type="GLTFState" />
+ <param index="1" name="json_pointer" type="String" />
+ <description>
+ Determines a mapping between the given glTF Object Model [param json_pointer] and the corresponding Godot node path(s) in the generated Godot scene. The details of this mapping are returned in a [GLTFObjectModelProperty] object. Additional mappings can be supplied via the [method GLTFDocumentExtension._export_object_model_property] callback method.
+ </description>
+ </method>
<method name="register_gltf_document_extension" qualifiers="static">
<return type="void" />
<param index="0" name="extension" type="GLTFDocumentExtension" />
<param index="1" name="first_priority" type="bool" default="false" />
<description>
- Registers the given [GLTFDocumentExtension] instance with GLTFDocument. If [param first_priority] is true, this extension will be run first. Otherwise, it will be run last.
+ Registers the given [GLTFDocumentExtension] instance with GLTFDocument. If [param first_priority] is [code]true[/code], this extension will be run first. Otherwise, it will be run last.
[b]Note:[/b] Like GLTFDocument itself, all GLTFDocumentExtension classes must be stateless in order to function properly. If you need to store data, use the [code]set_additional_data[/code] and [code]get_additional_data[/code] methods in [GLTFState] or [GLTFNode].
</description>
</method>
diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
index b33e296e1c..abdbce7eeb 100644
--- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml
+++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
@@ -30,7 +30,21 @@
<param index="3" name="node" type="Node" />
<description>
Part of the export process. This method is run after [method _get_saveable_image_formats] and before [method _export_post]. If this [GLTFDocumentExtension] is used for exporting images, this runs after [method _serialize_texture_json].
- This method can be used to modify the final JSON of each node. Data should be primarily stored in [param gltf_node] prior to serializing the JSON, but the original Godot [param node] is also provided if available. The node may be null if not available, such as when exporting glTF data not generated from a Godot scene.
+ This method can be used to modify the final JSON of each node. Data should be primarily stored in [param gltf_node] prior to serializing the JSON, but the original Godot [Node] is also provided if available. [param node] may be [code]null[/code] if not available, such as when exporting glTF data not generated from a Godot scene.
+ </description>
+ </method>
+ <method name="_export_object_model_property" qualifiers="virtual">
+ <return type="GLTFObjectModelProperty" />
+ <param index="0" name="state" type="GLTFState" />
+ <param index="1" name="node_path" type="NodePath" />
+ <param index="2" name="godot_node" type="Node" />
+ <param index="3" name="gltf_node_index" type="int" />
+ <param index="4" name="target_object" type="Object" />
+ <param index="5" name="target_depth" type="int" />
+ <description>
+ Part of the export process. Allows GLTFDocumentExtension classes to provide mappings for properties of nodes in the Godot scene tree, to JSON pointers to glTF properties, as defined by the glTF object model.
+ Returns a [GLTFObjectModelProperty] instance that defines how the property should be mapped. If your extension can't handle the property, return [code]null[/code] or an instance without any JSON pointers (see [method GLTFObjectModelProperty.has_json_pointers]). You should use [method GLTFObjectModelProperty.set_types] to set the types, and set the JSON pointer(s) using the [member GLTFObjectModelProperty.json_pointers] property.
+ The parameters provide context for the property, including the NodePath, the Godot node, the GLTF node index, and the target object. The [param target_object] will be equal to [param godot_node] if no sub-object can be found, otherwise it will point to a sub-object. For example, if the path is [code]^"A/B/C/MeshInstance3D:mesh:surface_0/material:emission_intensity"[/code], it will get the node, then the mesh, and then the material, so [param target_object] will be the [Material] resource, and [param target_depth] will be 2 because 2 levels were traversed to get to the target.
</description>
</method>
<method name="_export_post" qualifiers="virtual">
@@ -75,7 +89,7 @@
<description>
Part of the import process. This method is run after [method _import_pre_generate] and before [method _import_node].
Runs when generating a Godot scene node from a GLTFNode. The returned node will be added to the scene tree. Multiple nodes can be generated in this step if they are added as a child of the returned node.
- [b]Note:[/b] The [param scene_parent] parameter may be null if this is the single root node.
+ [b]Note:[/b] The [param scene_parent] parameter may be [code]null[/code] if this is the single root node.
</description>
</method>
<method name="_get_image_file_extension" qualifiers="virtual">
@@ -109,6 +123,17 @@
This method can be used to make modifications to each of the generated Godot scene nodes.
</description>
</method>
+ <method name="_import_object_model_property" qualifiers="virtual">
+ <return type="GLTFObjectModelProperty" />
+ <param index="0" name="state" type="GLTFState" />
+ <param index="1" name="split_json_pointer" type="PackedStringArray" />
+ <param index="2" name="partial_paths" type="NodePath[]" />
+ <description>
+ Part of the import process. Allows GLTFDocumentExtension classes to provide mappings for JSON pointers to glTF properties, as defined by the glTF object model, to properties of nodes in the Godot scene tree.
+ Returns a [GLTFObjectModelProperty] instance that defines how the property should be mapped. If your extension can't handle the property, return [code]null[/code] or an instance without any NodePaths (see [method GLTFObjectModelProperty.has_node_paths]). You should use [method GLTFObjectModelProperty.set_types] to set the types, and [method GLTFObjectModelProperty.append_path_to_property] function is useful for most simple cases.
+ In many cases, [param partial_paths] will contain the start of a path, allowing the extension to complete the path. For example, for [code]/nodes/3/extensions/MY_ext/prop[/code], Godot will pass you a NodePath that leads to node 3, so the GLTFDocumentExtension class only needs to resolve the last [code]MY_ext/prop[/code] part of the path. In this example, the extension should check [code]split.size() &gt; 4 and split[0] == "nodes" and split[2] == "extensions" and split[3] == "MY_ext"[/code] at the start of the function to check if this JSON pointer applies to it, then it can use [param partial_paths] and handle [code]split[4][/code].
+ </description>
+ </method>
<method name="_import_post" qualifiers="virtual">
<return type="int" enum="Error" />
<param index="0" name="state" type="GLTFState" />
diff --git a/modules/gltf/doc_classes/GLTFMesh.xml b/modules/gltf/doc_classes/GLTFMesh.xml
index da73c20c1d..439078ffcc 100644
--- a/modules/gltf/doc_classes/GLTFMesh.xml
+++ b/modules/gltf/doc_classes/GLTFMesh.xml
@@ -15,7 +15,7 @@
<param index="0" name="extension_name" type="StringName" />
<description>
Gets additional arbitrary data in this [GLTFMesh] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless.
- The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is null.
+ The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is [code]null[/code].
</description>
</method>
<method name="set_additional_data">
diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml
index a242a0d1d8..0fb85e4029 100644
--- a/modules/gltf/doc_classes/GLTFNode.xml
+++ b/modules/gltf/doc_classes/GLTFNode.xml
@@ -24,7 +24,16 @@
<param index="0" name="extension_name" type="StringName" />
<description>
Gets additional arbitrary data in this [GLTFNode] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless.
- The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is null.
+ The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is [code]null[/code].
+ </description>
+ </method>
+ <method name="get_scene_node_path">
+ <return type="NodePath" />
+ <param index="0" name="gltf_state" type="GLTFState" />
+ <param index="1" name="handle_skeletons" type="bool" default="true" />
+ <description>
+ Returns the [NodePath] that this GLTF node will have in the Godot scene tree after being imported. This is useful when importing glTF object model pointers with [GLTFObjectModelProperty], for handling extensions such as [code]KHR_animation_pointer[/code] or [code]KHR_interactivity[/code].
+ If [param handle_skeletons] is [code]true[/code], paths to skeleton bone glTF nodes will be resolved properly. For example, a path that would be [code]^"A/B/C/Bone1/Bone2/Bone3"[/code] if [code]false[/code] will become [code]^"A/B/C/Skeleton3D:Bone3"[/code].
</description>
</method>
<method name="set_additional_data">
diff --git a/modules/gltf/doc_classes/GLTFObjectModelProperty.xml b/modules/gltf/doc_classes/GLTFObjectModelProperty.xml
new file mode 100644
index 0000000000..a457d94c41
--- /dev/null
+++ b/modules/gltf/doc_classes/GLTFObjectModelProperty.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GLTFObjectModelProperty" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
+ <brief_description>
+ Describes how to access a property as defined in the glTF object model.
+ </brief_description>
+ <description>
+ GLTFObjectModelProperty defines a mapping between a property in the glTF object model and a NodePath in the Godot scene tree. This can be used to animate properties in a glTF file using the [code]KHR_animation_pointer[/code] extension, or to access them through an engine-agnostic script such as a behavior graph as defined by the [code]KHR_interactivity[/code] extension.
+ The glTF property is identified by JSON pointer(s) stored in [member json_pointers], while the Godot property it maps to is defined by [member node_paths]. In most cases [member json_pointers] and [member node_paths] will each only have one item, but in some cases a single glTF JSON pointer will map to multiple Godot properties, or a single Godot property will be mapped to multiple glTF JSON pointers, or it might be a many-to-many relationship.
+ [Expression] objects can be used to define conversions between the data, such as when glTF defines an angle in radians and Godot uses degrees. The [member object_model_type] property defines the type of data stored in the glTF file as defined by the object model, see [enum GLTFObjectModelType] for possible values.
+ </description>
+ <tutorials>
+ <link title="GLTF Object Model">https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/ObjectModel.adoc</link>
+ <link title="KHR_animation_pointer GLTF extension">https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_animation_pointer</link>
+ </tutorials>
+ <methods>
+ <method name="append_node_path">
+ <return type="void" />
+ <param index="0" name="node_path" type="NodePath" />
+ <description>
+ Appends a [NodePath] to [member node_paths]. This can be used by [GLTFDocumentExtension] classes to define how a glTF object model property maps to a Godot property, or multiple Godot properties. Prefer using [method append_path_to_property] for simple cases. Be sure to also call [method set_types] once (the order does not matter).
+ </description>
+ </method>
+ <method name="append_path_to_property">
+ <return type="void" />
+ <param index="0" name="node_path" type="NodePath" />
+ <param index="1" name="prop_name" type="StringName" />
+ <description>
+ High-level wrapper over [method append_node_path] that handles the most common cases. It constructs a new [NodePath] using [param node_path] as a base and appends [param prop_name] to the subpath. Be sure to also call [method set_types] once (the order does not matter).
+ </description>
+ </method>
+ <method name="get_accessor_type" qualifiers="const">
+ <return type="int" enum="GLTFAccessor.GLTFAccessorType" />
+ <description>
+ The GLTF accessor type associated with this property's [member object_model_type]. See [member GLTFAccessor.accessor_type] for possible values, and see [enum GLTFObjectModelType] for how the object model type maps to accessor types.
+ </description>
+ </method>
+ <method name="has_json_pointers" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if [member json_pointers] is not empty. This is used during export to determine if a [GLTFObjectModelProperty] can handle converting a Godot property to a glTF object model property.
+ </description>
+ </method>
+ <method name="has_node_paths" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if [member node_paths] is not empty. This is used during import to determine if a [GLTFObjectModelProperty] can handle converting a glTF object model property to a Godot property.
+ </description>
+ </method>
+ <method name="set_types">
+ <return type="void" />
+ <param index="0" name="variant_type" type="int" enum="Variant.Type" />
+ <param index="1" name="obj_model_type" type="int" enum="GLTFObjectModelProperty.GLTFObjectModelType" />
+ <description>
+ Sets the [member variant_type] and [member object_model_type] properties. This is a convenience method to set both properties at once, since they are almost always known at the same time. This method should be called once. Calling it again with the same values will have no effect.
+ </description>
+ </method>
+ </methods>
+ <members>
+ <member name="gltf_to_godot_expression" type="Expression" setter="set_gltf_to_godot_expression" getter="get_gltf_to_godot_expression">
+ If set, this [Expression] will be used to convert the property value from the glTF object model to the value expected by the Godot property. This is useful when the glTF object model uses a different unit system, or when the data needs to be transformed in some way. If [code]null[/code], the value will be copied as-is.
+ </member>
+ <member name="godot_to_gltf_expression" type="Expression" setter="set_godot_to_gltf_expression" getter="get_godot_to_gltf_expression">
+ If set, this [Expression] will be used to convert the property value from the Godot property to the value expected by the glTF object model. This is useful when the glTF object model uses a different unit system, or when the data needs to be transformed in some way. If [code]null[/code], the value will be copied as-is.
+ </member>
+ <member name="json_pointers" type="PackedStringArray[]" setter="set_json_pointers" getter="get_json_pointers" default="[]">
+ The glTF object model JSON pointers used to identify the property in the glTF object model. In most cases, there will be only one item in this array, but niche cases may require multiple pointers. The items are themselves arrays which represent the JSON pointer split into its components.
+ </member>
+ <member name="node_paths" type="NodePath[]" setter="set_node_paths" getter="get_node_paths" default="[]">
+ An array of [NodePath]s that point to a property, or multiple properties, in the Godot scene tree. On import, this will either be set by [GLTFDocument], or by a [GLTFDocumentExtension] class. For simple cases, use [method append_path_to_property] to add properties to this array.
+ In most cases [member node_paths] will only have one item, but in some cases a single glTF JSON pointer will map to multiple Godot properties. For example, a [GLTFCamera] or [GLTFLight] used on multiple glTF nodes will be represented by multiple Godot nodes.
+ </member>
+ <member name="object_model_type" type="int" setter="set_object_model_type" getter="get_object_model_type" enum="GLTFObjectModelProperty.GLTFObjectModelType" default="0">
+ The type of data stored in the glTF file as defined by the object model. This is a superset of the available accessor types, and determines the accessor type. See [enum GLTFObjectModelType] for possible values.
+ </member>
+ <member name="variant_type" type="int" setter="set_variant_type" getter="get_variant_type" enum="Variant.Type" default="0">
+ The type of data stored in the Godot property. This is the type of the property that the [member node_paths] point to.
+ </member>
+ </members>
+ <constants>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_UNKNOWN" value="0" enum="GLTFObjectModelType">
+ Unknown or not set object model type. If the object model type is set to this value, the real type still needs to be determined.
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_BOOL" value="1" enum="GLTFObjectModelType">
+ Object model type "bool". Represented in the glTF JSON as a boolean, and encoded in a [GLTFAccessor] as "SCALAR". When encoded in an accessor, a value of [code]0[/code] is [code]false[/code], and any other value is [code]true[/code].
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT" value="2" enum="GLTFObjectModelType">
+ Object model type "float". Represented in the glTF JSON as a number, and encoded in a [GLTFAccessor] as "SCALAR".
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT_ARRAY" value="3" enum="GLTFObjectModelType">
+ Object model type "float[lb][rb]". Represented in the glTF JSON as an array of numbers, and encoded in a [GLTFAccessor] as "SCALAR".
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT2" value="4" enum="GLTFObjectModelType">
+ Object model type "float2". Represented in the glTF JSON as an array of two numbers, and encoded in a [GLTFAccessor] as "VEC2".
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT3" value="5" enum="GLTFObjectModelType">
+ Object model type "float3". Represented in the glTF JSON as an array of three numbers, and encoded in a [GLTFAccessor] as "VEC3".
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT4" value="6" enum="GLTFObjectModelType">
+ Object model type "float4". Represented in the glTF JSON as an array of four numbers, and encoded in a [GLTFAccessor] as "VEC4".
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT2X2" value="7" enum="GLTFObjectModelType">
+ Object model type "float2x2". Represented in the glTF JSON as an array of four numbers, and encoded in a [GLTFAccessor] as "MAT2".
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT3X3" value="8" enum="GLTFObjectModelType">
+ Object model type "float3x3". Represented in the glTF JSON as an array of nine numbers, and encoded in a [GLTFAccessor] as "MAT3".
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT4X4" value="9" enum="GLTFObjectModelType">
+ Object model type "float4x4". Represented in the glTF JSON as an array of sixteen numbers, and encoded in a [GLTFAccessor] as "MAT4".
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_INT" value="10" enum="GLTFObjectModelType">
+ Object model type "int". Represented in the glTF JSON as a number, and encoded in a [GLTFAccessor] as "SCALAR". The range of values is limited to signed integers. For [code]KHR_interactivity[/code], only 32-bit integers are supported.
+ </constant>
+ </constants>
+</class>
diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml
index de7ec2a4ca..376c3c89f5 100644
--- a/modules/gltf/doc_classes/GLTFState.xml
+++ b/modules/gltf/doc_classes/GLTFState.xml
@@ -17,7 +17,7 @@
<param index="0" name="extension_name" type="String" />
<param index="1" name="required" type="bool" />
<description>
- Appends an extension to the list of extensions used by this glTF file during serialization. If [param required] is true, the extension will also be added to the list of required extensions. Do not run this in [method GLTFDocumentExtension._export_post], as that stage is too late to add extensions. The final list is sorted alphabetically.
+ Appends an extension to the list of extensions used by this glTF file during serialization. If [param required] is [code]true[/code], the extension will also be added to the list of required extensions. Do not run this in [method GLTFDocumentExtension._export_post], as that stage is too late to add extensions. The final list is sorted alphabetically.
</description>
</method>
<method name="append_data_to_buffers">
@@ -25,7 +25,7 @@
<param index="0" name="data" type="PackedByteArray" />
<param index="1" name="deduplication" type="bool" />
<description>
- Appends the given byte array data to the buffers and creates a [GLTFBufferView] for it. The index of the destination [GLTFBufferView] is returned. If [param deduplication] is true, the buffers will first be searched for duplicate data, otherwise new bytes will always be appended.
+ Appends the given byte array data to the buffers and creates a [GLTFBufferView] for it. The index of the destination [GLTFBufferView] is returned. If [param deduplication] is [code]true[/code], the buffers will first be searched for duplicate data, otherwise new bytes will always be appended.
</description>
</method>
<method name="append_gltf_node">
@@ -35,7 +35,7 @@
<param index="2" name="parent_node_index" type="int" />
<description>
Append the given [GLTFNode] to the state, and return its new index. This can be used to export one Godot node as multiple glTF nodes, or inject new glTF nodes at import time. On import, this must be called before [method GLTFDocumentExtension._generate_scene_node] finishes for the parent node. On export, this must be called before [method GLTFDocumentExtension._export_node] runs for the parent node.
- The [param godot_scene_node] parameter is the Godot scene node that corresponds to this glTF node. This is highly recommended to be set to a valid node, but may be null if there is no corresponding Godot scene node. One Godot scene node may be used for multiple glTF nodes, so if exporting multiple glTF nodes for one Godot scene node, use the same Godot scene node for each.
+ The [param godot_scene_node] parameter is the Godot scene node that corresponds to this glTF node. This is highly recommended to be set to a valid node, but may be [code]null[/code] if there is no corresponding Godot scene node. One Godot scene node may be used for multiple glTF nodes, so if exporting multiple glTF nodes for one Godot scene node, use the same Godot scene node for each.
The [param parent_node_index] parameter is the index of the parent [GLTFNode] in the state. If [code]-1[/code], the node will be a root node, otherwise the new node will be added to the parent's list of children. The index will also be written to the [member GLTFNode.parent] property of the new node.
</description>
</method>
@@ -49,7 +49,7 @@
<param index="0" name="extension_name" type="StringName" />
<description>
Gets additional arbitrary data in this [GLTFState] instance. This can be used to keep per-file state data in [GLTFDocumentExtension] classes, which is important because they are stateless.
- The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is null.
+ The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is [code]null[/code].
</description>
</method>
<method name="get_animation_player">
diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
index 022d2e4477..872054ec2e 100644
--- a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
+++ b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
@@ -88,7 +88,7 @@ void SceneExporterGLTFPlugin::_popup_gltf_export_dialog() {
}
_file_dialog->set_current_file(filename + String(".gltf"));
// Generate and refresh the export settings.
- _export_settings->generate_property_list(_gltf_document);
+ _export_settings->generate_property_list(_gltf_document, root);
_settings_inspector->edit(nullptr);
_settings_inspector->edit(_export_settings.ptr());
// Show the file dialog.
diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp
index 511da078d8..c14e92c3a0 100644
--- a/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp
+++ b/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp
@@ -129,7 +129,7 @@ String get_friendly_config_prefix(Ref<GLTFDocumentExtension> p_extension) {
}
// Run this before popping up the export settings, because the extensions may have changed.
-void EditorSceneExporterGLTFSettings::generate_property_list(Ref<GLTFDocument> p_document) {
+void EditorSceneExporterGLTFSettings::generate_property_list(Ref<GLTFDocument> p_document, Node *p_root) {
_property_list.clear();
_document = p_document;
String image_format_hint_string = "None,PNG,JPEG";
diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_settings.h b/modules/gltf/editor/editor_scene_exporter_gltf_settings.h
index 898cddfd68..aa0e54078d 100644
--- a/modules/gltf/editor/editor_scene_exporter_gltf_settings.h
+++ b/modules/gltf/editor/editor_scene_exporter_gltf_settings.h
@@ -55,7 +55,7 @@ protected:
bool _get_extension_setting(const String &p_name_str, Variant &r_ret) const;
public:
- void generate_property_list(Ref<GLTFDocument> p_document);
+ void generate_property_list(Ref<GLTFDocument> p_document, Node *p_root = nullptr);
String get_copyright() const;
void set_copyright(const String &p_copyright);
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index 542e00e560..2db46adef4 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -319,6 +319,8 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
state->set_import_as_skeleton_bones(true);
}
state->set_scene_name(blend_basename);
+ state->set_extract_path(p_path.get_base_dir());
+ state->set_extract_prefix(blend_basename);
err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, base_dir);
if (err != OK) {
if (r_err) {
diff --git a/modules/gltf/extensions/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp
index 6e611762b6..0806eee6bf 100644
--- a/modules/gltf/extensions/gltf_document_extension.cpp
+++ b/modules/gltf/extensions/gltf_document_extension.cpp
@@ -38,6 +38,7 @@ void GLTFDocumentExtension::_bind_methods() {
GDVIRTUAL_BIND(_parse_image_data, "state", "image_data", "mime_type", "ret_image");
GDVIRTUAL_BIND(_get_image_file_extension);
GDVIRTUAL_BIND(_parse_texture_json, "state", "texture_json", "ret_gltf_texture");
+ GDVIRTUAL_BIND(_import_object_model_property, "state", "split_json_pointer", "partial_paths");
GDVIRTUAL_BIND(_import_post_parse, "state");
GDVIRTUAL_BIND(_import_pre_generate, "state");
GDVIRTUAL_BIND(_generate_scene_node, "state", "gltf_node", "scene_parent");
@@ -48,6 +49,7 @@ void GLTFDocumentExtension::_bind_methods() {
GDVIRTUAL_BIND(_convert_scene_node, "state", "gltf_node", "scene_node");
GDVIRTUAL_BIND(_export_post_convert, "state", "root");
GDVIRTUAL_BIND(_export_preserialize, "state");
+ GDVIRTUAL_BIND(_export_object_model_property, "state", "node_path", "godot_node", "gltf_node_index", "target_object", "target_depth");
GDVIRTUAL_BIND(_get_saveable_image_formats);
GDVIRTUAL_BIND(_serialize_image_to_bytes, "state", "image", "image_dict", "image_format", "lossy_quality");
GDVIRTUAL_BIND(_save_image_at_path, "state", "image", "file_path", "image_format", "lossy_quality");
@@ -100,6 +102,13 @@ Error GLTFDocumentExtension::parse_texture_json(Ref<GLTFState> p_state, const Di
return err;
}
+Ref<GLTFObjectModelProperty> GLTFDocumentExtension::import_object_model_property(Ref<GLTFState> p_state, const PackedStringArray &p_split_json_pointer, const TypedArray<NodePath> &p_partial_paths) {
+ Ref<GLTFObjectModelProperty> ret;
+ ERR_FAIL_COND_V(p_state.is_null(), ret);
+ GDVIRTUAL_CALL(_import_object_model_property, p_state, p_split_json_pointer, p_partial_paths, ret);
+ return ret;
+}
+
Error GLTFDocumentExtension::import_post_parse(Ref<GLTFState> p_state) {
ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
@@ -169,6 +178,15 @@ Error GLTFDocumentExtension::export_preserialize(Ref<GLTFState> p_state) {
return err;
}
+Ref<GLTFObjectModelProperty> GLTFDocumentExtension::export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index, const Object *p_target_object, int p_target_depth) {
+ Ref<GLTFObjectModelProperty> ret;
+ ERR_FAIL_COND_V(p_state.is_null(), ret);
+ ERR_FAIL_NULL_V(p_godot_node, ret);
+ ERR_FAIL_NULL_V(p_target_object, ret);
+ GDVIRTUAL_CALL(_export_object_model_property, p_state, p_node_path, p_godot_node, p_gltf_node_index, p_target_object, p_target_depth, ret);
+ return ret;
+}
+
Vector<String> GLTFDocumentExtension::get_saveable_image_formats() {
Vector<String> ret;
GDVIRTUAL_CALL(_get_saveable_image_formats, ret);
diff --git a/modules/gltf/extensions/gltf_document_extension.h b/modules/gltf/extensions/gltf_document_extension.h
index b70710e015..a6368ea780 100644
--- a/modules/gltf/extensions/gltf_document_extension.h
+++ b/modules/gltf/extensions/gltf_document_extension.h
@@ -49,6 +49,7 @@ public:
virtual Error parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image);
virtual String get_image_file_extension();
virtual Error parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture);
+ virtual Ref<GLTFObjectModelProperty> import_object_model_property(Ref<GLTFState> p_state, const PackedStringArray &p_split_json_pointer, const TypedArray<NodePath> &p_partial_paths);
virtual Error import_post_parse(Ref<GLTFState> p_state);
virtual Error import_pre_generate(Ref<GLTFState> p_state);
virtual Node3D *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent);
@@ -59,6 +60,7 @@ public:
virtual void convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node);
virtual Error export_post_convert(Ref<GLTFState> p_state, Node *p_root);
virtual Error export_preserialize(Ref<GLTFState> p_state);
+ virtual Ref<GLTFObjectModelProperty> export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index, const Object *p_target_object, int p_target_depth);
virtual Vector<String> get_saveable_image_formats();
virtual PackedByteArray serialize_image_to_bytes(Ref<GLTFState> p_state, Ref<Image> p_image, Dictionary p_image_dict, const String &p_image_format, float p_lossy_quality);
virtual Error save_image_at_path(Ref<GLTFState> p_state, Ref<Image> p_image, const String &p_file_path, const String &p_image_format, float p_lossy_quality);
@@ -73,6 +75,7 @@ public:
GDVIRTUAL4R(Error, _parse_image_data, Ref<GLTFState>, PackedByteArray, String, Ref<Image>);
GDVIRTUAL0R(String, _get_image_file_extension);
GDVIRTUAL3R(Error, _parse_texture_json, Ref<GLTFState>, Dictionary, Ref<GLTFTexture>);
+ GDVIRTUAL3R(Ref<GLTFObjectModelProperty>, _import_object_model_property, Ref<GLTFState>, PackedStringArray, TypedArray<NodePath>);
GDVIRTUAL1R(Error, _import_post_parse, Ref<GLTFState>);
GDVIRTUAL1R(Error, _import_pre_generate, Ref<GLTFState>);
GDVIRTUAL3R(Node3D *, _generate_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *);
@@ -83,6 +86,7 @@ public:
GDVIRTUAL3(_convert_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *);
GDVIRTUAL2R(Error, _export_post_convert, Ref<GLTFState>, Node *);
GDVIRTUAL1R(Error, _export_preserialize, Ref<GLTFState>);
+ GDVIRTUAL6R(Ref<GLTFObjectModelProperty>, _export_object_model_property, Ref<GLTFState>, NodePath, const Node *, GLTFNodeIndex, const Object *, int);
GDVIRTUAL0R(Vector<String>, _get_saveable_image_formats);
GDVIRTUAL5R(PackedByteArray, _serialize_image_to_bytes, Ref<GLTFState>, Ref<Image>, Dictionary, String, float);
GDVIRTUAL5R(Error, _save_image_at_path, Ref<GLTFState>, Ref<Image>, String, String, float);
diff --git a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
index cde30bce18..b5edd35ad5 100644
--- a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
+++ b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
@@ -63,6 +63,7 @@ Error GLTFDocumentExtensionConvertImporterMesh::import_post(Ref<GLTFState> p_sta
mesh_instance_node_3d->set_mesh(array_mesh);
mesh_instance_node_3d->set_skin(importer_mesh_3d->get_skin());
mesh_instance_node_3d->set_skeleton_path(importer_mesh_3d->get_skeleton_path());
+ mesh_instance_node_3d->set_visible(importer_mesh_3d->is_visible());
node->replace_by(mesh_instance_node_3d);
_copy_meta(importer_mesh_3d, mesh_instance_node_3d);
_copy_meta(mesh.ptr(), array_mesh.ptr());
diff --git a/modules/gltf/extensions/gltf_light.cpp b/modules/gltf/extensions/gltf_light.cpp
index f6e91c1635..2bdcab2f0c 100644
--- a/modules/gltf/extensions/gltf_light.cpp
+++ b/modules/gltf/extensions/gltf_light.cpp
@@ -30,6 +30,7 @@
#include "gltf_light.h"
+#include "../structures/gltf_object_model_property.h"
#include "scene/3d/light_3d.h"
void GLTFLight::_bind_methods() {
@@ -62,6 +63,21 @@ void GLTFLight::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "outer_cone_angle"), "set_outer_cone_angle", "get_outer_cone_angle"); // float
}
+void GLTFLight::set_cone_inner_attenuation_conversion_expressions(Ref<GLTFObjectModelProperty> &r_obj_model_prop) {
+ // Expression to convert glTF innerConeAngle to Godot spot_angle_attenuation.
+ Ref<Expression> gltf_to_godot_expr;
+ gltf_to_godot_expr.instantiate();
+ PackedStringArray gltf_to_godot_args = { "inner_cone_angle" };
+ gltf_to_godot_expr->parse("0.2 / (1.0 - inner_cone_angle / spot_angle) - 0.1", gltf_to_godot_args);
+ r_obj_model_prop->set_gltf_to_godot_expression(gltf_to_godot_expr);
+ // Expression to convert Godot spot_angle_attenuation to glTF innerConeAngle.
+ Ref<Expression> godot_to_gltf_expr;
+ godot_to_gltf_expr.instantiate();
+ PackedStringArray godot_to_gltf_args = { "godot_spot_angle_att" };
+ godot_to_gltf_expr->parse("spot_angle * maxf(0.0, 1.0 - (0.2 / (0.1 + godot_spot_angle_att)))", godot_to_gltf_args);
+ r_obj_model_prop->set_godot_to_gltf_expression(godot_to_gltf_expr);
+}
+
Color GLTFLight::get_color() {
return color;
}
diff --git a/modules/gltf/extensions/gltf_light.h b/modules/gltf/extensions/gltf_light.h
index e0894fc8c6..3d522bd174 100644
--- a/modules/gltf/extensions/gltf_light.h
+++ b/modules/gltf/extensions/gltf_light.h
@@ -33,6 +33,7 @@
#include "core/io/resource.h"
+class GLTFObjectModelProperty;
class Light3D;
// https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual
@@ -54,6 +55,8 @@ private:
Dictionary additional_data;
public:
+ static void set_cone_inner_attenuation_conversion_expressions(Ref<GLTFObjectModelProperty> &r_obj_model_prop);
+
Color get_color();
void set_color(Color p_color);
diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
index 5c26a1686b..512f25a216 100644
--- a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
+++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
@@ -31,8 +31,11 @@
#include "gltf_document_extension_physics.h"
#include "scene/3d/physics/area_3d.h"
+#include "scene/3d/physics/rigid_body_3d.h"
#include "scene/3d/physics/static_body_3d.h"
+using GLTFShapeIndex = int64_t;
+
// Import process.
Error GLTFDocumentExtensionPhysics::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) {
if (!p_extensions.has("OMI_collider") && !p_extensions.has("OMI_physics_body") && !p_extensions.has("OMI_physics_shape")) {
@@ -105,6 +108,7 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state
Array state_shapes = p_state->get_additional_data(StringName("GLTFPhysicsShapes"));
ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "glTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ").");
p_gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShape"), state_shapes[node_shape_index]);
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShapeIndex"), node_shape_index);
} else {
// If this node is a collider but does not have a collider
// shape, then it only serves to combine together shapes.
@@ -119,6 +123,7 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state
Array state_shapes = p_state->get_additional_data(StringName("GLTFPhysicsShapes"));
ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "glTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ").");
p_gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShape"), state_shapes[node_shape_index]);
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShapeIndex"), node_shape_index);
} else {
// If this node is a trigger but does not have a trigger shape,
// then it's a trigger body, what Godot calls an Area3D node.
@@ -129,8 +134,8 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state
}
// If this node defines explicit member shape nodes, save this information.
if (node_trigger.has("nodes")) {
- Array node_trigger_nodes = node_trigger["nodes"];
- p_gltf_node->set_additional_data(StringName("GLTFPhysicsCompoundTriggerNodes"), node_trigger_nodes);
+ Array compound_trigger_nodes = node_trigger["nodes"];
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsCompoundTriggerNodes"), compound_trigger_nodes);
}
}
if (physics_body_ext.has("motion") || physics_body_ext.has("type")) {
@@ -140,6 +145,144 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state
return OK;
}
+bool _will_gltf_shape_become_subnode(Ref<GLTFState> p_state, const Ref<GLTFNode> p_gltf_node, GLTFNodeIndex p_gltf_node_index) {
+ if (p_gltf_node->has_additional_data(StringName("GLTFPhysicsBody"))) {
+ return true;
+ }
+ const TypedArray<GLTFNode> state_gltf_nodes = p_state->get_nodes();
+ const GLTFNodeIndex parent_index = p_gltf_node->get_parent();
+ if (parent_index == -1 || parent_index >= state_gltf_nodes.size()) {
+ return true;
+ }
+ const Ref<GLTFNode> parent_gltf_node = state_gltf_nodes[parent_index];
+ const Variant parent_body_maybe = parent_gltf_node->get_additional_data(StringName("GLTFPhysicsBody"));
+ if (parent_body_maybe.get_type() != Variant::NIL) {
+ Ref<GLTFPhysicsBody> parent_body = parent_body_maybe;
+ // If the parent matches the triggerness, then this node will be generated as a shape (CollisionShape3D).
+ // Otherwise, if there is a mismatch, a body will be generated for this node, and a subnode will also be generated for the shape.
+ if (parent_body->get_body_type() == "trigger") {
+ return p_gltf_node->has_additional_data(StringName("GLTFPhysicsColliderShape"));
+ } else {
+ return p_gltf_node->has_additional_data(StringName("GLTFPhysicsTriggerShape"));
+ }
+ }
+ if (parent_gltf_node->has_additional_data(StringName("GLTFPhysicsColliderShape"))) {
+ return false;
+ }
+ if (parent_gltf_node->has_additional_data(StringName("GLTFPhysicsTriggerShape"))) {
+ return false;
+ }
+ Variant compound_trigger_maybe = parent_gltf_node->has_additional_data(StringName("GLTFPhysicsCompoundTriggerNodes"));
+ if (compound_trigger_maybe.get_type() != Variant::NIL) {
+ Array compound_trigger_nodes = compound_trigger_maybe;
+ // Remember, JSON only has numbers, not integers, so must cast to double.
+ return !compound_trigger_nodes.has((double)p_gltf_node_index);
+ }
+ return true;
+}
+
+NodePath _get_scene_node_path_for_shape_index(Ref<GLTFState> p_state, const GLTFNodeIndex p_shape_index) {
+ TypedArray<GLTFNode> state_gltf_nodes = p_state->get_nodes();
+ for (GLTFNodeIndex node_index = 0; node_index < state_gltf_nodes.size(); node_index++) {
+ const Ref<GLTFNode> gltf_node = state_gltf_nodes[node_index];
+ ERR_CONTINUE(gltf_node.is_null());
+ // Check if this node has a shape index and if it matches the one we are looking for.
+ Variant shape_index_maybe = gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShapeIndex"));
+ if (shape_index_maybe.get_type() != Variant::INT) {
+ shape_index_maybe = gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShapeIndex"));
+ if (shape_index_maybe.get_type() != Variant::INT) {
+ continue;
+ }
+ }
+ const GLTFShapeIndex shape_index = shape_index_maybe;
+ if (shape_index != p_shape_index) {
+ continue;
+ }
+ NodePath node_path = gltf_node->get_scene_node_path(p_state);
+ // At this point, we have found a node with the shape index we were looking for.
+ if (_will_gltf_shape_become_subnode(p_state, gltf_node, node_index)) {
+ Vector<StringName> sname_path = node_path.get_names();
+ sname_path.append(gltf_node->get_name() + "Shape");
+ node_path = NodePath(sname_path, false);
+ }
+ return node_path;
+ }
+ return NodePath();
+}
+
+Ref<GLTFObjectModelProperty> GLTFDocumentExtensionPhysics::import_object_model_property(Ref<GLTFState> p_state, const PackedStringArray &p_split_json_pointer, const TypedArray<NodePath> &p_partial_paths) {
+ Ref<GLTFObjectModelProperty> ret;
+ if (p_split_json_pointer.size() != 6) {
+ // The only properties this class cares about are exactly 6 levels deep.
+ return ret;
+ }
+ ret.instantiate();
+ const String &prop_name = p_split_json_pointer[5];
+ if (p_split_json_pointer[0] == "extensions" && p_split_json_pointer[2] == "shapes") {
+ if (p_split_json_pointer[1] == "OMI_physics_shape" || p_split_json_pointer[1] == "KHR_collision_shapes") {
+ const GLTFNodeIndex shape_index = p_split_json_pointer[3].to_int();
+ NodePath node_path = _get_scene_node_path_for_shape_index(p_state, shape_index);
+ if (node_path.is_empty()) {
+ return ret;
+ }
+ String godot_prop_name = prop_name;
+ if (prop_name == "size") {
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (prop_name == "height" || prop_name == "radius") {
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (prop_name == "radiusBottom" || prop_name == "radiusTop") {
+ godot_prop_name = "radius";
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else {
+ // Not something we handle, return without appending a NodePath.
+ return ret;
+ }
+ // Example: `A/B/C/CollisionShape3D:shape:radius`.
+ Vector<StringName> subnames;
+ subnames.append("shape");
+ subnames.append(godot_prop_name);
+ node_path = NodePath(node_path.get_names(), subnames, false);
+ ret->append_node_path(node_path);
+ }
+ } else if (p_split_json_pointer[0] == "nodes" && p_split_json_pointer[2] == "extensions" && p_split_json_pointer[4] == "motion") {
+ if (p_split_json_pointer[3] == "OMI_physics_body" || p_split_json_pointer[3] == "KHR_physics_rigid_bodies") {
+ const GLTFNodeIndex node_index = p_split_json_pointer[1].to_int();
+ const TypedArray<GLTFNode> all_gltf_nodes = p_state->get_nodes();
+ ERR_FAIL_INDEX_V_MSG(node_index, all_gltf_nodes.size(), ret, "GLTF Physics: The node index " + itos(node_index) + " is not in the state nodes (size: " + itos(all_gltf_nodes.size()) + ").");
+ const Ref<GLTFNode> gltf_node = all_gltf_nodes[node_index];
+ NodePath node_path;
+ if (p_partial_paths.is_empty()) {
+ node_path = gltf_node->get_scene_node_path(p_state);
+ } else {
+ // The path is already computed for us, just grab it.
+ node_path = p_partial_paths[0];
+ }
+ if (prop_name == "mass") {
+ ret->append_path_to_property(node_path, "mass");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (prop_name == "linearVelocity") {
+ ret->append_path_to_property(node_path, "linear_velocity");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (prop_name == "angularVelocity") {
+ ret->append_path_to_property(node_path, "angular_velocity");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (prop_name == "centerOfMass") {
+ ret->append_path_to_property(node_path, "center_of_mass");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (prop_name == "inertiaDiagonal") {
+ ret->append_path_to_property(node_path, "inertia");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (prop_name == "inertiaOrientation") {
+ WARN_PRINT("GLTF Physics: The 'inertiaOrientation' property is not supported by Godot.");
+ } else {
+ // Not something we handle, return without appending a NodePath.
+ return ret;
+ }
+ }
+ }
+ return ret;
+}
+
void _setup_shape_mesh_resource_from_index_if_needed(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_gltf_shape) {
GLTFMeshIndex shape_mesh_index = p_gltf_shape->get_mesh_index();
if (shape_mesh_index == -1) {
@@ -434,24 +577,126 @@ Array _get_or_create_state_shapes_in_state(Ref<GLTFState> p_state) {
return state_shapes;
}
-Dictionary _export_node_shape(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_physics_shape) {
+GLTFShapeIndex _export_node_shape(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_physics_shape) {
Array state_shapes = _get_or_create_state_shapes_in_state(p_state);
- int size = state_shapes.size();
+ GLTFShapeIndex size = state_shapes.size();
Dictionary shape_property;
Dictionary shape_dict = p_physics_shape->to_dictionary();
- for (int i = 0; i < size; i++) {
+ for (GLTFShapeIndex i = 0; i < size; i++) {
Dictionary other = state_shapes[i];
if (other == shape_dict) {
// De-duplication: If we already have an identical shape,
// set the shape index to the existing one and return.
- shape_property["shape"] = i;
- return shape_property;
+ return i;
}
}
// If we don't have an identical shape, add it to the array.
state_shapes.push_back(shape_dict);
- shape_property["shape"] = size;
- return shape_property;
+ return size;
+}
+
+Error GLTFDocumentExtensionPhysics::export_preserialize(Ref<GLTFState> p_state) {
+ // Note: Need to do _export_node_shape before exporting animations, so export_node is too late.
+ TypedArray<GLTFNode> state_gltf_nodes = p_state->get_nodes();
+ for (Ref<GLTFNode> gltf_node : state_gltf_nodes) {
+ Ref<GLTFPhysicsShape> collider_shape = gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape"));
+ if (collider_shape.is_valid()) {
+ GLTFShapeIndex collider_shape_index = _export_node_shape(p_state, collider_shape);
+ gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShapeIndex"), collider_shape_index);
+ }
+ Ref<GLTFPhysicsShape> trigger_shape = gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape"));
+ if (trigger_shape.is_valid()) {
+ GLTFShapeIndex trigger_shape_index = _export_node_shape(p_state, trigger_shape);
+ gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShapeIndex"), trigger_shape_index);
+ }
+ }
+ return OK;
+}
+
+Ref<GLTFObjectModelProperty> GLTFDocumentExtensionPhysics::export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index, const Object *p_target_object, int p_target_depth) {
+ Ref<GLTFObjectModelProperty> ret;
+ const Vector<StringName> &path_subnames = p_node_path.get_subnames();
+ if (path_subnames.is_empty()) {
+ return ret;
+ }
+ ret.instantiate();
+ const StringName &node_prop = path_subnames[0];
+ if (Object::cast_to<RigidBody3D>(p_target_object)) {
+ if (path_subnames.size() != 1) {
+ return ret;
+ }
+ // Example: `/nodes/0/extensions/OMI_physics_body/motion/mass`
+ PackedStringArray split_json_pointer;
+ split_json_pointer.append("nodes");
+ split_json_pointer.append(itos(p_gltf_node_index));
+ split_json_pointer.append("extensions");
+ split_json_pointer.append("OMI_physics_body");
+ split_json_pointer.append("motion");
+ if (node_prop == StringName("mass")) {
+ split_json_pointer.append("mass");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (node_prop == StringName("linear_velocity")) {
+ split_json_pointer.append("linearVelocity");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (node_prop == StringName("angular_velocity")) {
+ split_json_pointer.append("angularVelocity");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (node_prop == StringName("center_of_mass")) {
+ split_json_pointer.append("centerOfMass");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (node_prop == StringName("inertia")) {
+ split_json_pointer.append("inertiaDiagonal");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else {
+ // Not something we handle, return without setting the JSON pointer.
+ return ret;
+ }
+ ret->set_json_pointers({ split_json_pointer });
+ } else if (Object::cast_to<CollisionShape3D>(p_godot_node)) {
+ if (path_subnames.size() != 2) {
+ return ret;
+ }
+ // Example: `/extensions/OMI_physics_shape/shapes/0/box/size`
+ PackedStringArray split_json_pointer;
+ split_json_pointer.append("extensions");
+ split_json_pointer.append("OMI_physics_shape");
+ split_json_pointer.append("shapes");
+ TypedArray<GLTFNode> state_gltf_nodes = p_state->get_nodes();
+ ERR_FAIL_INDEX_V(p_gltf_node_index, state_gltf_nodes.size(), ret);
+ Ref<GLTFNode> gltf_node = state_gltf_nodes[p_gltf_node_index];
+ Variant shape_index_maybe = gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShapeIndex"));
+ String shape_type;
+ if (shape_index_maybe.get_type() == Variant::INT) {
+ Ref<GLTFPhysicsShape> collider_shape = gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape"));
+ shape_type = collider_shape->get_shape_type();
+ } else {
+ shape_index_maybe = gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShapeIndex"));
+ if (shape_index_maybe.get_type() == Variant::INT) {
+ Ref<GLTFPhysicsShape> trigger_shape = gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape"));
+ shape_type = trigger_shape->get_shape_type();
+ }
+ }
+ ERR_FAIL_COND_V(shape_index_maybe.get_type() != Variant::INT, ret);
+ GLTFShapeIndex shape_index = shape_index_maybe;
+ split_json_pointer.append(itos(shape_index));
+ split_json_pointer.append(shape_type);
+ const StringName &shape_prop = path_subnames[1];
+ if (shape_prop == StringName("size")) {
+ split_json_pointer.append("size");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (shape_prop == StringName("radius")) {
+ split_json_pointer.append("radius");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (shape_prop == StringName("height")) {
+ split_json_pointer.append("height");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else {
+ // Not something we handle, return without setting the JSON pointer.
+ return ret;
+ }
+ ret->set_json_pointers({ split_json_pointer });
+ }
+ return ret;
}
Error GLTFDocumentExtensionPhysics::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_node_json, Node *p_node) {
@@ -465,13 +710,16 @@ Error GLTFDocumentExtensionPhysics::export_node(Ref<GLTFState> p_state, Ref<GLTF
trigger_property["nodes"] = compound_trigger_nodes;
}
}
- Ref<GLTFPhysicsShape> collider_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape"));
- if (collider_shape.is_valid()) {
- physics_body_ext["collider"] = _export_node_shape(p_state, collider_shape);
+ Variant collider_shape_index = p_gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShapeIndex"));
+ if (collider_shape_index.get_type() == Variant::INT) {
+ Dictionary collider_dict;
+ collider_dict["shape"] = collider_shape_index;
+ physics_body_ext["collider"] = collider_dict;
}
- Ref<GLTFPhysicsShape> trigger_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape"));
- if (trigger_shape.is_valid()) {
- physics_body_ext["trigger"] = _export_node_shape(p_state, trigger_shape);
+ Variant trigger_shape_index = p_gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShapeIndex"));
+ if (trigger_shape_index.get_type() == Variant::INT) {
+ Dictionary trigger_dict = physics_body_ext.get_or_add("trigger", {});
+ trigger_dict["shape"] = trigger_shape_index;
}
if (!physics_body_ext.is_empty()) {
Dictionary node_extensions = r_node_json["extensions"];
diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.h b/modules/gltf/extensions/physics/gltf_document_extension_physics.h
index 3d5027c0df..76a60a6375 100644
--- a/modules/gltf/extensions/physics/gltf_document_extension_physics.h
+++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.h
@@ -43,9 +43,12 @@ public:
Error import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) override;
Vector<String> get_supported_extensions() override;
Error parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions) override;
+ Ref<GLTFObjectModelProperty> import_object_model_property(Ref<GLTFState> p_state, const PackedStringArray &p_split_json_pointer, const TypedArray<NodePath> &p_partial_paths) override;
Node3D *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) override;
// Export process.
void convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node) override;
+ Error export_preserialize(Ref<GLTFState> p_state) override;
+ Ref<GLTFObjectModelProperty> export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index, const Object *p_target_object, int p_target_depth) override;
Error export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_node_json, Node *p_scene_node) override;
};
diff --git a/modules/gltf/extensions/physics/gltf_physics_body.cpp b/modules/gltf/extensions/physics/gltf_physics_body.cpp
index c11aa5d2ff..7c40f96e0a 100644
--- a/modules/gltf/extensions/physics/gltf_physics_body.cpp
+++ b/modules/gltf/extensions/physics/gltf_physics_body.cpp
@@ -193,9 +193,6 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_node(const CollisionObject3D *p_body_
physics_body->angular_velocity = body->get_angular_velocity();
physics_body->center_of_mass = body->get_center_of_mass();
physics_body->inertia_diagonal = body->get_inertia();
- if (body->get_center_of_mass() != Vector3()) {
- WARN_PRINT("GLTFPhysicsBody: This rigid body has a center of mass offset from the origin, which will be ignored when exporting to glTF.");
- }
if (cast_to<VehicleBody3D>(p_body_node)) {
physics_body->body_type = PhysicsBodyType::VEHICLE;
} else {
diff --git a/modules/gltf/gltf_defines.h b/modules/gltf/gltf_defines.h
index c1918e5908..4d88f7c342 100644
--- a/modules/gltf/gltf_defines.h
+++ b/modules/gltf/gltf_defines.h
@@ -43,6 +43,7 @@ class GLTFDocumentExtension;
class GLTFLight;
class GLTFMesh;
class GLTFNode;
+class GLTFObjectModelProperty;
class GLTFSkeleton;
class GLTFSkin;
class GLTFSpecGloss;
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 0a487430a3..aa482615da 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -69,6 +69,10 @@
#include <stdlib.h>
#include <cstdint>
+constexpr int COMPONENT_COUNT_FOR_ACCESSOR_TYPE[7] = {
+ 1, 2, 3, 4, 4, 9, 16
+};
+
static void _attach_extras_to_meta(const Dictionary &p_extras, Ref<Resource> p_node) {
if (!p_extras.is_empty()) {
p_node->set_meta("extras", p_extras);
@@ -1013,7 +1017,7 @@ Error GLTFDocument::_parse_accessors(Ref<GLTFState> p_state) {
accessor.instantiate();
ERR_FAIL_COND_V(!d.has("componentType"), ERR_PARSE_ERROR);
- accessor->component_type = d["componentType"];
+ accessor->component_type = (GLTFAccessor::GLTFComponentType)(int32_t)d["componentType"];
ERR_FAIL_COND_V(!d.has("count"), ERR_PARSE_ERROR);
accessor->count = d["count"];
ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR);
@@ -1050,7 +1054,7 @@ Error GLTFDocument::_parse_accessors(Ref<GLTFState> p_state) {
ERR_FAIL_COND_V(!si.has("bufferView"), ERR_PARSE_ERROR);
accessor->sparse_indices_buffer_view = si["bufferView"];
ERR_FAIL_COND_V(!si.has("componentType"), ERR_PARSE_ERROR);
- accessor->sparse_indices_component_type = si["componentType"];
+ accessor->sparse_indices_component_type = (GLTFAccessor::GLTFComponentType)(int32_t)si["componentType"];
if (si.has("byteOffset")) {
accessor->sparse_indices_byte_offset = si["byteOffset"];
@@ -1082,31 +1086,39 @@ double GLTFDocument::_filter_number(double p_float) {
return (double)(float)p_float;
}
-String GLTFDocument::_get_component_type_name(const uint32_t p_component) {
+String GLTFDocument::_get_component_type_name(const GLTFAccessor::GLTFComponentType p_component) {
switch (p_component) {
- case GLTFDocument::COMPONENT_TYPE_BYTE:
+ case GLTFAccessor::COMPONENT_TYPE_NONE:
+ return "None";
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_BYTE:
return "Byte";
- case GLTFDocument::COMPONENT_TYPE_UNSIGNED_BYTE:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_BYTE:
return "UByte";
- case GLTFDocument::COMPONENT_TYPE_SHORT:
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_SHORT:
return "Short";
- case GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT:
return "UShort";
- case GLTFDocument::COMPONENT_TYPE_INT:
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_INT:
return "Int";
- case GLTFDocument::COMPONENT_TYPE_FLOAT:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_INT:
+ return "UInt";
+ case GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT:
return "Float";
+ case GLTFAccessor::COMPONENT_TYPE_DOUBLE_FLOAT:
+ return "Double";
+ case GLTFAccessor::COMPONENT_TYPE_HALF_FLOAT:
+ return "Half";
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_LONG:
+ return "Long";
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_LONG:
+ return "ULong";
}
return "<Error>";
}
-Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_src, const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_type, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex, GLTFBufferViewIndex &r_accessor, const bool p_for_vertex_indices) {
- const int component_count_for_type[7] = {
- 1, 2, 3, 4, 4, 9, 16
- };
-
- const int component_count = component_count_for_type[p_accessor_type];
+Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_src, const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, const GLTFAccessor::GLTFComponentType p_component_type, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex, GLTFBufferViewIndex &r_accessor, const bool p_for_vertex_indices) {
+ const int component_count = COMPONENT_COUNT_FOR_ACCESSOR_TYPE[p_accessor_type];
const int component_size = _get_component_type_size(p_component_type);
ERR_FAIL_COND_V(component_size == 0, FAILED);
@@ -1114,8 +1126,8 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
int skip_bytes = 0;
//special case of alignments, as described in spec
switch (p_component_type) {
- case COMPONENT_TYPE_BYTE:
- case COMPONENT_TYPE_UNSIGNED_BYTE: {
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_BYTE:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_BYTE: {
if (p_accessor_type == GLTFAccessor::TYPE_MAT2) {
skip_every = 2;
skip_bytes = 2;
@@ -1125,8 +1137,8 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
skip_bytes = 1;
}
} break;
- case COMPONENT_TYPE_SHORT:
- case COMPONENT_TYPE_UNSIGNED_SHORT: {
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_SHORT:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT: {
if (p_accessor_type == GLTFAccessor::TYPE_MAT3) {
skip_every = 6;
skip_bytes = 4;
@@ -1161,7 +1173,10 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
}
switch (p_component_type) {
- case COMPONENT_TYPE_BYTE: {
+ case GLTFAccessor::COMPONENT_TYPE_NONE: {
+ ERR_FAIL_V_MSG(ERR_INVALID_DATA, "glTF: Failed to encode buffer view, component type not set.");
+ }
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_BYTE: {
Vector<int8_t> buffer;
buffer.resize(p_count * component_count);
int32_t dst_i = 0;
@@ -1185,7 +1200,7 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(int8_t));
bv->byte_length = buffer.size() * sizeof(int8_t);
} break;
- case COMPONENT_TYPE_UNSIGNED_BYTE: {
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_BYTE: {
Vector<uint8_t> buffer;
buffer.resize(p_count * component_count);
int32_t dst_i = 0;
@@ -1207,7 +1222,7 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
gltf_buffer.append_array(buffer);
bv->byte_length = buffer.size() * sizeof(uint8_t);
} break;
- case COMPONENT_TYPE_SHORT: {
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_SHORT: {
Vector<int16_t> buffer;
buffer.resize(p_count * component_count);
int32_t dst_i = 0;
@@ -1231,7 +1246,7 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(int16_t));
bv->byte_length = buffer.size() * sizeof(int16_t);
} break;
- case COMPONENT_TYPE_UNSIGNED_SHORT: {
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT: {
Vector<uint16_t> buffer;
buffer.resize(p_count * component_count);
int32_t dst_i = 0;
@@ -1255,8 +1270,28 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(uint16_t));
bv->byte_length = buffer.size() * sizeof(uint16_t);
} break;
- case COMPONENT_TYPE_INT: {
- Vector<int> buffer;
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_INT: {
+ Vector<int32_t> buffer;
+ buffer.resize(p_count * component_count);
+ int32_t dst_i = 0;
+ for (int i = 0; i < p_count; i++) {
+ for (int j = 0; j < component_count; j++) {
+ if (skip_every && j > 0 && (j % skip_every) == 0) {
+ dst_i += skip_bytes;
+ }
+ double d = *p_src;
+ buffer.write[dst_i] = d;
+ p_src++;
+ dst_i++;
+ }
+ }
+ int64_t old_size = gltf_buffer.size();
+ gltf_buffer.resize(old_size + (buffer.size() * sizeof(uint32_t)));
+ memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(uint32_t));
+ bv->byte_length = buffer.size() * sizeof(uint32_t);
+ } break;
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_INT: {
+ Vector<uint32_t> buffer;
buffer.resize(p_count * component_count);
int32_t dst_i = 0;
for (int i = 0; i < p_count; i++) {
@@ -1271,11 +1306,11 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
}
}
int64_t old_size = gltf_buffer.size();
- gltf_buffer.resize(old_size + (buffer.size() * sizeof(int32_t)));
- memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(int32_t));
- bv->byte_length = buffer.size() * sizeof(int32_t);
+ gltf_buffer.resize(old_size + (buffer.size() * sizeof(uint32_t)));
+ memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(uint32_t));
+ bv->byte_length = buffer.size() * sizeof(uint32_t);
} break;
- case COMPONENT_TYPE_FLOAT: {
+ case GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT: {
Vector<float> buffer;
buffer.resize(p_count * component_count);
int32_t dst_i = 0;
@@ -1295,6 +1330,71 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(float));
bv->byte_length = buffer.size() * sizeof(float);
} break;
+ case GLTFAccessor::COMPONENT_TYPE_DOUBLE_FLOAT: {
+ Vector<double> buffer;
+ buffer.resize(p_count * component_count);
+ int32_t dst_i = 0;
+ for (int i = 0; i < p_count; i++) {
+ for (int j = 0; j < component_count; j++) {
+ if (skip_every && j > 0 && (j % skip_every) == 0) {
+ dst_i += skip_bytes;
+ }
+ double d = *p_src;
+ buffer.write[dst_i] = d;
+ p_src++;
+ dst_i++;
+ }
+ }
+ int64_t old_size = gltf_buffer.size();
+ gltf_buffer.resize(old_size + (buffer.size() * sizeof(double)));
+ memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(double));
+ bv->byte_length = buffer.size() * sizeof(double);
+ } break;
+ case GLTFAccessor::COMPONENT_TYPE_HALF_FLOAT: {
+ ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "glTF: Half float not supported yet.");
+ } break;
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_LONG: {
+ Vector<int64_t> buffer;
+ buffer.resize(p_count * component_count);
+ int32_t dst_i = 0;
+ for (int i = 0; i < p_count; i++) {
+ for (int j = 0; j < component_count; j++) {
+ if (skip_every && j > 0 && (j % skip_every) == 0) {
+ dst_i += skip_bytes;
+ }
+ // FIXME: This can result in precision loss because int64_t can store some values that double can't.
+ double d = *p_src;
+ buffer.write[dst_i] = d;
+ p_src++;
+ dst_i++;
+ }
+ }
+ int64_t old_size = gltf_buffer.size();
+ gltf_buffer.resize(old_size + (buffer.size() * sizeof(int64_t)));
+ memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(int64_t));
+ bv->byte_length = buffer.size() * sizeof(int64_t);
+ } break;
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_LONG: {
+ Vector<uint64_t> buffer;
+ buffer.resize(p_count * component_count);
+ int32_t dst_i = 0;
+ for (int i = 0; i < p_count; i++) {
+ for (int j = 0; j < component_count; j++) {
+ if (skip_every && j > 0 && (j % skip_every) == 0) {
+ dst_i += skip_bytes;
+ }
+ // FIXME: This can result in precision loss because int64_t can store some values that double can't.
+ double d = *p_src;
+ buffer.write[dst_i] = d;
+ p_src++;
+ dst_i++;
+ }
+ }
+ int64_t old_size = gltf_buffer.size();
+ gltf_buffer.resize(old_size + (buffer.size() * sizeof(uint64_t)));
+ memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(uint64_t));
+ bv->byte_length = buffer.size() * sizeof(uint64_t);
+ } break;
}
ERR_FAIL_COND_V(buffer_end > bv->byte_length, ERR_INVALID_DATA);
@@ -1309,7 +1409,7 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
return OK;
}
-Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, const GLTFBufferViewIndex p_buffer_view, const int p_skip_every, const int p_skip_bytes, const int p_element_size, const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_count, const int p_component_type, const int p_component_size, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex) {
+Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, const GLTFBufferViewIndex p_buffer_view, const int p_skip_every, const int p_skip_bytes, const int p_element_size, const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_count, const GLTFAccessor::GLTFComponentType p_component_type, const int p_component_size, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex) {
const Ref<GLTFBufferView> bv = p_state->buffer_views[p_buffer_view];
int stride = p_element_size;
@@ -1348,7 +1448,10 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c
double d = 0;
switch (p_component_type) {
- case COMPONENT_TYPE_BYTE: {
+ case GLTFAccessor::COMPONENT_TYPE_NONE: {
+ ERR_FAIL_V_MSG(ERR_INVALID_DATA, "glTF: Failed to decode buffer view, component type not set.");
+ } break;
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_BYTE: {
int8_t b = int8_t(*src);
if (p_normalized) {
d = (double(b) / 128.0);
@@ -1356,7 +1459,7 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c
d = double(b);
}
} break;
- case COMPONENT_TYPE_UNSIGNED_BYTE: {
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_BYTE: {
uint8_t b = *src;
if (p_normalized) {
d = (double(b) / 255.0);
@@ -1364,7 +1467,7 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c
d = double(b);
}
} break;
- case COMPONENT_TYPE_SHORT: {
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_SHORT: {
int16_t s = *(int16_t *)src;
if (p_normalized) {
d = (double(s) / 32768.0);
@@ -1372,7 +1475,7 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c
d = double(s);
}
} break;
- case COMPONENT_TYPE_UNSIGNED_SHORT: {
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT: {
uint16_t s = *(uint16_t *)src;
if (p_normalized) {
d = (double(s) / 65535.0);
@@ -1380,12 +1483,27 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c
d = double(s);
}
} break;
- case COMPONENT_TYPE_INT: {
- d = *(int *)src;
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_INT: {
+ d = *(int32_t *)src;
+ } break;
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_INT: {
+ d = *(uint32_t *)src;
} break;
- case COMPONENT_TYPE_FLOAT: {
+ case GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT: {
d = *(float *)src;
} break;
+ case GLTFAccessor::COMPONENT_TYPE_DOUBLE_FLOAT: {
+ d = *(double *)src;
+ } break;
+ case GLTFAccessor::COMPONENT_TYPE_HALF_FLOAT: {
+ ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "glTF: Half float not supported yet.");
+ } break;
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_LONG: {
+ d = *(int64_t *)src;
+ } break;
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_LONG: {
+ d = *(uint64_t *)src;
+ } break;
}
*p_dst++ = d;
@@ -1396,25 +1514,27 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c
return OK;
}
-int GLTFDocument::_get_component_type_size(const int p_component_type) {
+int GLTFDocument::_get_component_type_size(const GLTFAccessor::GLTFComponentType p_component_type) {
switch (p_component_type) {
- case COMPONENT_TYPE_BYTE:
- case COMPONENT_TYPE_UNSIGNED_BYTE:
+ case GLTFAccessor::COMPONENT_TYPE_NONE:
+ ERR_FAIL_V(0);
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_BYTE:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_BYTE:
return 1;
- break;
- case COMPONENT_TYPE_SHORT:
- case COMPONENT_TYPE_UNSIGNED_SHORT:
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_SHORT:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT:
+ case GLTFAccessor::COMPONENT_TYPE_HALF_FLOAT:
return 2;
- break;
- case COMPONENT_TYPE_INT:
- case COMPONENT_TYPE_FLOAT:
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_INT:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_INT:
+ case GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT:
return 4;
- break;
- default: {
- ERR_FAIL_V(0);
- }
+ case GLTFAccessor::COMPONENT_TYPE_DOUBLE_FLOAT:
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_LONG:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_LONG:
+ return 8;
}
- return 0;
+ ERR_FAIL_V(0);
}
Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
@@ -1425,11 +1545,7 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTF
const Ref<GLTFAccessor> a = p_state->accessors[p_accessor];
- const int component_count_for_type[7] = {
- 1, 2, 3, 4, 4, 9, 16
- };
-
- const int component_count = component_count_for_type[a->accessor_type];
+ const int component_count = COMPONENT_COUNT_FOR_ACCESSOR_TYPE[a->accessor_type];
const int component_size = _get_component_type_size(a->component_type);
ERR_FAIL_COND_V(component_size == 0, Vector<double>());
int element_size = component_count * component_size;
@@ -1438,8 +1554,8 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTF
int skip_bytes = 0;
//special case of alignments, as described in spec
switch (a->component_type) {
- case COMPONENT_TYPE_BYTE:
- case COMPONENT_TYPE_UNSIGNED_BYTE: {
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_BYTE:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_BYTE: {
if (a->accessor_type == GLTFAccessor::TYPE_MAT2) {
skip_every = 2;
skip_bytes = 2;
@@ -1451,8 +1567,8 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTF
element_size = 12; //override for this case
}
} break;
- case COMPONENT_TYPE_SHORT:
- case COMPONENT_TYPE_UNSIGNED_SHORT: {
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_SHORT:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT: {
if (a->accessor_type == GLTFAccessor::TYPE_MAT3) {
skip_every = 6;
skip_bytes = 4;
@@ -1550,11 +1666,11 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(Ref<GLTFState> p_state,
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_SCALAR;
- int component_type;
+ GLTFAccessor::GLTFComponentType component_type;
if (max_index > 65535 || p_for_vertex) {
- component_type = GLTFDocument::COMPONENT_TYPE_INT;
+ component_type = GLTFAccessor::COMPONENT_TYPE_UNSIGNED_INT;
} else {
- component_type = GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT;
+ component_type = GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT;
}
accessor->max = type_max;
@@ -1664,7 +1780,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec2(Ref<GLTFState> p_state,
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC2;
- const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
+ const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT;
accessor->max = type_max;
accessor->min = type_min;
@@ -1717,7 +1833,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_color(Ref<GLTFState> p_state
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4;
- const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
+ const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT;
accessor->max = type_max;
accessor->min = type_min;
@@ -1784,7 +1900,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_weights(Ref<GLTFState> p_sta
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4;
- const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
+ const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT;
accessor->max = type_max;
accessor->min = type_min;
@@ -1835,7 +1951,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_joints(Ref<GLTFState> p_stat
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4;
- const int component_type = GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT;
+ const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT;
accessor->max = type_max;
accessor->min = type_min;
@@ -1888,7 +2004,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_quaternions(Ref<GLTFState> p
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4;
- const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
+ const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT;
accessor->max = type_max;
accessor->min = type_min;
@@ -1932,7 +2048,7 @@ Vector<Vector2> GLTFDocument::_decode_accessor_as_vec2(Ref<GLTFState> p_state, c
return ret;
}
-GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> p_state, const Vector<real_t> p_attribs, const bool p_for_vertex) {
+GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> p_state, const Vector<double> p_attribs, const bool p_for_vertex) {
if (p_attribs.size() == 0) {
return -1;
}
@@ -1963,7 +2079,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> p_stat
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_SCALAR;
- const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
+ const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT;
accessor->max = type_max;
accessor->min = type_min;
@@ -2013,7 +2129,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec3(Ref<GLTFState> p_state,
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC3;
- const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
+ const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT;
accessor->max = type_max;
accessor->min = type_min;
@@ -2089,7 +2205,7 @@ GLTFAccessorIndex GLTFDocument::_encode_sparse_accessor_as_vec3(Ref<GLTFState> p
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC3;
- const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
+ const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT;
sparse_accessor->normalized = false;
sparse_accessor->count = p_attribs.size();
@@ -2112,9 +2228,9 @@ GLTFAccessorIndex GLTFDocument::_encode_sparse_accessor_as_vec3(Ref<GLTFState> p
GLTFBufferIndex buffer_view_i_indices = -1;
GLTFBufferIndex buffer_view_i_values = -1;
if (sparse_accessor_index_stride == 4) {
- sparse_accessor->sparse_indices_component_type = GLTFDocument::COMPONENT_TYPE_INT;
+ sparse_accessor->sparse_indices_component_type = GLTFAccessor::COMPONENT_TYPE_UNSIGNED_INT;
} else {
- sparse_accessor->sparse_indices_component_type = GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT;
+ sparse_accessor->sparse_indices_component_type = GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT;
}
if (_encode_buffer_view(p_state, changed_indices.ptr(), changed_indices.size(), GLTFAccessor::TYPE_SCALAR, sparse_accessor->sparse_indices_component_type, sparse_accessor->normalized, sparse_accessor->sparse_indices_byte_offset, false, buffer_view_i_indices) != OK) {
return -1;
@@ -2194,7 +2310,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_xform(Ref<GLTFState> p_state
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_MAT4;
- const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
+ const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT;
accessor->max = type_max;
accessor->min = type_min;
@@ -2343,6 +2459,325 @@ Vector<Transform3D> GLTFDocument::_decode_accessor_as_xform(Ref<GLTFState> p_sta
return ret;
}
+Vector<Variant> GLTFDocument::_decode_accessor_as_variant(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, Variant::Type p_variant_type, GLTFAccessor::GLTFAccessorType p_accessor_type) {
+ const Vector<double> attribs = _decode_accessor(p_state, p_accessor, false);
+ Vector<Variant> ret;
+ ERR_FAIL_COND_V_MSG(attribs.is_empty(), ret, "glTF: The accessor was empty.");
+ const int component_count = COMPONENT_COUNT_FOR_ACCESSOR_TYPE[p_accessor_type];
+ ERR_FAIL_COND_V_MSG(attribs.size() % component_count != 0, ret, "glTF: The accessor size was not a multiple of the component count.");
+ const int ret_size = attribs.size() / component_count;
+ ret.resize(ret_size);
+ for (int i = 0; i < ret_size; i++) {
+ switch (p_variant_type) {
+ case Variant::BOOL: {
+ ret.write[i] = attribs[i * component_count] != 0.0;
+ } break;
+ case Variant::INT: {
+ ret.write[i] = (int64_t)attribs[i * component_count];
+ } break;
+ case Variant::FLOAT: {
+ ret.write[i] = attribs[i * component_count];
+ } break;
+ case Variant::VECTOR2:
+ case Variant::RECT2:
+ case Variant::VECTOR3:
+ case Variant::VECTOR4:
+ case Variant::PLANE:
+ case Variant::QUATERNION: {
+ // General-purpose code for importing glTF accessor data with any component count into structs up to 4 `real_t`s in size.
+ Variant v;
+ switch (component_count) {
+ case 1: {
+ v = Vector4(attribs[i * component_count], 0.0f, 0.0f, 0.0f);
+ } break;
+ case 2: {
+ v = Vector4(attribs[i * component_count], attribs[i * component_count + 1], 0.0f, 0.0f);
+ } break;
+ case 3: {
+ v = Vector4(attribs[i * component_count], attribs[i * component_count + 1], attribs[i * component_count + 2], 0.0f);
+ } break;
+ default: {
+ v = Vector4(attribs[i * component_count], attribs[i * component_count + 1], attribs[i * component_count + 2], attribs[i * component_count + 3]);
+ } break;
+ }
+ // Evil hack that relies on the structure of Variant, but it's the
+ // only way to accomplish this without a ton of code duplication.
+ *(Variant::Type *)&v = p_variant_type;
+ ret.write[i] = v;
+ } break;
+ case Variant::VECTOR2I:
+ case Variant::RECT2I:
+ case Variant::VECTOR3I:
+ case Variant::VECTOR4I: {
+ // General-purpose code for importing glTF accessor data with any component count into structs up to 4 `int32_t`s in size.
+ Variant v;
+ switch (component_count) {
+ case 1: {
+ v = Vector4i((int32_t)attribs[i * component_count], 0, 0, 0);
+ } break;
+ case 2: {
+ v = Vector4i((int32_t)attribs[i * component_count], (int32_t)attribs[i * component_count + 1], 0, 0);
+ } break;
+ case 3: {
+ v = Vector4i((int32_t)attribs[i * component_count], (int32_t)attribs[i * component_count + 1], (int32_t)attribs[i * component_count + 2], 0);
+ } break;
+ default: {
+ v = Vector4i((int32_t)attribs[i * component_count], (int32_t)attribs[i * component_count + 1], (int32_t)attribs[i * component_count + 2], (int32_t)attribs[i * component_count + 3]);
+ } break;
+ }
+ // Evil hack that relies on the structure of Variant, but it's the
+ // only way to accomplish this without a ton of code duplication.
+ *(Variant::Type *)&v = p_variant_type;
+ ret.write[i] = v;
+ } break;
+ // No more generalized hacks, each of the below types needs a lot of repetitive code.
+ case Variant::COLOR: {
+ Variant v;
+ switch (component_count) {
+ case 1: {
+ v = Color(attribs[i * component_count], 0.0f, 0.0f, 1.0f);
+ } break;
+ case 2: {
+ v = Color(attribs[i * component_count], attribs[i * component_count + 1], 0.0f, 1.0f);
+ } break;
+ case 3: {
+ v = Color(attribs[i * component_count], attribs[i * component_count + 1], attribs[i * component_count + 2], 1.0f);
+ } break;
+ default: {
+ v = Color(attribs[i * component_count], attribs[i * component_count + 1], attribs[i * component_count + 2], attribs[i * component_count + 3]);
+ } break;
+ }
+ ret.write[i] = v;
+ } break;
+ case Variant::TRANSFORM2D: {
+ Transform2D t;
+ switch (component_count) {
+ case 4: {
+ t.columns[0] = Vector2(attribs[i * component_count + 0], attribs[i * component_count + 1]);
+ t.columns[1] = Vector2(attribs[i * component_count + 2], attribs[i * component_count + 3]);
+ } break;
+ case 9: {
+ t.columns[0] = Vector2(attribs[i * component_count + 0], attribs[i * component_count + 1]);
+ t.columns[1] = Vector2(attribs[i * component_count + 3], attribs[i * component_count + 4]);
+ t.columns[2] = Vector2(attribs[i * component_count + 6], attribs[i * component_count + 7]);
+ } break;
+ case 16: {
+ t.columns[0] = Vector2(attribs[i * component_count + 0], attribs[i * component_count + 1]);
+ t.columns[1] = Vector2(attribs[i * component_count + 4], attribs[i * component_count + 5]);
+ t.columns[2] = Vector2(attribs[i * component_count + 12], attribs[i * component_count + 13]);
+ } break;
+ }
+ ret.write[i] = t;
+ } break;
+ case Variant::BASIS: {
+ Basis b;
+ switch (component_count) {
+ case 4: {
+ b.rows[0] = Vector3(attribs[i * component_count + 0], attribs[i * component_count + 2], 0.0f);
+ b.rows[1] = Vector3(attribs[i * component_count + 1], attribs[i * component_count + 3], 0.0f);
+ } break;
+ case 9: {
+ b.rows[0] = Vector3(attribs[i * component_count + 0], attribs[i * component_count + 3], attribs[i * component_count + 6]);
+ b.rows[1] = Vector3(attribs[i * component_count + 1], attribs[i * component_count + 4], attribs[i * component_count + 7]);
+ b.rows[2] = Vector3(attribs[i * component_count + 2], attribs[i * component_count + 5], attribs[i * component_count + 8]);
+ } break;
+ case 16: {
+ b.rows[0] = Vector3(attribs[i * component_count + 0], attribs[i * component_count + 4], attribs[i * component_count + 8]);
+ b.rows[1] = Vector3(attribs[i * component_count + 1], attribs[i * component_count + 5], attribs[i * component_count + 9]);
+ b.rows[2] = Vector3(attribs[i * component_count + 2], attribs[i * component_count + 6], attribs[i * component_count + 10]);
+ } break;
+ }
+ ret.write[i] = b;
+ } break;
+ case Variant::TRANSFORM3D: {
+ Transform3D t;
+ switch (component_count) {
+ case 4: {
+ t.basis.rows[0] = Vector3(attribs[i * component_count + 0], attribs[i * component_count + 2], 0.0f);
+ t.basis.rows[1] = Vector3(attribs[i * component_count + 1], attribs[i * component_count + 3], 0.0f);
+ } break;
+ case 9: {
+ t.basis.rows[0] = Vector3(attribs[i * component_count + 0], attribs[i * component_count + 3], attribs[i * component_count + 6]);
+ t.basis.rows[1] = Vector3(attribs[i * component_count + 1], attribs[i * component_count + 4], attribs[i * component_count + 7]);
+ t.basis.rows[2] = Vector3(attribs[i * component_count + 2], attribs[i * component_count + 5], attribs[i * component_count + 8]);
+ } break;
+ case 16: {
+ t.basis.rows[0] = Vector3(attribs[i * component_count + 0], attribs[i * component_count + 4], attribs[i * component_count + 8]);
+ t.basis.rows[1] = Vector3(attribs[i * component_count + 1], attribs[i * component_count + 5], attribs[i * component_count + 9]);
+ t.basis.rows[2] = Vector3(attribs[i * component_count + 2], attribs[i * component_count + 6], attribs[i * component_count + 10]);
+ t.origin = Vector3(attribs[i * component_count + 12], attribs[i * component_count + 13], attribs[i * component_count + 14]);
+ } break;
+ }
+ ret.write[i] = t;
+ } break;
+ case Variant::PROJECTION: {
+ Projection p;
+ switch (component_count) {
+ case 4: {
+ p.columns[0] = Vector4(attribs[i * component_count + 0], attribs[i * component_count + 1], 0.0f, 0.0f);
+ p.columns[1] = Vector4(attribs[i * component_count + 4], attribs[i * component_count + 5], 0.0f, 0.0f);
+ } break;
+ case 9: {
+ p.columns[0] = Vector4(attribs[i * component_count + 0], attribs[i * component_count + 1], attribs[i * component_count + 2], 0.0f);
+ p.columns[1] = Vector4(attribs[i * component_count + 4], attribs[i * component_count + 5], attribs[i * component_count + 6], 0.0f);
+ p.columns[2] = Vector4(attribs[i * component_count + 8], attribs[i * component_count + 9], attribs[i * component_count + 10], 0.0f);
+ } break;
+ case 16: {
+ p.columns[0] = Vector4(attribs[i * component_count + 0], attribs[i * component_count + 1], attribs[i * component_count + 2], attribs[i * component_count + 3]);
+ p.columns[1] = Vector4(attribs[i * component_count + 4], attribs[i * component_count + 5], attribs[i * component_count + 6], attribs[i * component_count + 7]);
+ p.columns[2] = Vector4(attribs[i * component_count + 8], attribs[i * component_count + 9], attribs[i * component_count + 10], attribs[i * component_count + 11]);
+ p.columns[3] = Vector4(attribs[i * component_count + 12], attribs[i * component_count + 13], attribs[i * component_count + 14], attribs[i * component_count + 15]);
+ } break;
+ }
+ ret.write[i] = p;
+ } break;
+ default: {
+ ERR_FAIL_V_MSG(ret, "glTF: Cannot decode accessor as Variant of type " + Variant::get_type_name(p_variant_type) + ".");
+ }
+ }
+ }
+ return ret;
+}
+
+GLTFAccessorIndex GLTFDocument::_encode_accessor_as_variant(Ref<GLTFState> p_state, Vector<Variant> p_attribs, Variant::Type p_variant_type, GLTFAccessor::GLTFAccessorType p_accessor_type, GLTFAccessor::GLTFComponentType p_component_type) {
+ const int accessor_component_count = COMPONENT_COUNT_FOR_ACCESSOR_TYPE[p_accessor_type];
+ Vector<double> encoded_attribs;
+ for (const Variant &v : p_attribs) {
+ switch (p_variant_type) {
+ case Variant::NIL:
+ case Variant::BOOL:
+ case Variant::INT:
+ case Variant::FLOAT: {
+ // For scalar values, just append them. Variant can convert all of these to double. Some padding may also be needed.
+ encoded_attribs.append(v);
+ if (unlikely(accessor_component_count > 1)) {
+ for (int i = 1; i < accessor_component_count; i++) {
+ encoded_attribs.append(0.0);
+ }
+ }
+ } break;
+ case Variant::VECTOR2:
+ case Variant::VECTOR2I:
+ case Variant::VECTOR3:
+ case Variant::VECTOR3I:
+ case Variant::VECTOR4:
+ case Variant::VECTOR4I: {
+ // Variant can handle converting Vector2/2i/3/3i/4/4i to Vector4 for us.
+ Vector4 vec = v;
+ if (likely(accessor_component_count < 5)) {
+ for (int i = 0; i < accessor_component_count; i++) {
+ encoded_attribs.append(vec[i]);
+ }
+ }
+ } break;
+ case Variant::PLANE: {
+ Plane p = v;
+ if (likely(accessor_component_count == 4)) {
+ encoded_attribs.append(p.normal.x);
+ encoded_attribs.append(p.normal.y);
+ encoded_attribs.append(p.normal.z);
+ encoded_attribs.append(p.d);
+ }
+ } break;
+ case Variant::QUATERNION: {
+ Quaternion q = v;
+ if (likely(accessor_component_count < 5)) {
+ for (int i = 0; i < accessor_component_count; i++) {
+ encoded_attribs.append(q[i]);
+ }
+ }
+ } break;
+ case Variant::COLOR: {
+ Color c = v;
+ if (likely(accessor_component_count < 5)) {
+ for (int i = 0; i < accessor_component_count; i++) {
+ encoded_attribs.append(c[i]);
+ }
+ }
+ } break;
+ case Variant::RECT2:
+ case Variant::RECT2I: {
+ // Variant can handle converting Rect2i to Rect2 for us.
+ Rect2 r = v;
+ if (likely(accessor_component_count == 4)) {
+ encoded_attribs.append(r.position.x);
+ encoded_attribs.append(r.position.y);
+ encoded_attribs.append(r.size.x);
+ encoded_attribs.append(r.size.y);
+ }
+ } break;
+ case Variant::TRANSFORM2D:
+ case Variant::BASIS:
+ case Variant::TRANSFORM3D:
+ case Variant::PROJECTION: {
+ // Variant can handle converting Transform2D/Transform3D/Basis to Projection for us.
+ Projection p = v;
+ if (accessor_component_count == 16) {
+ for (int i = 0; i < 4; i++) {
+ encoded_attribs.append(p.columns[i][0]);
+ encoded_attribs.append(p.columns[i][1]);
+ encoded_attribs.append(p.columns[i][2]);
+ encoded_attribs.append(p.columns[i][3]);
+ }
+ } else if (accessor_component_count == 9) {
+ for (int i = 0; i < 3; i++) {
+ encoded_attribs.append(p.columns[i][0]);
+ encoded_attribs.append(p.columns[i][1]);
+ encoded_attribs.append(p.columns[i][2]);
+ }
+ } else if (accessor_component_count == 4) {
+ encoded_attribs.append(p.columns[0][0]);
+ encoded_attribs.append(p.columns[0][1]);
+ encoded_attribs.append(p.columns[1][0]);
+ encoded_attribs.append(p.columns[1][1]);
+ }
+ } break;
+ default: {
+ ERR_FAIL_V_MSG(-1, "glTF: Cannot encode accessor from Variant of type " + Variant::get_type_name(p_variant_type) + ".");
+ }
+ }
+ }
+ // Determine the min and max values for the accessor.
+ Vector<double> type_max;
+ type_max.resize(accessor_component_count);
+ Vector<double> type_min;
+ type_min.resize(accessor_component_count);
+ for (int i = 0; i < encoded_attribs.size(); i++) {
+ if (Math::is_zero_approx(encoded_attribs[i])) {
+ encoded_attribs.write[i] = 0.0;
+ } else {
+ encoded_attribs.write[i] = _filter_number(encoded_attribs[i]);
+ }
+ }
+ for (int i = 0; i < p_attribs.size(); i++) {
+ _calc_accessor_min_max(i, accessor_component_count, type_max, encoded_attribs, type_min);
+ }
+ _round_min_max_components(type_min, type_max);
+ // Encode the data in a buffer view.
+ GLTFBufferIndex buffer_view_index = 0;
+ if (p_state->buffers.is_empty()) {
+ p_state->buffers.push_back(Vector<uint8_t>());
+ }
+ const int64_t buffer_size = p_state->buffers[buffer_view_index].size();
+ Error err = _encode_buffer_view(p_state, encoded_attribs.ptr(), p_attribs.size(), p_accessor_type, p_component_type, false, buffer_size, false, buffer_view_index);
+ if (err != OK) {
+ return -1;
+ }
+ // Create the accessor and fill it with the data.
+ Ref<GLTFAccessor> accessor;
+ accessor.instantiate();
+ accessor->max = type_max;
+ accessor->min = type_min;
+ accessor->count = p_attribs.size();
+ accessor->accessor_type = p_accessor_type;
+ accessor->component_type = p_component_type;
+ accessor->byte_offset = 0;
+ accessor->buffer_view = buffer_view_index;
+ const GLTFAccessorIndex new_accessor_index = p_state->accessors.size();
+ p_state->accessors.push_back(accessor);
+ return new_accessor_index;
+}
+
Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) {
Array meshes;
for (GLTFMeshIndex gltf_mesh_i = 0; gltf_mesh_i < p_state->meshes.size(); gltf_mesh_i++) {
@@ -2778,41 +3213,42 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
Array meshes = p_state->json["meshes"];
for (GLTFMeshIndex i = 0; i < meshes.size(); i++) {
print_verbose("glTF: Parsing mesh: " + itos(i));
- Dictionary d = meshes[i];
+ Dictionary mesh_dict = meshes[i];
Ref<GLTFMesh> mesh;
mesh.instantiate();
bool has_vertex_color = false;
- ERR_FAIL_COND_V(!d.has("primitives"), ERR_PARSE_ERROR);
+ ERR_FAIL_COND_V(!mesh_dict.has("primitives"), ERR_PARSE_ERROR);
- Array primitives = d["primitives"];
- const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary();
+ Array primitives = mesh_dict["primitives"];
+ const Dictionary &extras = mesh_dict.has("extras") ? (Dictionary)mesh_dict["extras"] : Dictionary();
_attach_extras_to_meta(extras, mesh);
Ref<ImporterMesh> import_mesh;
import_mesh.instantiate();
String mesh_name = "mesh";
- if (d.has("name") && !String(d["name"]).is_empty()) {
- mesh_name = d["name"];
+ if (mesh_dict.has("name") && !String(mesh_dict["name"]).is_empty()) {
+ mesh_name = mesh_dict["name"];
mesh->set_original_name(mesh_name);
}
import_mesh->set_name(_gen_unique_name(p_state, vformat("%s_%s", p_state->scene_name, mesh_name)));
mesh->set_name(import_mesh->get_name());
+ TypedArray<Material> instance_materials;
for (int j = 0; j < primitives.size(); j++) {
uint64_t flags = RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
- Dictionary p = primitives[j];
+ Dictionary mesh_prim = primitives[j];
Array array;
array.resize(Mesh::ARRAY_MAX);
- ERR_FAIL_COND_V(!p.has("attributes"), ERR_PARSE_ERROR);
+ ERR_FAIL_COND_V(!mesh_prim.has("attributes"), ERR_PARSE_ERROR);
- Dictionary a = p["attributes"];
+ Dictionary a = mesh_prim["attributes"];
Mesh::PrimitiveType primitive = Mesh::PRIMITIVE_TRIANGLES;
- if (p.has("mode")) {
- const int mode = p["mode"];
+ if (mesh_prim.has("mode")) {
+ const int mode = mesh_prim["mode"];
ERR_FAIL_INDEX_V(mode, 7, ERR_FILE_CORRUPT);
// Convert mesh.primitive.mode to Godot Mesh enum. See:
// https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#_mesh_primitive_mode
@@ -2843,8 +3279,8 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
Vector<int> indices_mapping;
Vector<int> indices_rev_mapping;
Vector<int> indices_vec4_mapping;
- if (p.has("indices")) {
- indices = _decode_accessor_as_ints(p_state, p["indices"], false);
+ if (mesh_prim.has("indices")) {
+ indices = _decode_accessor_as_ints(p_state, mesh_prim["indices"], false);
const int is = indices.size();
if (primitive == Mesh::PRIMITIVE_TRIANGLES) {
@@ -3046,6 +3482,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
}
}
array[Mesh::ARRAY_WEIGHTS] = weights;
+ flags |= Mesh::ARRAY_FLAG_USE_8_BONE_WEIGHTS;
}
if (!indices.is_empty()) {
@@ -3102,7 +3539,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
}
}
- if (p_state->force_disable_compression || is_mesh_2d || !a.has("POSITION") || !a.has("NORMAL") || p.has("targets") || (a.has("JOINTS_0") || a.has("JOINTS_1"))) {
+ if (p_state->force_disable_compression || is_mesh_2d || !a.has("POSITION") || !a.has("NORMAL") || mesh_prim.has("targets") || (a.has("JOINTS_0") || a.has("JOINTS_1"))) {
flags &= ~RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
}
@@ -3134,9 +3571,9 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
Array morphs;
// Blend shapes
- if (p.has("targets")) {
+ if (mesh_prim.has("targets")) {
print_verbose("glTF: Mesh has targets");
- const Array &targets = p["targets"];
+ const Array &targets = mesh_prim["targets"];
import_mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED);
@@ -3267,8 +3704,8 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
Ref<Material> mat;
String mat_name;
if (!p_state->discard_meshes_and_materials) {
- if (p.has("material")) {
- const int material = p["material"];
+ if (mesh_prim.has("material")) {
+ const int material = mesh_prim["material"];
ERR_FAIL_INDEX_V(material, p_state->materials.size(), ERR_FILE_CORRUPT);
Ref<Material> mat3d = p_state->materials[material];
ERR_FAIL_COND_V(mat3d.is_null(), ERR_FILE_CORRUPT);
@@ -3288,6 +3725,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
mat = mat3d;
}
ERR_FAIL_COND_V(mat.is_null(), ERR_FILE_CORRUPT);
+ instance_materials.append(mat);
mat_name = mat->get_name();
}
import_mesh->add_surface(primitive, array, morphs,
@@ -3300,8 +3738,8 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
blend_weights.write[weight_i] = 0.0f;
}
- if (d.has("weights")) {
- const Array &weights = d["weights"];
+ if (mesh_dict.has("weights")) {
+ const Array &weights = mesh_dict["weights"];
for (int j = 0; j < weights.size(); j++) {
if (j >= blend_weights.size()) {
break;
@@ -3310,6 +3748,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
}
}
mesh->set_blend_weights(blend_weights);
+ mesh->set_instance_materials(instance_materials);
mesh->set_mesh(import_mesh);
p_state->meshes.push_back(mesh);
@@ -3501,18 +3940,19 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const Vector<
}
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint() && handling == GLTFState::GLTFHandleBinary::HANDLE_BINARY_EXTRACT_TEXTURES) {
- if (p_state->base_path.is_empty()) {
- p_state->images.push_back(Ref<Texture2D>());
- p_state->source_images.push_back(Ref<Image>());
- } else if (p_image->get_name().is_empty()) {
- WARN_PRINT(vformat("glTF: Image index '%d' couldn't be named. Skipping it.", p_index));
- p_state->images.push_back(Ref<Texture2D>());
- p_state->source_images.push_back(Ref<Image>());
+ if (p_state->extract_path.is_empty()) {
+ WARN_PRINT("glTF: Couldn't extract image because the base and extract paths are empty. It will be loaded directly instead, uncompressed.");
+ } else if (p_state->extract_path.begins_with("res://.godot/imported")) {
+ WARN_PRINT(vformat("glTF: Extract path is in the imported directory. Image index '%d' will be loaded directly, uncompressed.", p_index));
} else {
+ if (p_image->get_name().is_empty()) {
+ WARN_PRINT(vformat("glTF: Image index '%d' did not have a name. It will be automatically given a name based on its index.", p_index));
+ p_image->set_name(itos(p_index));
+ }
bool must_import = true;
Vector<uint8_t> img_data = p_image->get_data();
Dictionary generator_parameters;
- String file_path = p_state->get_base_path().path_join(p_state->filename.get_basename() + "_" + p_image->get_name());
+ String file_path = p_state->get_extract_path().path_join(p_state->get_extract_prefix() + "_" + p_image->get_name());
file_path += p_file_extension.is_empty() ? ".png" : p_file_extension;
if (FileAccess::exists(file_path + ".import")) {
Ref<ConfigFile> config;
@@ -3559,14 +3999,11 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const Vector<
if (saved_image.is_valid()) {
p_state->images.push_back(saved_image);
p_state->source_images.push_back(saved_image->get_image());
+ return;
} else {
- WARN_PRINT(vformat("glTF: Image index '%d' couldn't be loaded with the name: %s. Skipping it.", p_index, p_image->get_name()));
- // Placeholder to keep count.
- p_state->images.push_back(Ref<Texture2D>());
- p_state->source_images.push_back(Ref<Image>());
+ WARN_PRINT(vformat("glTF: Image index '%d' with the name '%s' couldn't be imported. It will be loaded directly instead, uncompressed.", p_index, p_image->get_name()));
}
}
- return;
}
#endif // TOOLS_ENABLED
if (handling == GLTFState::GLTFHandleBinary::HANDLE_BINARY_EMBED_AS_BASISU) {
@@ -3649,16 +4086,19 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p
ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER);
uri = uri.uri_decode();
uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows.
- // ResourceLoader will rely on the file extension to use the relevant loader.
- // The spec says that if mimeType is defined, it should take precedence (e.g.
- // there could be a `.png` image which is actually JPEG), but there's no easy
- // API for that in Godot, so we'd have to load as a buffer (i.e. embedded in
- // the material), so we only do that only as fallback.
- Ref<Texture2D> texture = ResourceLoader::load(uri);
- if (texture.is_valid()) {
- p_state->images.push_back(texture);
- p_state->source_images.push_back(texture->get_image());
- continue;
+ // If the image is in the .godot/imported directory, we can't use ResourceLoader.
+ if (!p_base_path.begins_with("res://.godot/imported")) {
+ // ResourceLoader will rely on the file extension to use the relevant loader.
+ // The spec says that if mimeType is defined, it should take precedence (e.g.
+ // there could be a `.png` image which is actually JPEG), but there's no easy
+ // API for that in Godot, so we'd have to load as a buffer (i.e. embedded in
+ // the material), so we only do that only as fallback.
+ Ref<Texture2D> texture = ResourceLoader::load(uri, "Texture2D");
+ if (texture.is_valid()) {
+ p_state->images.push_back(texture);
+ p_state->source_images.push_back(texture->get_image());
+ continue;
+ }
}
// mimeType is optional, but if we have it in the file extension, let's use it.
// If the mimeType does not match with the file extension, either it should be
@@ -4781,7 +5221,7 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) {
for (GLTFAnimationIndex animation_i = 0; animation_i < p_state->animations.size(); animation_i++) {
Dictionary d;
Ref<GLTFAnimation> gltf_animation = p_state->animations[animation_i];
- if (!gltf_animation->get_tracks().size()) {
+ if (gltf_animation->is_empty_of_tracks()) {
continue;
}
@@ -4790,18 +5230,18 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) {
}
Array channels;
Array samplers;
-
- for (KeyValue<int, GLTFAnimation::Track> &track_i : gltf_animation->get_tracks()) {
- GLTFAnimation::Track track = track_i.value;
+ // Serialize glTF node tracks with the vanilla glTF animation system.
+ for (KeyValue<int, GLTFAnimation::NodeTrack> &track_i : gltf_animation->get_node_tracks()) {
+ GLTFAnimation::NodeTrack track = track_i.value;
if (track.position_track.times.size()) {
Dictionary t;
t["sampler"] = samplers.size();
Dictionary s;
s["interpolation"] = interpolation_to_string(track.position_track.interpolation);
- Vector<real_t> times = Variant(track.position_track.times);
+ Vector<double> times = track.position_track.times;
s["input"] = _encode_accessor_as_floats(p_state, times, false);
- Vector<Vector3> values = Variant(track.position_track.values);
+ Vector<Vector3> values = track.position_track.values;
s["output"] = _encode_accessor_as_vec3(p_state, values, false);
samplers.push_back(s);
@@ -4819,7 +5259,7 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) {
Dictionary s;
s["interpolation"] = interpolation_to_string(track.rotation_track.interpolation);
- Vector<real_t> times = Variant(track.rotation_track.times);
+ Vector<double> times = track.rotation_track.times;
s["input"] = _encode_accessor_as_floats(p_state, times, false);
Vector<Quaternion> values = track.rotation_track.values;
s["output"] = _encode_accessor_as_quaternions(p_state, values, false);
@@ -4839,9 +5279,9 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) {
Dictionary s;
s["interpolation"] = interpolation_to_string(track.scale_track.interpolation);
- Vector<real_t> times = Variant(track.scale_track.times);
+ Vector<double> times = track.scale_track.times;
s["input"] = _encode_accessor_as_floats(p_state, times, false);
- Vector<Vector3> values = Variant(track.scale_track.values);
+ Vector<Vector3> values = track.scale_track.values;
s["output"] = _encode_accessor_as_vec3(p_state, values, false);
samplers.push_back(s);
@@ -4864,7 +5304,7 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) {
Dictionary t;
t["sampler"] = samplers.size();
Dictionary s;
- Vector<real_t> times;
+ Vector<double> times;
const double increment = 1.0 / p_state->get_bake_fps();
{
double time = 0.0;
@@ -4905,8 +5345,8 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) {
track.weight_tracks.write[track_idx].values = weight_track;
}
- Vector<real_t> all_track_times = times;
- Vector<real_t> all_track_values;
+ Vector<double> all_track_times = times;
+ Vector<double> all_track_values;
int32_t values_size = track.weight_tracks[0].values.size();
int32_t weight_tracks_size = track.weight_tracks.size();
all_track_values.resize(weight_tracks_size * values_size);
@@ -4933,6 +5373,33 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) {
channels.push_back(t);
}
}
+ if (!gltf_animation->get_pointer_tracks().is_empty()) {
+ // Serialize glTF pointer tracks with the KHR_animation_pointer extension.
+ if (!p_state->extensions_used.has("KHR_animation_pointer")) {
+ p_state->extensions_used.push_back("KHR_animation_pointer");
+ }
+ for (KeyValue<String, GLTFAnimation::Channel<Variant>> &pointer_track_iter : gltf_animation->get_pointer_tracks()) {
+ const String &json_pointer = pointer_track_iter.key;
+ const GLTFAnimation::Channel<Variant> &pointer_track = pointer_track_iter.value;
+ const Ref<GLTFObjectModelProperty> &obj_model_prop = p_state->object_model_properties[json_pointer];
+ Dictionary channel;
+ channel["sampler"] = samplers.size();
+ Dictionary channel_target;
+ channel_target["path"] = "pointer";
+ Dictionary channel_target_ext;
+ Dictionary channel_target_ext_khr_anim_ptr;
+ channel_target_ext_khr_anim_ptr["pointer"] = json_pointer;
+ channel_target_ext["KHR_animation_pointer"] = channel_target_ext_khr_anim_ptr;
+ channel_target["extensions"] = channel_target_ext;
+ channel["target"] = channel_target;
+ channels.push_back(channel);
+ Dictionary sampler;
+ sampler["input"] = _encode_accessor_as_floats(p_state, pointer_track.times, false);
+ sampler["interpolation"] = interpolation_to_string(pointer_track.interpolation);
+ sampler["output"] = _encode_accessor_as_variant(p_state, pointer_track.values, obj_model_prop->get_variant_type(), obj_model_prop->get_accessor_type());
+ samplers.push_back(sampler);
+ }
+ }
if (channels.size() && samplers.size()) {
d["channels"] = channels;
d["samplers"] = samplers;
@@ -4957,21 +5424,21 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> p_state) {
const Array &animations = p_state->json["animations"];
- for (GLTFAnimationIndex i = 0; i < animations.size(); i++) {
- const Dictionary &d = animations[i];
+ for (GLTFAnimationIndex anim_index = 0; anim_index < animations.size(); anim_index++) {
+ const Dictionary &anim_dict = animations[anim_index];
Ref<GLTFAnimation> animation;
animation.instantiate();
- if (!d.has("channels") || !d.has("samplers")) {
+ if (!anim_dict.has("channels") || !anim_dict.has("samplers")) {
continue;
}
- Array channels = d["channels"];
- Array samplers = d["samplers"];
+ Array channels = anim_dict["channels"];
+ Array samplers = anim_dict["samplers"];
- if (d.has("name")) {
- const String anim_name = d["name"];
+ if (anim_dict.has("name")) {
+ const String anim_name = anim_dict["name"];
const String anim_name_lower = anim_name.to_lower();
if (anim_name_lower.begins_with("loop") || anim_name_lower.ends_with("loop") || anim_name_lower.begins_with("cycle") || anim_name_lower.ends_with("cycle")) {
animation->set_loop(true);
@@ -4980,46 +5447,22 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> p_state) {
animation->set_name(_gen_unique_animation_name(p_state, anim_name));
}
- for (int j = 0; j < channels.size(); j++) {
- const Dictionary &c = channels[j];
- if (!c.has("target")) {
- continue;
- }
-
- const Dictionary &t = c["target"];
- if (!t.has("node") || !t.has("path")) {
- continue;
- }
-
- ERR_FAIL_COND_V(!c.has("sampler"), ERR_PARSE_ERROR);
- const int sampler = c["sampler"];
- ERR_FAIL_INDEX_V(sampler, samplers.size(), ERR_PARSE_ERROR);
-
- GLTFNodeIndex node = t["node"];
- String path = t["path"];
-
- ERR_FAIL_INDEX_V(node, p_state->nodes.size(), ERR_PARSE_ERROR);
-
- GLTFAnimation::Track *track = nullptr;
-
- if (!animation->get_tracks().has(node)) {
- animation->get_tracks()[node] = GLTFAnimation::Track();
- }
-
- track = &animation->get_tracks()[node];
-
- const Dictionary &s = samplers[sampler];
-
- ERR_FAIL_COND_V(!s.has("input"), ERR_PARSE_ERROR);
- ERR_FAIL_COND_V(!s.has("output"), ERR_PARSE_ERROR);
-
- const int input = s["input"];
- const int output = s["output"];
-
+ for (int channel_index = 0; channel_index < channels.size(); channel_index++) {
+ const Dictionary &anim_channel = channels[channel_index];
+ ERR_FAIL_COND_V_MSG(!anim_channel.has("sampler"), ERR_PARSE_ERROR, "glTF: Animation channel missing required 'sampler' property.");
+ ERR_FAIL_COND_V_MSG(!anim_channel.has("target"), ERR_PARSE_ERROR, "glTF: Animation channel missing required 'target' property.");
+ // Parse sampler.
+ const int sampler_index = anim_channel["sampler"];
+ ERR_FAIL_INDEX_V(sampler_index, samplers.size(), ERR_PARSE_ERROR);
+ const Dictionary &sampler_dict = samplers[sampler_index];
+ ERR_FAIL_COND_V(!sampler_dict.has("input"), ERR_PARSE_ERROR);
+ ERR_FAIL_COND_V(!sampler_dict.has("output"), ERR_PARSE_ERROR);
+ const int input_time_accessor_index = sampler_dict["input"];
+ const int output_value_accessor_index = sampler_dict["output"];
GLTFAnimation::Interpolation interp = GLTFAnimation::INTERP_LINEAR;
int output_count = 1;
- if (s.has("interpolation")) {
- const String &in = s["interpolation"];
+ if (sampler_dict.has("interpolation")) {
+ const String &in = sampler_dict["interpolation"];
if (in == "STEP") {
interp = GLTFAnimation::INTERP_STEP;
} else if (in == "LINEAR") {
@@ -5032,52 +5475,83 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> p_state) {
output_count = 3;
}
}
+ const Vector<double> times = _decode_accessor(p_state, input_time_accessor_index, false);
+ // Parse target.
+ const Dictionary &anim_target = anim_channel["target"];
+ ERR_FAIL_COND_V_MSG(!anim_target.has("path"), ERR_PARSE_ERROR, "glTF: Animation channel target missing required 'path' property.");
+ String path = anim_target["path"];
+ if (path == "pointer") {
+ ERR_FAIL_COND_V(!anim_target.has("extensions"), ERR_PARSE_ERROR);
+ Dictionary target_extensions = anim_target["extensions"];
+ ERR_FAIL_COND_V(!target_extensions.has("KHR_animation_pointer"), ERR_PARSE_ERROR);
+ Dictionary khr_anim_ptr = target_extensions["KHR_animation_pointer"];
+ ERR_FAIL_COND_V(!khr_anim_ptr.has("pointer"), ERR_PARSE_ERROR);
+ String anim_json_ptr = khr_anim_ptr["pointer"];
+ _parse_animation_pointer(p_state, anim_json_ptr, animation, interp, times, output_value_accessor_index);
+ } else {
+ // If it's not a pointer, it's a regular animation channel from vanilla glTF (pos/rot/scale/weights).
+ if (!anim_target.has("node")) {
+ WARN_PRINT("glTF: Animation channel target missing 'node' property. Ignoring this channel.");
+ continue;
+ }
- const Vector<float> times = _decode_accessor_as_floats(p_state, input, false);
- if (path == "translation") {
- const Vector<Vector3> positions = _decode_accessor_as_vec3(p_state, output, false);
- track->position_track.interpolation = interp;
- track->position_track.times = Variant(times); //convert via variant
- track->position_track.values = Variant(positions); //convert via variant
- } else if (path == "rotation") {
- const Vector<Quaternion> rotations = _decode_accessor_as_quaternion(p_state, output, false);
- track->rotation_track.interpolation = interp;
- track->rotation_track.times = Variant(times); //convert via variant
- track->rotation_track.values = rotations;
- } else if (path == "scale") {
- const Vector<Vector3> scales = _decode_accessor_as_vec3(p_state, output, false);
- track->scale_track.interpolation = interp;
- track->scale_track.times = Variant(times); //convert via variant
- track->scale_track.values = Variant(scales); //convert via variant
- } else if (path == "weights") {
- const Vector<float> weights = _decode_accessor_as_floats(p_state, output, false);
-
- ERR_FAIL_INDEX_V(p_state->nodes[node]->mesh, p_state->meshes.size(), ERR_PARSE_ERROR);
- Ref<GLTFMesh> mesh = p_state->meshes[p_state->nodes[node]->mesh];
- ERR_CONTINUE(!mesh->get_blend_weights().size());
- const int wc = mesh->get_blend_weights().size();
-
- track->weight_tracks.resize(wc);
-
- const int expected_value_count = times.size() * output_count * wc;
- ERR_CONTINUE_MSG(weights.size() != expected_value_count, "Invalid weight data, expected " + itos(expected_value_count) + " weight values, got " + itos(weights.size()) + " instead.");
-
- const int wlen = weights.size() / wc;
- for (int k = 0; k < wc; k++) { //separate tracks, having them together is not such a good idea
- GLTFAnimation::Channel<real_t> cf;
- cf.interpolation = interp;
- cf.times = Variant(times);
- Vector<real_t> wdata;
- wdata.resize(wlen);
- for (int l = 0; l < wlen; l++) {
- wdata.write[l] = weights[l * wc + k];
- }
+ GLTFNodeIndex node = anim_target["node"];
+
+ ERR_FAIL_INDEX_V(node, p_state->nodes.size(), ERR_PARSE_ERROR);
- cf.values = wdata;
- track->weight_tracks.write[k] = cf;
+ GLTFAnimation::NodeTrack *track = nullptr;
+
+ if (!animation->get_node_tracks().has(node)) {
+ animation->get_node_tracks()[node] = GLTFAnimation::NodeTrack();
+ }
+
+ track = &animation->get_node_tracks()[node];
+
+ if (path == "translation") {
+ const Vector<Vector3> positions = _decode_accessor_as_vec3(p_state, output_value_accessor_index, false);
+ track->position_track.interpolation = interp;
+ track->position_track.times = times;
+ track->position_track.values = positions;
+ } else if (path == "rotation") {
+ const Vector<Quaternion> rotations = _decode_accessor_as_quaternion(p_state, output_value_accessor_index, false);
+ track->rotation_track.interpolation = interp;
+ track->rotation_track.times = times;
+ track->rotation_track.values = rotations;
+ } else if (path == "scale") {
+ const Vector<Vector3> scales = _decode_accessor_as_vec3(p_state, output_value_accessor_index, false);
+ track->scale_track.interpolation = interp;
+ track->scale_track.times = times;
+ track->scale_track.values = scales;
+ } else if (path == "weights") {
+ const Vector<float> weights = _decode_accessor_as_floats(p_state, output_value_accessor_index, false);
+
+ ERR_FAIL_INDEX_V(p_state->nodes[node]->mesh, p_state->meshes.size(), ERR_PARSE_ERROR);
+ Ref<GLTFMesh> mesh = p_state->meshes[p_state->nodes[node]->mesh];
+ const int wc = mesh->get_blend_weights().size();
+ ERR_CONTINUE_MSG(wc == 0, "glTF: Animation tried to animate weights, but mesh has no weights.");
+
+ track->weight_tracks.resize(wc);
+
+ const int expected_value_count = times.size() * output_count * wc;
+ ERR_CONTINUE_MSG(weights.size() != expected_value_count, "Invalid weight data, expected " + itos(expected_value_count) + " weight values, got " + itos(weights.size()) + " instead.");
+
+ const int wlen = weights.size() / wc;
+ for (int k = 0; k < wc; k++) { //separate tracks, having them together is not such a good idea
+ GLTFAnimation::Channel<real_t> cf;
+ cf.interpolation = interp;
+ cf.times = Variant(times);
+ Vector<real_t> wdata;
+ wdata.resize(wlen);
+ for (int l = 0; l < wlen; l++) {
+ wdata.write[l] = weights[l * wc + k];
+ }
+
+ cf.values = wdata;
+ track->weight_tracks.write[k] = cf;
+ }
+ } else {
+ WARN_PRINT("Invalid path '" + path + "'.");
}
- } else {
- WARN_PRINT("Invalid path '" + path + "'.");
}
}
@@ -5089,6 +5563,96 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> p_state) {
return OK;
}
+void GLTFDocument::_parse_animation_pointer(Ref<GLTFState> p_state, const String &p_animation_json_pointer, const Ref<GLTFAnimation> p_gltf_animation, const GLTFAnimation::Interpolation p_interp, const Vector<double> &p_times, const int p_output_value_accessor_index) {
+ // Special case: Convert TRS animation pointers to node track pos/rot/scale.
+ // This is required to handle skeleton bones, and improves performance for regular nodes.
+ // Mark this as unlikely because TRS animation pointers are not recommended,
+ // since vanilla glTF animations can already animate TRS properties directly.
+ // But having this code exist is required to be spec-compliant and handle all test files.
+ // Note that TRS still needs to be handled in the general case as well, for KHR_interactivity.
+ const PackedStringArray split = p_animation_json_pointer.split("/", false, 3);
+ if (unlikely(split.size() == 3 && split[0] == "nodes" && (split[2] == "translation" || split[2] == "rotation" || split[2] == "scale" || split[2] == "matrix" || split[2] == "weights"))) {
+ const GLTFNodeIndex node_index = split[1].to_int();
+ HashMap<int, GLTFAnimation::NodeTrack> &node_tracks = p_gltf_animation->get_node_tracks();
+ if (!node_tracks.has(node_index)) {
+ node_tracks[node_index] = GLTFAnimation::NodeTrack();
+ }
+ GLTFAnimation::NodeTrack *track = &node_tracks[node_index];
+ if (split[2] == "translation") {
+ const Vector<Vector3> positions = _decode_accessor_as_vec3(p_state, p_output_value_accessor_index, false);
+ track->position_track.interpolation = p_interp;
+ track->position_track.times = p_times;
+ track->position_track.values = positions;
+ } else if (split[2] == "rotation") {
+ const Vector<Quaternion> rotations = _decode_accessor_as_quaternion(p_state, p_output_value_accessor_index, false);
+ track->rotation_track.interpolation = p_interp;
+ track->rotation_track.times = p_times;
+ track->rotation_track.values = rotations;
+ } else if (split[2] == "scale") {
+ const Vector<Vector3> scales = _decode_accessor_as_vec3(p_state, p_output_value_accessor_index, false);
+ track->scale_track.interpolation = p_interp;
+ track->scale_track.times = p_times;
+ track->scale_track.values = scales;
+ } else if (split[2] == "matrix") {
+ const Vector<Transform3D> transforms = _decode_accessor_as_xform(p_state, p_output_value_accessor_index, false);
+ track->position_track.interpolation = p_interp;
+ track->position_track.times = p_times;
+ track->position_track.values.resize(transforms.size());
+ track->rotation_track.interpolation = p_interp;
+ track->rotation_track.times = p_times;
+ track->rotation_track.values.resize(transforms.size());
+ track->scale_track.interpolation = p_interp;
+ track->scale_track.times = p_times;
+ track->scale_track.values.resize(transforms.size());
+ for (int i = 0; i < transforms.size(); i++) {
+ track->position_track.values.write[i] = transforms[i].get_origin();
+ track->rotation_track.values.write[i] = transforms[i].basis.get_rotation_quaternion();
+ track->scale_track.values.write[i] = transforms[i].basis.get_scale();
+ }
+ } else { // if (split[2] == "weights")
+ const Vector<float> accessor_weights = _decode_accessor_as_floats(p_state, p_output_value_accessor_index, false);
+ const GLTFMeshIndex mesh_index = p_state->nodes[node_index]->mesh;
+ ERR_FAIL_INDEX(mesh_index, p_state->meshes.size());
+ const Ref<GLTFMesh> gltf_mesh = p_state->meshes[mesh_index];
+ const Vector<float> &blend_weights = gltf_mesh->get_blend_weights();
+ const int blend_weight_count = gltf_mesh->get_blend_weights().size();
+ const int anim_weights_size = accessor_weights.size();
+ // For example, if a mesh has 2 blend weights, and the accessor provides 10 values, then there are 5 frames of animation, each with 2 blend weights.
+ ERR_FAIL_COND_MSG(blend_weight_count == 0 || ((anim_weights_size % blend_weight_count) != 0), "glTF: Cannot apply " + itos(accessor_weights.size()) + " weights to a mesh with " + itos(blend_weights.size()) + " blend weights.");
+ const int frame_count = anim_weights_size / blend_weight_count;
+ track->weight_tracks.resize(blend_weight_count);
+ for (int blend_weight_index = 0; blend_weight_index < blend_weight_count; blend_weight_index++) {
+ GLTFAnimation::Channel<real_t> weight_track;
+ weight_track.interpolation = p_interp;
+ weight_track.times = p_times;
+ weight_track.values.resize(frame_count);
+ for (int frame_index = 0; frame_index < frame_count; frame_index++) {
+ // For example, if a mesh has 2 blend weights, and the accessor provides 10 values,
+ // then the first frame has indices [0, 1], the second frame has [2, 3], and so on.
+ // Here we process all frames of one blend weight, so we want [0, 2, 4, 6, 8] or [1, 3, 5, 7, 9].
+ // For the fist one we calculate 0 * 2 + 0, 1 * 2 + 0, 2 * 2 + 0, etc, then for the second 0 * 2 + 1, 1 * 2 + 1, 2 * 2 + 1, etc.
+ weight_track.values.write[frame_index] = accessor_weights[frame_index * blend_weight_count + blend_weight_index];
+ }
+ track->weight_tracks.write[blend_weight_index] = weight_track;
+ }
+ }
+ // The special case was handled, return to skip the general case.
+ return;
+ }
+ // General case: Convert animation pointers to Variant value pointer tracks.
+ Ref<GLTFObjectModelProperty> obj_model_prop = import_object_model_property(p_state, p_animation_json_pointer);
+ if (obj_model_prop.is_null() || !obj_model_prop->has_node_paths()) {
+ // Exit quietly, `import_object_model_property` already prints a warning if the property is not found.
+ return;
+ }
+ HashMap<String, GLTFAnimation::Channel<Variant>> &anim_ptr_map = p_gltf_animation->get_pointer_tracks();
+ GLTFAnimation::Channel<Variant> channel;
+ channel.interpolation = p_interp;
+ channel.times = p_times;
+ channel.values = _decode_accessor_as_variant(p_state, p_output_value_accessor_index, obj_model_prop->get_variant_type(), obj_model_prop->get_accessor_type());
+ anim_ptr_map[p_animation_json_pointer] = channel;
+}
+
void GLTFDocument::_assign_node_names(Ref<GLTFState> p_state) {
for (int i = 0; i < p_state->nodes.size(); i++) {
Ref<GLTFNode> gltf_node = p_state->nodes[i];
@@ -5254,46 +5818,46 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current,
gltf_node->set_original_name(p_current->get_name());
gltf_node->set_name(_gen_unique_name(p_state, p_current->get_name()));
gltf_node->merge_meta_from(p_current);
- if (cast_to<Node3D>(p_current)) {
- Node3D *spatial = cast_to<Node3D>(p_current);
+ if (Object::cast_to<Node3D>(p_current)) {
+ Node3D *spatial = Object::cast_to<Node3D>(p_current);
_convert_spatial(p_state, spatial, gltf_node);
}
- if (cast_to<MeshInstance3D>(p_current)) {
- MeshInstance3D *mi = cast_to<MeshInstance3D>(p_current);
+ if (Object::cast_to<MeshInstance3D>(p_current)) {
+ MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_current);
_convert_mesh_instance_to_gltf(mi, p_state, gltf_node);
- } else if (cast_to<BoneAttachment3D>(p_current)) {
- BoneAttachment3D *bone = cast_to<BoneAttachment3D>(p_current);
+ } else if (Object::cast_to<BoneAttachment3D>(p_current)) {
+ BoneAttachment3D *bone = Object::cast_to<BoneAttachment3D>(p_current);
_convert_bone_attachment_to_gltf(bone, p_state, p_gltf_parent, p_gltf_root, gltf_node);
return;
- } else if (cast_to<Skeleton3D>(p_current)) {
- Skeleton3D *skel = cast_to<Skeleton3D>(p_current);
+ } else if (Object::cast_to<Skeleton3D>(p_current)) {
+ Skeleton3D *skel = Object::cast_to<Skeleton3D>(p_current);
_convert_skeleton_to_gltf(skel, p_state, p_gltf_parent, p_gltf_root, gltf_node);
// We ignore the Godot Engine node that is the skeleton.
return;
- } else if (cast_to<MultiMeshInstance3D>(p_current)) {
- MultiMeshInstance3D *multi = cast_to<MultiMeshInstance3D>(p_current);
+ } else if (Object::cast_to<MultiMeshInstance3D>(p_current)) {
+ MultiMeshInstance3D *multi = Object::cast_to<MultiMeshInstance3D>(p_current);
_convert_multi_mesh_instance_to_gltf(multi, p_gltf_parent, p_gltf_root, gltf_node, p_state);
#ifdef MODULE_CSG_ENABLED
- } else if (cast_to<CSGShape3D>(p_current)) {
- CSGShape3D *shape = cast_to<CSGShape3D>(p_current);
+ } else if (Object::cast_to<CSGShape3D>(p_current)) {
+ CSGShape3D *shape = Object::cast_to<CSGShape3D>(p_current);
if (shape->get_parent() && shape->is_root_shape()) {
_convert_csg_shape_to_gltf(shape, p_gltf_parent, gltf_node, p_state);
}
#endif // MODULE_CSG_ENABLED
#ifdef MODULE_GRIDMAP_ENABLED
- } else if (cast_to<GridMap>(p_current)) {
+ } else if (Object::cast_to<GridMap>(p_current)) {
GridMap *gridmap = Object::cast_to<GridMap>(p_current);
_convert_grid_map_to_gltf(gridmap, p_gltf_parent, p_gltf_root, gltf_node, p_state);
#endif // MODULE_GRIDMAP_ENABLED
- } else if (cast_to<Camera3D>(p_current)) {
+ } else if (Object::cast_to<Camera3D>(p_current)) {
Camera3D *camera = Object::cast_to<Camera3D>(p_current);
_convert_camera_to_gltf(camera, p_state, gltf_node);
- } else if (cast_to<Light3D>(p_current)) {
+ } else if (Object::cast_to<Light3D>(p_current)) {
Light3D *light = Object::cast_to<Light3D>(p_current);
_convert_light_to_gltf(light, p_state, gltf_node);
- } else if (cast_to<AnimationPlayer>(p_current)) {
+ } else if (Object::cast_to<AnimationPlayer>(p_current)) {
AnimationPlayer *animation_player = Object::cast_to<AnimationPlayer>(p_current);
- _convert_animation_player_to_gltf(animation_player, p_state, p_gltf_parent, p_gltf_root, gltf_node, p_current);
+ p_state->animation_players.push_back(animation_player);
}
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
@@ -5371,12 +5935,6 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd
}
#endif // MODULE_CSG_ENABLED
-void GLTFDocument::_convert_animation_player_to_gltf(AnimationPlayer *p_animation_player, Ref<GLTFState> p_state, GLTFNodeIndex p_gltf_current, GLTFNodeIndex p_gltf_root_index, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) {
- ERR_FAIL_NULL(p_animation_player);
- p_state->animation_players.push_back(p_animation_player);
- print_verbose(String("glTF: Converting animation player: ") + p_animation_player->get_name());
-}
-
void GLTFDocument::_check_visibility(Node *p_node, bool &r_retflag) {
r_retflag = true;
Node3D *spatial = Object::cast_to<Node3D>(p_node);
@@ -5569,9 +6127,9 @@ void GLTFDocument::_convert_bone_attachment_to_gltf(BoneAttachment3D *p_bone_att
Skeleton3D *skeleton;
// Note that relative transforms to external skeletons and pose overrides are not supported.
if (p_bone_attachment->get_use_external_skeleton()) {
- skeleton = cast_to<Skeleton3D>(p_bone_attachment->get_node_or_null(p_bone_attachment->get_external_skeleton()));
+ skeleton = Object::cast_to<Skeleton3D>(p_bone_attachment->get_node_or_null(p_bone_attachment->get_external_skeleton()));
} else {
- skeleton = cast_to<Skeleton3D>(p_bone_attachment->get_parent());
+ skeleton = Object::cast_to<Skeleton3D>(p_bone_attachment->get_parent());
}
GLTFSkeletonIndex skel_gltf_i = -1;
if (skeleton != nullptr && p_state->skeleton3d_to_gltf_skeleton.has(skeleton->get_instance_id())) {
@@ -5854,7 +6412,7 @@ struct SceneFormatImporterGLTFInterpolate<Quaternion> {
};
template <typename T>
-T GLTFDocument::_interpolate_track(const Vector<real_t> &p_times, const Vector<T> &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp) {
+T GLTFDocument::_interpolate_track(const Vector<double> &p_times, const Vector<T> &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp) {
ERR_FAIL_COND_V(p_values.is_empty(), T());
if (p_times.size() != (p_values.size() / (p_interp == GLTFAnimation::INTERP_CUBIC_SPLINE ? 3 : 1))) {
ERR_PRINT_ONCE("The interpolated values are not corresponding to its times.");
@@ -5925,8 +6483,433 @@ T GLTFDocument::_interpolate_track(const Vector<real_t> &p_times, const Vector<T
ERR_FAIL_V(p_values[0]);
}
+NodePath GLTFDocument::_find_material_node_path(Ref<GLTFState> p_state, Ref<Material> p_material) {
+ int mesh_index = 0;
+ for (Ref<GLTFMesh> gltf_mesh : p_state->meshes) {
+ TypedArray<Material> materials = gltf_mesh->get_instance_materials();
+ for (int mat_index = 0; mat_index < materials.size(); mat_index++) {
+ if (materials[mat_index] == p_material) {
+ for (Ref<GLTFNode> gltf_node : p_state->nodes) {
+ if (gltf_node->mesh == mesh_index) {
+ NodePath node_path = gltf_node->get_scene_node_path(p_state);
+ // Example: MyNode:mesh:surface_0/material:albedo_color, so we want the mesh:surface_0/material part.
+ Vector<StringName> subpath;
+ subpath.append("mesh");
+ subpath.append("surface_" + itos(mat_index) + "/material");
+ return NodePath(node_path.get_names(), subpath, false);
+ }
+ }
+ }
+ }
+ mesh_index++;
+ }
+ return NodePath();
+}
+
+Ref<GLTFObjectModelProperty> GLTFDocument::import_object_model_property(Ref<GLTFState> p_state, const String &p_json_pointer) {
+ if (p_state->object_model_properties.has(p_json_pointer)) {
+ return p_state->object_model_properties[p_json_pointer];
+ }
+ Ref<GLTFObjectModelProperty> ret;
+ // Split the JSON pointer into its components.
+ const PackedStringArray split = p_json_pointer.split("/", false);
+ ERR_FAIL_COND_V_MSG(split.size() < 3, ret, "glTF: Cannot use JSON pointer '" + p_json_pointer + "' because it does not contain enough elements. The only animatable properties are at least 3 levels deep (ex: '/nodes/0/translation' or '/materials/0/emissiveFactor').");
+ ret.instantiate();
+ ret->set_json_pointers({ split });
+ // Partial paths are passed to GLTFDocumentExtension classes if GLTFDocument cannot handle a given JSON pointer.
+ TypedArray<NodePath> partial_paths;
+ // Note: This might not be an integer, but in that case, we don't use this value anyway.
+ const int top_level_index = split[1].to_int();
+ // For JSON pointers present in the core glTF Object Model, hard-code them in GLTFDocument.
+ // https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/ObjectModel.adoc
+ if (split[0] == "nodes") {
+ ERR_FAIL_INDEX_V_MSG(top_level_index, p_state->nodes.size(), ret, vformat("glTF: Unable to find node %d for JSON pointer '%s'.", top_level_index, p_json_pointer));
+ Ref<GLTFNode> pointed_gltf_node = p_state->nodes[top_level_index];
+ NodePath node_path = pointed_gltf_node->get_scene_node_path(p_state);
+ partial_paths.append(node_path);
+ // Check if it's something we should be able to handle.
+ const String &node_prop = split[2];
+ if (node_prop == "translation") {
+ ret->append_path_to_property(node_path, "position");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (node_prop == "rotation") {
+ ret->append_path_to_property(node_path, "quaternion");
+ ret->set_types(Variant::QUATERNION, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4);
+ } else if (node_prop == "scale") {
+ ret->append_path_to_property(node_path, "scale");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (node_prop == "matrix") {
+ ret->append_path_to_property(node_path, "transform");
+ ret->set_types(Variant::TRANSFORM3D, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4X4);
+ } else if (node_prop == "globalMatrix") {
+ ret->append_path_to_property(node_path, "global_transform");
+ ret->set_types(Variant::TRANSFORM3D, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4X4);
+ } else if (node_prop == "weights") {
+ if (split.size() > 3) {
+ const String &weight_index_string = split[3];
+ ret->append_path_to_property(node_path, "blend_shapes/morph_" + weight_index_string);
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ }
+ // Else, Godot's MeshInstance3D does not expose the blend shape weights as one property.
+ // But that's fine, we handle this case in _parse_animation_pointer instead.
+ }
+ } else if (split[0] == "cameras") {
+ const String &camera_prop = split[2];
+ for (Ref<GLTFNode> gltf_node : p_state->nodes) {
+ if (gltf_node->camera == top_level_index) {
+ NodePath node_path = gltf_node->get_scene_node_path(p_state);
+ partial_paths.append(node_path);
+ // Check if it's something we should be able to handle.
+ if (camera_prop == "orthographic" || camera_prop == "perspective") {
+ ERR_FAIL_COND_V(split.size() < 4, ret);
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ const String &sub_prop = split[3];
+ if (sub_prop == "xmag" || sub_prop == "ymag") {
+ ret->append_path_to_property(node_path, "size");
+ } else if (sub_prop == "yfov") {
+ ret->append_path_to_property(node_path, "fov");
+ GLTFCamera::set_fov_conversion_expressions(ret);
+ } else if (sub_prop == "zfar") {
+ ret->append_path_to_property(node_path, "far");
+ } else if (sub_prop == "znear") {
+ ret->append_path_to_property(node_path, "near");
+ }
+ }
+ }
+ }
+ } else if (split[0] == "materials") {
+ ERR_FAIL_INDEX_V_MSG(top_level_index, p_state->materials.size(), ret, vformat("glTF: Unable to find material %d for JSON pointer '%s'.", top_level_index, p_json_pointer));
+ Ref<Material> pointed_material = p_state->materials[top_level_index];
+ NodePath mat_path = _find_material_node_path(p_state, pointed_material);
+ if (mat_path.is_empty()) {
+ WARN_PRINT(vformat("glTF: Unable to find a path to the material %d for JSON pointer '%s'. This is likely bad data but it's also possible this is intentional. Continuing anyway.", top_level_index, p_json_pointer));
+ } else {
+ partial_paths.append(mat_path);
+ const String &mat_prop = split[2];
+ if (mat_prop == "alphaCutoff") {
+ ret->append_path_to_property(mat_path, "alpha_scissor_threshold");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (mat_prop == "emissiveFactor") {
+ ret->append_path_to_property(mat_path, "emission");
+ ret->set_types(Variant::COLOR, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (mat_prop == "extensions") {
+ ERR_FAIL_COND_V(split.size() < 5, ret);
+ const String &ext_name = split[3];
+ const String &ext_prop = split[4];
+ if (ext_name == "KHR_materials_emissive_strength" && ext_prop == "emissiveStrength") {
+ ret->append_path_to_property(mat_path, "emission_energy_multiplier");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ }
+ } else {
+ ERR_FAIL_COND_V(split.size() < 4, ret);
+ const String &sub_prop = split[3];
+ if (mat_prop == "normalTexture") {
+ if (sub_prop == "scale") {
+ ret->append_path_to_property(mat_path, "normal_scale");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ }
+ } else if (mat_prop == "occlusionTexture") {
+ if (sub_prop == "strength") {
+ // This is the closest thing Godot has to an occlusion strength property.
+ ret->append_path_to_property(mat_path, "ao_light_affect");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ }
+ } else if (mat_prop == "pbrMetallicRoughness") {
+ if (sub_prop == "baseColorFactor") {
+ ret->append_path_to_property(mat_path, "albedo_color");
+ ret->set_types(Variant::COLOR, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4);
+ } else if (sub_prop == "metallicFactor") {
+ ret->append_path_to_property(mat_path, "metallic");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (sub_prop == "roughnessFactor") {
+ ret->append_path_to_property(mat_path, "roughness");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (sub_prop == "baseColorTexture") {
+ ERR_FAIL_COND_V(split.size() < 6, ret);
+ const String &tex_ext_dict = split[4];
+ const String &tex_ext_name = split[5];
+ const String &tex_ext_prop = split[6];
+ if (tex_ext_dict == "extensions" && tex_ext_name == "KHR_texture_transform") {
+ // Godot only supports UVs for the whole material, not per texture.
+ // We treat the albedo texture as the main texture, and import as UV1.
+ // Godot does not support texture rotation, only offset and scale.
+ if (tex_ext_prop == "offset") {
+ ret->append_path_to_property(mat_path, "uv1_offset");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT2);
+ } else if (tex_ext_prop == "scale") {
+ ret->append_path_to_property(mat_path, "uv1_scale");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT2);
+ }
+ }
+ }
+ }
+ }
+ }
+ } else if (split[0] == "meshes") {
+ for (Ref<GLTFNode> gltf_node : p_state->nodes) {
+ if (gltf_node->mesh == top_level_index) {
+ NodePath node_path = gltf_node->get_scene_node_path(p_state);
+ Vector<StringName> subpath;
+ subpath.append("mesh");
+ partial_paths.append(NodePath(node_path.get_names(), subpath, false));
+ break;
+ }
+ }
+ } else if (split[0] == "extensions") {
+ if (split[1] == "KHR_lights_punctual" && split[2] == "lights" && split.size() > 4) {
+ const int light_index = split[3].to_int();
+ ERR_FAIL_INDEX_V_MSG(light_index, p_state->lights.size(), ret, vformat("glTF: Unable to find light %d for JSON pointer '%s'.", light_index, p_json_pointer));
+ const String &light_prop = split[4];
+ const Ref<GLTFLight> pointed_light = p_state->lights[light_index];
+ for (Ref<GLTFNode> gltf_node : p_state->nodes) {
+ if (gltf_node->light == light_index) {
+ NodePath node_path = gltf_node->get_scene_node_path(p_state);
+ partial_paths.append(node_path);
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ // Check if it's something we should be able to handle.
+ if (light_prop == "color") {
+ ret->append_path_to_property(node_path, "light_color");
+ ret->set_types(Variant::COLOR, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (light_prop == "intensity") {
+ ret->append_path_to_property(node_path, "light_energy");
+ } else if (light_prop == "range") {
+ const String &light_type = p_state->lights[light_index]->light_type;
+ if (light_type == "spot") {
+ ret->append_path_to_property(node_path, "spot_range");
+ } else {
+ ret->append_path_to_property(node_path, "omni_range");
+ }
+ } else if (light_prop == "spot") {
+ ERR_FAIL_COND_V(split.size() < 6, ret);
+ const String &sub_prop = split[5];
+ if (sub_prop == "innerConeAngle") {
+ ret->append_path_to_property(node_path, "spot_angle_attenuation");
+ GLTFLight::set_cone_inner_attenuation_conversion_expressions(ret);
+ } else if (sub_prop == "outerConeAngle") {
+ ret->append_path_to_property(node_path, "spot_angle");
+ }
+ }
+ }
+ }
+ }
+ }
+ // Additional JSON pointers can be added by GLTFDocumentExtension classes.
+ // We only need this if no mapping has been found yet from GLTFDocument's internal code.
+ // When available, we pass the partial paths to the extension to help it generate the full path.
+ // For example, for `/nodes/3/extensions/MY_ext/prop`, we pass a NodePath that leads to node 3,
+ // so the GLTFDocumentExtension class only needs to resolve the last `MY_ext/prop` part of the path.
+ // It should check `split.size() > 4 and split[0] == "nodes" and split[2] == "extensions" and split[3] == "MY_ext"`
+ // at the start of the function to check if this JSON pointer applies to it, then it can handle `split[4]`.
+ if (!ret->has_node_paths()) {
+ for (Ref<GLTFDocumentExtension> ext : all_document_extensions) {
+ ret = ext->import_object_model_property(p_state, split, partial_paths);
+ if (ret.is_valid() && ret->has_node_paths()) {
+ if (!ret->has_json_pointers()) {
+ ret->set_json_pointers({ split });
+ }
+ break;
+ }
+ }
+ if (ret.is_null() || !ret->has_node_paths()) {
+ if (split.has("KHR_texture_transform")) {
+ WARN_VERBOSE(vformat("glTF: Texture transforms are only supported per material in Godot. All KHR_texture_transform properties will be ignored except for the albedo texture. Ignoring JSON pointer '%s'.", p_json_pointer));
+ } else {
+ WARN_PRINT(vformat("glTF: Animation contained JSON pointer '%s' which could not be resolved. This property will not be animated.", p_json_pointer));
+ }
+ }
+ }
+ p_state->object_model_properties[p_json_pointer] = ret;
+ return ret;
+}
+
+Ref<GLTFObjectModelProperty> GLTFDocument::export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index) {
+ Ref<GLTFObjectModelProperty> ret;
+ const Object *target_object = p_godot_node;
+ const Vector<StringName> subpath = p_node_path.get_subnames();
+ ERR_FAIL_COND_V_MSG(subpath.is_empty(), ret, "glTF: Cannot export empty property. No property was specified in the NodePath: " + p_node_path);
+ int target_prop_depth = 0;
+ for (StringName subname : subpath) {
+ Variant target_property = target_object->get(subname);
+ if (target_property.get_type() == Variant::OBJECT) {
+ target_object = target_property;
+ if (target_object) {
+ target_prop_depth++;
+ continue;
+ }
+ }
+ break;
+ }
+ const String &target_prop = subpath[target_prop_depth];
+ ret.instantiate();
+ ret->set_node_paths({ p_node_path });
+ Vector<PackedStringArray> split_json_pointers;
+ PackedStringArray split_json_pointer;
+ if (Object::cast_to<BaseMaterial3D>(target_object)) {
+ for (int i = 0; i < p_state->materials.size(); i++) {
+ if (p_state->materials[i].ptr() == target_object) {
+ split_json_pointer.append("materials");
+ split_json_pointer.append(itos(i));
+ if (target_prop == "alpha_scissor_threshold") {
+ split_json_pointer.append("alphaCutoff");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (target_prop == "emission") {
+ split_json_pointer.append("emissiveFactor");
+ ret->set_types(Variant::COLOR, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (target_prop == "emission_energy_multiplier") {
+ split_json_pointer.append("extensions");
+ split_json_pointer.append("KHR_materials_emissive_strength");
+ split_json_pointer.append("emissiveStrength");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (target_prop == "normal_scale") {
+ split_json_pointer.append("normalTexture");
+ split_json_pointer.append("scale");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (target_prop == "ao_light_affect") {
+ split_json_pointer.append("occlusionTexture");
+ split_json_pointer.append("strength");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (target_prop == "albedo_color") {
+ split_json_pointer.append("pbrMetallicRoughness");
+ split_json_pointer.append("baseColorFactor");
+ ret->set_types(Variant::COLOR, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4);
+ } else if (target_prop == "metallic") {
+ split_json_pointer.append("pbrMetallicRoughness");
+ split_json_pointer.append("metallicFactor");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (target_prop == "roughness") {
+ split_json_pointer.append("pbrMetallicRoughness");
+ split_json_pointer.append("roughnessFactor");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (target_prop == "uv1_offset" || target_prop == "uv1_scale") {
+ split_json_pointer.append("pbrMetallicRoughness");
+ split_json_pointer.append("baseColorTexture");
+ split_json_pointer.append("extensions");
+ split_json_pointer.append("KHR_texture_transform");
+ if (target_prop == "uv1_offset") {
+ split_json_pointer.append("offset");
+ } else {
+ split_json_pointer.append("scale");
+ }
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT2);
+ } else {
+ split_json_pointer.clear();
+ }
+ break;
+ }
+ }
+ } else {
+ // Properties directly on Godot nodes.
+ Ref<GLTFNode> gltf_node = p_state->nodes[p_gltf_node_index];
+ if (Object::cast_to<Camera3D>(target_object) && gltf_node->camera >= 0) {
+ split_json_pointer.append("cameras");
+ split_json_pointer.append(itos(gltf_node->camera));
+ const Camera3D *camera_node = Object::cast_to<Camera3D>(target_object);
+ const Camera3D::ProjectionType projection_type = camera_node->get_projection();
+ if (projection_type == Camera3D::PROJECTION_PERSPECTIVE) {
+ split_json_pointer.append("perspective");
+ } else {
+ split_json_pointer.append("orthographic");
+ }
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ if (target_prop == "size") {
+ PackedStringArray xmag = split_json_pointer.duplicate();
+ xmag.append("xmag");
+ split_json_pointers.append(xmag);
+ split_json_pointer.append("ymag");
+ } else if (target_prop == "fov") {
+ split_json_pointer.append("yfov");
+ GLTFCamera::set_fov_conversion_expressions(ret);
+ } else if (target_prop == "far") {
+ split_json_pointer.append("zfar");
+ } else if (target_prop == "near") {
+ split_json_pointer.append("znear");
+ } else {
+ split_json_pointer.clear();
+ }
+ } else if (Object::cast_to<Light3D>(target_object) && gltf_node->light >= 0) {
+ split_json_pointer.append("extensions");
+ split_json_pointer.append("KHR_lights_punctual");
+ split_json_pointer.append("lights");
+ split_json_pointer.append(itos(gltf_node->light));
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ if (target_prop == "light_color") {
+ split_json_pointer.append("color");
+ ret->set_types(Variant::COLOR, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (target_prop == "light_energy") {
+ split_json_pointer.append("intensity");
+ } else if (target_prop == "spot_range") {
+ split_json_pointer.append("range");
+ } else if (target_prop == "omni_range") {
+ split_json_pointer.append("range");
+ } else if (target_prop == "spot_angle") {
+ split_json_pointer.append("spot");
+ split_json_pointer.append("outerConeAngle");
+ } else if (target_prop == "spot_angle_attenuation") {
+ split_json_pointer.append("spot");
+ split_json_pointer.append("innerConeAngle");
+ GLTFLight::set_cone_inner_attenuation_conversion_expressions(ret);
+ } else {
+ split_json_pointer.clear();
+ }
+ } else if (Object::cast_to<MeshInstance3D>(target_object) && target_prop.begins_with("blend_shapes/morph_")) {
+ const String &weight_index_string = target_prop.trim_prefix("blend_shapes/morph_");
+ split_json_pointer.append("nodes");
+ split_json_pointer.append(itos(p_gltf_node_index));
+ split_json_pointer.append("weights");
+ split_json_pointer.append(weight_index_string);
+ }
+ // Transform properties. Check for all 3D nodes if we haven't resolved the JSON pointer yet.
+ // Note: Do not put this in an `else`, because otherwise this will not be reached.
+ if (split_json_pointer.is_empty() && Object::cast_to<Node3D>(target_object)) {
+ split_json_pointer.append("nodes");
+ split_json_pointer.append(itos(p_gltf_node_index));
+ if (target_prop == "position") {
+ split_json_pointer.append("translation");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (target_prop == "quaternion") {
+ // Note: Only Quaternion rotation can be converted from Godot in this mapping.
+ // Struct methods like from_euler are not accessible from the Expression class. :(
+ split_json_pointer.append("rotation");
+ ret->set_types(Variant::QUATERNION, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4);
+ } else if (target_prop == "scale") {
+ split_json_pointer.append("scale");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (target_prop == "transform") {
+ split_json_pointer.append("matrix");
+ ret->set_types(Variant::TRANSFORM3D, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4X4);
+ } else if (target_prop == "global_transform") {
+ split_json_pointer.append("globalMatrix");
+ ret->set_types(Variant::TRANSFORM3D, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4X4);
+ } else {
+ split_json_pointer.clear();
+ }
+ }
+ }
+ // Additional JSON pointers can be added by GLTFDocumentExtension classes.
+ // We only need this if no mapping has been found yet from GLTFDocument's internal code.
+ // We pass as many pieces of information as we can to the extension to give it lots of context.
+ if (split_json_pointer.is_empty()) {
+ for (Ref<GLTFDocumentExtension> ext : all_document_extensions) {
+ ret = ext->export_object_model_property(p_state, p_node_path, p_godot_node, p_gltf_node_index, target_object, target_prop_depth);
+ if (ret.is_valid() && ret->has_json_pointers()) {
+ if (!ret->has_node_paths()) {
+ ret->set_node_paths({ p_node_path });
+ }
+ break;
+ }
+ }
+ } else {
+ // GLTFDocument's internal code found a mapping, so set it and return it.
+ split_json_pointers.append(split_json_pointer);
+ ret->set_json_pointers(split_json_pointers);
+ }
+ return ret;
+}
+
void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, const GLTFAnimationIndex p_index, const bool p_trimming, const bool p_remove_immutable_tracks) {
ERR_FAIL_COND(p_state.is_null());
+ Node *scene_root = p_animation_player->get_parent();
+ ERR_FAIL_NULL(scene_root);
Ref<GLTFAnimation> anim = p_state->animations[p_index];
String anim_name = anim->get_name();
@@ -5947,8 +6930,8 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
double anim_start = p_trimming ? INFINITY : 0.0;
double anim_end = 0.0;
- for (const KeyValue<int, GLTFAnimation::Track> &track_i : anim->get_tracks()) {
- const GLTFAnimation::Track &track = track_i.value;
+ for (const KeyValue<int, GLTFAnimation::NodeTrack> &track_i : anim->get_node_tracks()) {
+ const GLTFAnimation::NodeTrack &track = track_i.value;
//need to find the path: for skeletons, weight tracks will affect the mesh
NodePath node_path;
//for skeletons, transform tracks always affect bones
@@ -5960,14 +6943,12 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
const Ref<GLTFNode> gltf_node = p_state->nodes[track_i.key];
- Node *root = p_animation_player->get_parent();
- ERR_FAIL_NULL(root);
HashMap<GLTFNodeIndex, Node *>::Iterator node_element = p_state->scene_nodes.find(node_index);
ERR_CONTINUE_MSG(!node_element, vformat("Unable to find node %d for animation.", node_index));
- node_path = root->get_path_to(node_element->value);
+ node_path = scene_root->get_path_to(node_element->value);
HashMap<GLTFNodeIndex, ImporterMeshInstance3D *>::Iterator mesh_instance_element = p_state->scene_mesh_instances.find(node_index);
if (mesh_instance_element) {
- mesh_instance_node_path = root->get_path_to(mesh_instance_element->value);
+ mesh_instance_node_path = scene_root->get_path_to(mesh_instance_element->value);
} else {
mesh_instance_node_path = node_path;
}
@@ -6201,6 +7182,56 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
}
}
+ for (const KeyValue<String, GLTFAnimation::Channel<Variant>> &track_iter : anim->get_pointer_tracks()) {
+ // Determine the property to animate.
+ const String json_pointer = track_iter.key;
+ const Ref<GLTFObjectModelProperty> prop = import_object_model_property(p_state, json_pointer);
+ ERR_FAIL_COND(prop.is_null());
+ // Adjust the animation duration to encompass all keyframes.
+ const GLTFAnimation::Channel<Variant> &channel = track_iter.value;
+ ERR_CONTINUE_MSG(channel.times.size() != channel.values.size(), vformat("glTF: Animation pointer '%s' has mismatched keyframe times and values.", json_pointer));
+ if (p_trimming) {
+ for (int i = 0; i < channel.times.size(); i++) {
+ anim_start = MIN(anim_start, channel.times[i]);
+ anim_end = MAX(anim_end, channel.times[i]);
+ }
+ } else {
+ for (int i = 0; i < channel.times.size(); i++) {
+ anim_end = MAX(anim_end, channel.times[i]);
+ }
+ }
+ // Begin converting the glTF animation to a Godot animation.
+ const Ref<Expression> gltf_to_godot_expr = prop->get_gltf_to_godot_expression();
+ const bool is_gltf_to_godot_expr_valid = gltf_to_godot_expr.is_valid();
+ for (const NodePath node_path : prop->get_node_paths()) {
+ // If using an expression, determine the base instance to pass to the expression.
+ Object *base_instance = nullptr;
+ if (is_gltf_to_godot_expr_valid) {
+ Ref<Resource> resource;
+ Vector<StringName> leftover_subpath;
+ base_instance = scene_root->get_node_and_resource(node_path, resource, leftover_subpath);
+ if (resource.is_valid()) {
+ base_instance = resource.ptr();
+ }
+ }
+ // Add a track and insert all keys and values.
+ const int track_index = animation->get_track_count();
+ animation->add_track(Animation::TYPE_VALUE);
+ animation->track_set_interpolation_type(track_index, GLTFAnimation::gltf_to_godot_interpolation(channel.interpolation));
+ animation->track_set_path(track_index, node_path);
+ for (int i = 0; i < channel.times.size(); i++) {
+ const double time = channel.times[i];
+ Variant value = channel.values[i];
+ if (is_gltf_to_godot_expr_valid) {
+ Array inputs;
+ inputs.append(value);
+ value = gltf_to_godot_expr->execute(inputs, base_instance);
+ }
+ animation->track_insert_key(track_index, time, value);
+ }
+ }
+ }
+
animation->set_length(anim_end - anim_start);
Ref<AnimationLibrary> library;
@@ -6371,41 +7402,56 @@ void GLTFDocument::_process_mesh_instances(Ref<GLTFState> p_state, Node *p_scene
}
}
-GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_state, GLTFAnimation::Track p_track, Ref<Animation> p_animation, int32_t p_track_i, GLTFNodeIndex p_node_i) {
- Animation::InterpolationType interpolation = p_animation->track_get_interpolation_type(p_track_i);
-
- GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- if (interpolation == Animation::InterpolationType::INTERPOLATION_LINEAR) {
- gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- } else if (interpolation == Animation::InterpolationType::INTERPOLATION_NEAREST) {
- gltf_interpolation = GLTFAnimation::INTERP_STEP;
- } else if (interpolation == Animation::InterpolationType::INTERPOLATION_CUBIC) {
- gltf_interpolation = GLTFAnimation::INTERP_CUBIC_SPLINE;
+GLTFNodeIndex GLTFDocument::_node_and_or_bone_to_gltf_node_index(Ref<GLTFState> p_state, const Vector<StringName> &p_node_subpath, const Node *p_godot_node) {
+ const Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_godot_node);
+ if (skeleton && p_node_subpath.size() == 1) {
+ // Special case: Handle skeleton bone TRS tracks. They use the format `A/B/C/Skeleton3D:bone_name`.
+ // We have a Skeleton3D, check if it has a bone with the same name as this subpath.
+ const String &bone_name = p_node_subpath[0];
+ const int32_t bone_index = skeleton->find_bone(bone_name);
+ if (bone_index != -1) {
+ // A bone was found! But we still need to figure out which glTF node it corresponds to.
+ for (GLTFSkeletonIndex skeleton_i = 0; skeleton_i < p_state->skeletons.size(); skeleton_i++) {
+ const Ref<GLTFSkeleton> &skeleton_gltf = p_state->skeletons[skeleton_i];
+ if (skeleton == skeleton_gltf->godot_skeleton) {
+ GLTFNodeIndex node_i = skeleton_gltf->godot_bone_node[bone_index];
+ return node_i;
+ }
+ }
+ ERR_FAIL_V_MSG(-1, vformat("glTF: Found a bone %s in a Skeleton3D that wasn't in the GLTFState. Ensure that all nodes referenced by the AnimationPlayer are in the scene you are exporting.", bone_name));
+ }
}
- Animation::TrackType track_type = p_animation->track_get_type(p_track_i);
- int32_t key_count = p_animation->track_get_key_count(p_track_i);
- Vector<real_t> times;
- times.resize(key_count);
- String path = p_animation->track_get_path(p_track_i);
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- times.write[key_i] = p_animation->track_get_key_time(p_track_i, key_i);
+ // General case: Not a skeleton bone, usually this means a normal node, or it could be the Skeleton3D itself.
+ for (const KeyValue<GLTFNodeIndex, Node *> &scene_node_i : p_state->scene_nodes) {
+ if (scene_node_i.value == p_godot_node) {
+ return scene_node_i.key;
+ }
}
- double anim_end = p_animation->get_length();
+ ERR_FAIL_V_MSG(-1, vformat("glTF: A node was animated, but it wasn't found in the GLTFState. Ensure that all nodes referenced by the AnimationPlayer are in the scene you are exporting."));
+}
+
+bool GLTFDocument::_convert_animation_node_track(Ref<GLTFState> p_state, GLTFAnimation::NodeTrack &p_gltf_node_track, const Ref<Animation> &p_godot_animation, int32_t p_godot_anim_track_index, Vector<double> &p_times) {
+ GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::godot_to_gltf_interpolation(p_godot_animation, p_godot_anim_track_index);
+ const Animation::TrackType track_type = p_godot_animation->track_get_type(p_godot_anim_track_index);
+ const int32_t key_count = p_godot_animation->track_get_key_count(p_godot_anim_track_index);
+ const NodePath node_path = p_godot_animation->track_get_path(p_godot_anim_track_index);
+ const Vector<StringName> subpath = node_path.get_subnames();
+ double anim_end = p_godot_animation->get_length();
if (track_type == Animation::TYPE_SCALE_3D) {
if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- p_track.scale_track.times.clear();
- p_track.scale_track.values.clear();
+ p_gltf_node_track.scale_track.times.clear();
+ p_gltf_node_track.scale_track.values.clear();
// CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
const double increment = 1.0 / p_state->get_bake_fps();
double time = 0.0;
bool last = false;
while (true) {
Vector3 scale;
- Error err = p_animation->try_scale_track_interpolate(p_track_i, time, &scale);
+ Error err = p_godot_animation->try_scale_track_interpolate(p_godot_anim_track_index, time, &scale);
ERR_CONTINUE(err != OK);
- p_track.scale_track.values.push_back(scale);
- p_track.scale_track.times.push_back(time);
+ p_gltf_node_track.scale_track.values.push_back(scale);
+ p_gltf_node_track.scale_track.times.push_back(time);
if (last) {
break;
}
@@ -6416,31 +7462,31 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta
}
}
} else {
- p_track.scale_track.times = times;
- p_track.scale_track.interpolation = gltf_interpolation;
- p_track.scale_track.values.resize(key_count);
+ p_gltf_node_track.scale_track.times = p_times;
+ p_gltf_node_track.scale_track.interpolation = gltf_interpolation;
+ p_gltf_node_track.scale_track.values.resize(key_count);
for (int32_t key_i = 0; key_i < key_count; key_i++) {
Vector3 scale;
- Error err = p_animation->scale_track_get_key(p_track_i, key_i, &scale);
+ Error err = p_godot_animation->scale_track_get_key(p_godot_anim_track_index, key_i, &scale);
ERR_CONTINUE(err != OK);
- p_track.scale_track.values.write[key_i] = scale;
+ p_gltf_node_track.scale_track.values.write[key_i] = scale;
}
}
} else if (track_type == Animation::TYPE_POSITION_3D) {
if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- p_track.position_track.times.clear();
- p_track.position_track.values.clear();
+ p_gltf_node_track.position_track.times.clear();
+ p_gltf_node_track.position_track.values.clear();
// CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
const double increment = 1.0 / p_state->get_bake_fps();
double time = 0.0;
bool last = false;
while (true) {
Vector3 scale;
- Error err = p_animation->try_position_track_interpolate(p_track_i, time, &scale);
+ Error err = p_godot_animation->try_position_track_interpolate(p_godot_anim_track_index, time, &scale);
ERR_CONTINUE(err != OK);
- p_track.position_track.values.push_back(scale);
- p_track.position_track.times.push_back(time);
+ p_gltf_node_track.position_track.values.push_back(scale);
+ p_gltf_node_track.position_track.times.push_back(time);
if (last) {
break;
}
@@ -6451,31 +7497,31 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta
}
}
} else {
- p_track.position_track.times = times;
- p_track.position_track.values.resize(key_count);
- p_track.position_track.interpolation = gltf_interpolation;
+ p_gltf_node_track.position_track.times = p_times;
+ p_gltf_node_track.position_track.values.resize(key_count);
+ p_gltf_node_track.position_track.interpolation = gltf_interpolation;
for (int32_t key_i = 0; key_i < key_count; key_i++) {
Vector3 position;
- Error err = p_animation->position_track_get_key(p_track_i, key_i, &position);
+ Error err = p_godot_animation->position_track_get_key(p_godot_anim_track_index, key_i, &position);
ERR_CONTINUE(err != OK);
- p_track.position_track.values.write[key_i] = position;
+ p_gltf_node_track.position_track.values.write[key_i] = position;
}
}
} else if (track_type == Animation::TYPE_ROTATION_3D) {
if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- p_track.rotation_track.times.clear();
- p_track.rotation_track.values.clear();
+ p_gltf_node_track.rotation_track.times.clear();
+ p_gltf_node_track.rotation_track.values.clear();
// CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
const double increment = 1.0 / p_state->get_bake_fps();
double time = 0.0;
bool last = false;
while (true) {
Quaternion rotation;
- Error err = p_animation->try_rotation_track_interpolate(p_track_i, time, &rotation);
+ Error err = p_godot_animation->try_rotation_track_interpolate(p_godot_anim_track_index, time, &rotation);
ERR_CONTINUE(err != OK);
- p_track.rotation_track.values.push_back(rotation);
- p_track.rotation_track.times.push_back(time);
+ p_gltf_node_track.rotation_track.values.push_back(rotation);
+ p_gltf_node_track.rotation_track.times.push_back(time);
if (last) {
break;
}
@@ -6486,306 +7532,326 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta
}
}
} else {
- p_track.rotation_track.times = times;
- p_track.rotation_track.values.resize(key_count);
- p_track.rotation_track.interpolation = gltf_interpolation;
+ p_gltf_node_track.rotation_track.times = p_times;
+ p_gltf_node_track.rotation_track.values.resize(key_count);
+ p_gltf_node_track.rotation_track.interpolation = gltf_interpolation;
for (int32_t key_i = 0; key_i < key_count; key_i++) {
Quaternion rotation;
- Error err = p_animation->rotation_track_get_key(p_track_i, key_i, &rotation);
+ Error err = p_godot_animation->rotation_track_get_key(p_godot_anim_track_index, key_i, &rotation);
ERR_CONTINUE(err != OK);
- p_track.rotation_track.values.write[key_i] = rotation;
- }
- }
- } else if (track_type == Animation::TYPE_VALUE) {
- if (path.contains(":position")) {
- p_track.position_track.interpolation = gltf_interpolation;
- p_track.position_track.times = times;
- p_track.position_track.values.resize(key_count);
-
- if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
- gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- p_track.position_track.times.clear();
- p_track.position_track.values.clear();
- // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
- const double increment = 1.0 / p_state->get_bake_fps();
- double time = 0.0;
- bool last = false;
- while (true) {
- Vector3 position;
- Error err = p_animation->try_position_track_interpolate(p_track_i, time, &position);
- ERR_CONTINUE(err != OK);
- p_track.position_track.values.push_back(position);
- p_track.position_track.times.push_back(time);
- if (last) {
- break;
+ p_gltf_node_track.rotation_track.values.write[key_i] = rotation;
+ }
+ }
+ } else if (subpath.size() > 0) {
+ const StringName &node_prop = subpath[0];
+ if (track_type == Animation::TYPE_VALUE) {
+ if (node_prop == "position") {
+ p_gltf_node_track.position_track.interpolation = gltf_interpolation;
+ p_gltf_node_track.position_track.times = p_times;
+ p_gltf_node_track.position_track.values.resize(key_count);
+
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_gltf_node_track.position_track.times.clear();
+ p_gltf_node_track.position_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / p_state->get_bake_fps();
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Vector3 position;
+ Error err = p_godot_animation->try_position_track_interpolate(p_godot_anim_track_index, time, &position);
+ ERR_CONTINUE(err != OK);
+ p_gltf_node_track.position_track.values.push_back(position);
+ p_gltf_node_track.position_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
}
- time += increment;
- if (time >= anim_end) {
- last = true;
- time = anim_end;
+ } else {
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Vector3 position = p_godot_animation->track_get_key_value(p_godot_anim_track_index, key_i);
+ p_gltf_node_track.position_track.values.write[key_i] = position;
}
}
- } else {
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Vector3 position = p_animation->track_get_key_value(p_track_i, key_i);
- p_track.position_track.values.write[key_i] = position;
- }
- }
- } else if (path.contains(":rotation")) {
- p_track.rotation_track.interpolation = gltf_interpolation;
- p_track.rotation_track.times = times;
- p_track.rotation_track.values.resize(key_count);
- if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
- gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- p_track.rotation_track.times.clear();
- p_track.rotation_track.values.clear();
- // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
- const double increment = 1.0 / p_state->get_bake_fps();
- double time = 0.0;
- bool last = false;
- while (true) {
- Quaternion rotation;
- Error err = p_animation->try_rotation_track_interpolate(p_track_i, time, &rotation);
- ERR_CONTINUE(err != OK);
- p_track.rotation_track.values.push_back(rotation);
- p_track.rotation_track.times.push_back(time);
- if (last) {
- break;
+ } else if (node_prop == "rotation" || node_prop == "rotation_degrees" || node_prop == "quaternion") {
+ p_gltf_node_track.rotation_track.interpolation = gltf_interpolation;
+ p_gltf_node_track.rotation_track.times = p_times;
+ p_gltf_node_track.rotation_track.values.resize(key_count);
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_gltf_node_track.rotation_track.times.clear();
+ p_gltf_node_track.rotation_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / p_state->get_bake_fps();
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Quaternion rotation;
+ Error err = p_godot_animation->try_rotation_track_interpolate(p_godot_anim_track_index, time, &rotation);
+ ERR_CONTINUE(err != OK);
+ p_gltf_node_track.rotation_track.values.push_back(rotation);
+ p_gltf_node_track.rotation_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
}
- time += increment;
- if (time >= anim_end) {
- last = true;
- time = anim_end;
+ } else {
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Quaternion rotation_quaternion;
+ if (node_prop == "quaternion") {
+ rotation_quaternion = p_godot_animation->track_get_key_value(p_godot_anim_track_index, key_i);
+ } else {
+ Vector3 rotation_euler = p_godot_animation->track_get_key_value(p_godot_anim_track_index, key_i);
+ if (node_prop == "rotation_degrees") {
+ rotation_euler *= Math_TAU / 360.0;
+ }
+ rotation_quaternion = Quaternion::from_euler(rotation_euler);
+ }
+ p_gltf_node_track.rotation_track.values.write[key_i] = rotation_quaternion;
}
}
- } else {
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Vector3 rotation_radian = p_animation->track_get_key_value(p_track_i, key_i);
- p_track.rotation_track.values.write[key_i] = Quaternion::from_euler(rotation_radian);
+ } else if (node_prop == "scale") {
+ p_gltf_node_track.scale_track.interpolation = gltf_interpolation;
+ p_gltf_node_track.scale_track.times = p_times;
+ p_gltf_node_track.scale_track.values.resize(key_count);
+
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_gltf_node_track.scale_track.times.clear();
+ p_gltf_node_track.scale_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / p_state->get_bake_fps();
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Vector3 scale;
+ Error err = p_godot_animation->try_scale_track_interpolate(p_godot_anim_track_index, time, &scale);
+ ERR_CONTINUE(err != OK);
+ p_gltf_node_track.scale_track.values.push_back(scale);
+ p_gltf_node_track.scale_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
+ }
+ } else {
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Vector3 scale_track = p_godot_animation->track_get_key_value(p_godot_anim_track_index, key_i);
+ p_gltf_node_track.scale_track.values.write[key_i] = scale_track;
+ }
}
- }
- } else if (path.contains(":scale")) {
- p_track.scale_track.times = times;
- p_track.scale_track.interpolation = gltf_interpolation;
-
- p_track.scale_track.values.resize(key_count);
- p_track.scale_track.interpolation = gltf_interpolation;
-
- if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
- gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- p_track.scale_track.times.clear();
- p_track.scale_track.values.clear();
- // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
- const double increment = 1.0 / p_state->get_bake_fps();
- double time = 0.0;
- bool last = false;
- while (true) {
- Vector3 scale;
- Error err = p_animation->try_scale_track_interpolate(p_track_i, time, &scale);
- ERR_CONTINUE(err != OK);
- p_track.scale_track.values.push_back(scale);
- p_track.scale_track.times.push_back(time);
- if (last) {
- break;
+ } else if (node_prop == "transform") {
+ p_gltf_node_track.position_track.interpolation = gltf_interpolation;
+ p_gltf_node_track.position_track.times = p_times;
+ p_gltf_node_track.position_track.values.resize(key_count);
+ p_gltf_node_track.rotation_track.interpolation = gltf_interpolation;
+ p_gltf_node_track.rotation_track.times = p_times;
+ p_gltf_node_track.rotation_track.values.resize(key_count);
+ p_gltf_node_track.scale_track.interpolation = gltf_interpolation;
+ p_gltf_node_track.scale_track.times = p_times;
+ p_gltf_node_track.scale_track.values.resize(key_count);
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_gltf_node_track.position_track.times.clear();
+ p_gltf_node_track.position_track.values.clear();
+ p_gltf_node_track.rotation_track.times.clear();
+ p_gltf_node_track.rotation_track.values.clear();
+ p_gltf_node_track.scale_track.times.clear();
+ p_gltf_node_track.scale_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / p_state->get_bake_fps();
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Vector3 position;
+ Quaternion rotation;
+ Vector3 scale;
+ Error err = p_godot_animation->try_position_track_interpolate(p_godot_anim_track_index, time, &position);
+ ERR_CONTINUE(err != OK);
+ err = p_godot_animation->try_rotation_track_interpolate(p_godot_anim_track_index, time, &rotation);
+ ERR_CONTINUE(err != OK);
+ err = p_godot_animation->try_scale_track_interpolate(p_godot_anim_track_index, time, &scale);
+ ERR_CONTINUE(err != OK);
+ p_gltf_node_track.position_track.values.push_back(position);
+ p_gltf_node_track.position_track.times.push_back(time);
+ p_gltf_node_track.rotation_track.values.push_back(rotation);
+ p_gltf_node_track.rotation_track.times.push_back(time);
+ p_gltf_node_track.scale_track.values.push_back(scale);
+ p_gltf_node_track.scale_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
}
- time += increment;
- if (time >= anim_end) {
- last = true;
- time = anim_end;
+ } else {
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Transform3D transform = p_godot_animation->track_get_key_value(p_godot_anim_track_index, key_i);
+ p_gltf_node_track.position_track.values.write[key_i] = transform.get_origin();
+ p_gltf_node_track.rotation_track.values.write[key_i] = transform.basis.get_rotation_quaternion();
+ p_gltf_node_track.scale_track.values.write[key_i] = transform.basis.get_scale();
}
}
} else {
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Vector3 scale_track = p_animation->track_get_key_value(p_track_i, key_i);
- p_track.scale_track.values.write[key_i] = scale_track;
- }
- }
- }
- } else if (track_type == Animation::TYPE_BEZIER) {
- const int32_t keys = anim_end * p_state->get_bake_fps();
- if (path.contains(":scale")) {
- if (!p_track.scale_track.times.size()) {
- p_track.scale_track.interpolation = gltf_interpolation;
- Vector<real_t> new_times;
- new_times.resize(keys);
- for (int32_t key_i = 0; key_i < keys; key_i++) {
- new_times.write[key_i] = key_i / p_state->get_bake_fps();
- }
- p_track.scale_track.times = new_times;
+ // This is a Value track animating a property, but not a TRS property, so it can't be converted into a node track.
+ return false;
+ }
+ } else if (track_type == Animation::TYPE_BEZIER) {
+ const int32_t keys = anim_end * p_state->get_bake_fps();
+ if (node_prop == "scale") {
+ if (p_gltf_node_track.scale_track.times.is_empty()) {
+ p_gltf_node_track.scale_track.interpolation = gltf_interpolation;
+ Vector<double> new_times;
+ new_times.resize(keys);
+ for (int32_t key_i = 0; key_i < keys; key_i++) {
+ new_times.write[key_i] = key_i / p_state->get_bake_fps();
+ }
+ p_gltf_node_track.scale_track.times = new_times;
- p_track.scale_track.values.resize(keys);
+ p_gltf_node_track.scale_track.values.resize(keys);
- for (int32_t key_i = 0; key_i < keys; key_i++) {
- p_track.scale_track.values.write[key_i] = Vector3(1.0f, 1.0f, 1.0f);
- }
+ for (int32_t key_i = 0; key_i < keys; key_i++) {
+ p_gltf_node_track.scale_track.values.write[key_i] = Vector3(1.0f, 1.0f, 1.0f);
+ }
- for (int32_t key_i = 0; key_i < keys; key_i++) {
- Vector3 bezier_track = p_track.scale_track.values[key_i];
- if (path.contains(":scale:x")) {
- bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
- } else if (path.contains(":scale:y")) {
- bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
- } else if (path.contains(":scale:z")) {
- bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
+ for (int32_t key_i = 0; key_i < keys; key_i++) {
+ Vector3 bezier_track = p_gltf_node_track.scale_track.values[key_i];
+ if (subpath.size() == 2) {
+ if (subpath[1] == StringName("x")) {
+ bezier_track.x = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ } else if (subpath[1] == StringName("y")) {
+ bezier_track.y = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ } else if (subpath[1] == StringName("z")) {
+ bezier_track.z = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ }
+ }
+ p_gltf_node_track.scale_track.values.write[key_i] = bezier_track;
}
- p_track.scale_track.values.write[key_i] = bezier_track;
}
- }
- } else if (path.contains(":position")) {
- if (!p_track.position_track.times.size()) {
- p_track.position_track.interpolation = gltf_interpolation;
- Vector<real_t> new_times;
- new_times.resize(keys);
- for (int32_t key_i = 0; key_i < keys; key_i++) {
- new_times.write[key_i] = key_i / p_state->get_bake_fps();
+ } else if (node_prop == "position") {
+ if (p_gltf_node_track.position_track.times.is_empty()) {
+ p_gltf_node_track.position_track.interpolation = gltf_interpolation;
+ Vector<double> new_times;
+ new_times.resize(keys);
+ for (int32_t key_i = 0; key_i < keys; key_i++) {
+ new_times.write[key_i] = key_i / p_state->get_bake_fps();
+ }
+ p_gltf_node_track.position_track.times = new_times;
+
+ p_gltf_node_track.position_track.values.resize(keys);
}
- p_track.position_track.times = new_times;
- p_track.position_track.values.resize(keys);
- }
+ for (int32_t key_i = 0; key_i < keys; key_i++) {
+ Vector3 bezier_track = p_gltf_node_track.position_track.values[key_i];
+ if (subpath.size() == 2) {
+ if (subpath[1] == StringName("x")) {
+ bezier_track.x = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ } else if (subpath[1] == StringName("y")) {
+ bezier_track.y = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ } else if (subpath[1] == StringName("z")) {
+ bezier_track.z = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ }
+ }
+ p_gltf_node_track.position_track.values.write[key_i] = bezier_track;
+ }
+ } else if (node_prop == "quaternion") {
+ if (p_gltf_node_track.rotation_track.times.is_empty()) {
+ p_gltf_node_track.rotation_track.interpolation = gltf_interpolation;
+ Vector<double> new_times;
+ new_times.resize(keys);
+ for (int32_t key_i = 0; key_i < keys; key_i++) {
+ new_times.write[key_i] = key_i / p_state->get_bake_fps();
+ }
+ p_gltf_node_track.rotation_track.times = new_times;
- for (int32_t key_i = 0; key_i < keys; key_i++) {
- Vector3 bezier_track = p_track.position_track.values[key_i];
- if (path.contains(":position:x")) {
- bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
- } else if (path.contains(":position:y")) {
- bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
- } else if (path.contains(":position:z")) {
- bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
+ p_gltf_node_track.rotation_track.values.resize(keys);
}
- p_track.position_track.values.write[key_i] = bezier_track;
- }
- } else if (path.contains(":rotation")) {
- if (!p_track.rotation_track.times.size()) {
- p_track.rotation_track.interpolation = gltf_interpolation;
- Vector<real_t> new_times;
- new_times.resize(keys);
for (int32_t key_i = 0; key_i < keys; key_i++) {
- new_times.write[key_i] = key_i / p_state->get_bake_fps();
- }
- p_track.rotation_track.times = new_times;
-
- p_track.rotation_track.values.resize(keys);
- }
- for (int32_t key_i = 0; key_i < keys; key_i++) {
- Quaternion bezier_track = p_track.rotation_track.values[key_i];
- if (path.contains(":rotation:x")) {
- bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
- } else if (path.contains(":rotation:y")) {
- bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
- } else if (path.contains(":rotation:z")) {
- bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
- } else if (path.contains(":rotation:w")) {
- bezier_track.w = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
+ Quaternion bezier_track = p_gltf_node_track.rotation_track.values[key_i];
+ if (subpath.size() == 2) {
+ if (subpath[1] == StringName("x")) {
+ bezier_track.x = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ } else if (subpath[1] == StringName("y")) {
+ bezier_track.y = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ } else if (subpath[1] == StringName("z")) {
+ bezier_track.z = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ } else if (subpath[1] == StringName("w")) {
+ bezier_track.w = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ }
+ }
+ p_gltf_node_track.rotation_track.values.write[key_i] = bezier_track;
}
- p_track.rotation_track.values.write[key_i] = bezier_track;
+ } else {
+ // This is a Bezier track animating a property, but not a TRS property, so it can't be converted into a node track.
+ return false;
}
+ } else {
+ // This property track isn't a Value track or Bezier track, so it can't be converted into a node track.
+ return false;
}
+ } else {
+ // This isn't a TRS track or a property track, so it can't be converted into a node track.
+ return false;
}
- return p_track;
+ // If we reached this point, the track was some kind of TRS track and was successfully converted.
+ // All failure paths should return false before this point to indicate this
+ // isn't a node track so it can be handled by KHR_animation_pointer instead.
+ return true;
}
-void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, String p_animation_track_name) {
+void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, const String &p_animation_track_name) {
Ref<Animation> animation = p_animation_player->get_animation(p_animation_track_name);
Ref<GLTFAnimation> gltf_animation;
gltf_animation.instantiate();
gltf_animation->set_original_name(p_animation_track_name);
gltf_animation->set_name(_gen_unique_name(p_state, p_animation_track_name));
- for (int32_t track_i = 0; track_i < animation->get_track_count(); track_i++) {
- if (!animation->track_is_enabled(track_i)) {
+ HashMap<int, GLTFAnimation::NodeTrack> &node_tracks = gltf_animation->get_node_tracks();
+ for (int32_t track_index = 0; track_index < animation->get_track_count(); track_index++) {
+ if (!animation->track_is_enabled(track_index)) {
continue;
}
- String final_track_path = animation->track_get_path(track_i);
- Node *animation_base_node = p_animation_player->get_parent();
- ERR_CONTINUE_MSG(!animation_base_node, "Cannot get the parent of the animation player.");
- if (String(final_track_path).contains(":position")) {
- const Vector<String> node_suffix = String(final_track_path).split(":position");
- const NodePath path = node_suffix[0];
- const Node *node = animation_base_node->get_node_or_null(path);
- ERR_CONTINUE_MSG(!node, "Cannot get the node from a position path.");
- for (const KeyValue<GLTFNodeIndex, Node *> &position_scene_node_i : p_state->scene_nodes) {
- if (position_scene_node_i.value == node) {
- GLTFNodeIndex node_index = position_scene_node_i.key;
- HashMap<int, GLTFAnimation::Track>::Iterator position_track_i = gltf_animation->get_tracks().find(node_index);
- GLTFAnimation::Track track;
- if (position_track_i) {
- track = position_track_i->value;
- }
- track = _convert_animation_track(p_state, track, animation, track_i, node_index);
- gltf_animation->get_tracks().insert(node_index, track);
- }
- }
- } else if (String(final_track_path).contains(":rotation_degrees")) {
- const Vector<String> node_suffix = String(final_track_path).split(":rotation_degrees");
- const NodePath path = node_suffix[0];
- const Node *node = animation_base_node->get_node_or_null(path);
- ERR_CONTINUE_MSG(!node, "Cannot get the node from a rotation degrees path.");
- for (const KeyValue<GLTFNodeIndex, Node *> &rotation_degree_scene_node_i : p_state->scene_nodes) {
- if (rotation_degree_scene_node_i.value == node) {
- GLTFNodeIndex node_index = rotation_degree_scene_node_i.key;
- HashMap<int, GLTFAnimation::Track>::Iterator rotation_degree_track_i = gltf_animation->get_tracks().find(node_index);
- GLTFAnimation::Track track;
- if (rotation_degree_track_i) {
- track = rotation_degree_track_i->value;
- }
- track = _convert_animation_track(p_state, track, animation, track_i, node_index);
- gltf_animation->get_tracks().insert(node_index, track);
- }
- }
- } else if (String(final_track_path).contains(":scale")) {
- const Vector<String> node_suffix = String(final_track_path).split(":scale");
- const NodePath path = node_suffix[0];
- const Node *node = animation_base_node->get_node_or_null(path);
- ERR_CONTINUE_MSG(!node, "Cannot get the node from a scale path.");
- for (const KeyValue<GLTFNodeIndex, Node *> &scale_scene_node_i : p_state->scene_nodes) {
- if (scale_scene_node_i.value == node) {
- GLTFNodeIndex node_index = scale_scene_node_i.key;
- HashMap<int, GLTFAnimation::Track>::Iterator scale_track_i = gltf_animation->get_tracks().find(node_index);
- GLTFAnimation::Track track;
- if (scale_track_i) {
- track = scale_track_i->value;
- }
- track = _convert_animation_track(p_state, track, animation, track_i, node_index);
- gltf_animation->get_tracks().insert(node_index, track);
- }
- }
- } else if (String(final_track_path).contains(":transform")) {
- const Vector<String> node_suffix = String(final_track_path).split(":transform");
- const NodePath path = node_suffix[0];
- const Node *node = animation_base_node->get_node_or_null(path);
- ERR_CONTINUE_MSG(!node, "Cannot get the node from a transform path.");
- for (const KeyValue<GLTFNodeIndex, Node *> &transform_track_i : p_state->scene_nodes) {
- if (transform_track_i.value == node) {
- GLTFAnimation::Track track;
- track = _convert_animation_track(p_state, track, animation, track_i, transform_track_i.key);
- gltf_animation->get_tracks().insert(transform_track_i.key, track);
- }
- }
- } else if (String(final_track_path).contains(":") && animation->track_get_type(track_i) == Animation::TYPE_BLEND_SHAPE) {
- const Vector<String> node_suffix = String(final_track_path).split(":");
- const NodePath path = node_suffix[0];
- const String suffix = node_suffix[1];
- Node *node = animation_base_node->get_node_or_null(path);
- ERR_CONTINUE_MSG(!node, "Cannot get the node from a blend shape path.");
- MeshInstance3D *mi = cast_to<MeshInstance3D>(node);
- if (!mi) {
- continue;
- }
- Ref<Mesh> mesh = mi->get_mesh();
+ // Get the Godot node and the glTF node index for the animation track.
+ const NodePath track_path = animation->track_get_path(track_index);
+ const Node *anim_player_parent = p_animation_player->get_parent();
+ const Node *animated_node = anim_player_parent->get_node_or_null(track_path);
+ ERR_CONTINUE_MSG(!animated_node, "glTF: Cannot get node for animated track using path: " + String(track_path));
+ const GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::godot_to_gltf_interpolation(animation, track_index);
+ // First, check if it's a Blend Shape track.
+ if (animation->track_get_type(track_index) == Animation::TYPE_BLEND_SHAPE) {
+ const MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(animated_node);
+ ERR_CONTINUE_MSG(!mesh_instance, "glTF: Animation had a Blend Shape track, but the node wasn't a MeshInstance3D. Ignoring this track.");
+ Ref<Mesh> mesh = mesh_instance->get_mesh();
ERR_CONTINUE(mesh.is_null());
int32_t mesh_index = -1;
for (const KeyValue<GLTFNodeIndex, Node *> &mesh_track_i : p_state->scene_nodes) {
- if (mesh_track_i.value == node) {
+ if (mesh_track_i.value == animated_node) {
mesh_index = mesh_track_i.key;
}
}
ERR_CONTINUE(mesh_index == -1);
- HashMap<int, GLTFAnimation::Track> &tracks = gltf_animation->get_tracks();
- GLTFAnimation::Track track = gltf_animation->get_tracks().has(mesh_index) ? gltf_animation->get_tracks()[mesh_index] : GLTFAnimation::Track();
- if (!tracks.has(mesh_index)) {
+ GLTFAnimation::NodeTrack track = node_tracks.has(mesh_index) ? node_tracks[mesh_index] : GLTFAnimation::NodeTrack();
+ if (!node_tracks.has(mesh_index)) {
for (int32_t shape_i = 0; shape_i < mesh->get_blend_shape_count(); shape_i++) {
String shape_name = mesh->get_blend_shape_name(shape_i);
- NodePath shape_path = String(path) + ":" + shape_name;
+ NodePath shape_path = NodePath(track_path.get_names(), { shape_name }, false);
int32_t shape_track_i = animation->find_track(shape_path, Animation::TYPE_BLEND_SHAPE);
if (shape_track_i == -1) {
GLTFAnimation::Channel<real_t> weight;
@@ -6797,15 +7863,6 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
track.weight_tracks.push_back(weight);
continue;
}
- Animation::InterpolationType interpolation = animation->track_get_interpolation_type(track_i);
- GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- if (interpolation == Animation::InterpolationType::INTERPOLATION_LINEAR) {
- gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- } else if (interpolation == Animation::InterpolationType::INTERPOLATION_NEAREST) {
- gltf_interpolation = GLTFAnimation::INTERP_STEP;
- } else if (interpolation == Animation::InterpolationType::INTERPOLATION_CUBIC) {
- gltf_interpolation = GLTFAnimation::INTERP_CUBIC_SPLINE;
- }
int32_t key_count = animation->track_get_key_count(shape_track_i);
GLTFAnimation::Channel<real_t> weight;
weight.interpolation = gltf_interpolation;
@@ -6819,64 +7876,74 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
}
track.weight_tracks.push_back(weight);
}
- tracks[mesh_index] = track;
- }
- } else if (String(final_track_path).contains(":")) {
- //Process skeleton
- const Vector<String> node_suffix = String(final_track_path).split(":");
- const String &node = node_suffix[0];
- const NodePath node_path = node;
- const String &suffix = node_suffix[1];
- Node *godot_node = animation_base_node->get_node_or_null(node_path);
- if (!godot_node) {
- continue;
- }
- Skeleton3D *skeleton = cast_to<Skeleton3D>(animation_base_node->get_node_or_null(node));
- if (!skeleton) {
- continue;
- }
- GLTFSkeletonIndex skeleton_gltf_i = -1;
- for (GLTFSkeletonIndex skeleton_i = 0; skeleton_i < p_state->skeletons.size(); skeleton_i++) {
- if (p_state->skeletons[skeleton_i]->godot_skeleton == cast_to<Skeleton3D>(godot_node)) {
- skeleton = p_state->skeletons[skeleton_i]->godot_skeleton;
- skeleton_gltf_i = skeleton_i;
- ERR_CONTINUE(!skeleton);
- Ref<GLTFSkeleton> skeleton_gltf = p_state->skeletons[skeleton_gltf_i];
- int32_t bone = skeleton->find_bone(suffix);
- ERR_CONTINUE_MSG(bone == -1, vformat("Cannot find the bone %s.", suffix));
- if (!skeleton_gltf->godot_bone_node.has(bone)) {
- continue;
- }
- GLTFNodeIndex node_i = skeleton_gltf->godot_bone_node[bone];
- HashMap<int, GLTFAnimation::Track>::Iterator property_track_i = gltf_animation->get_tracks().find(node_i);
- GLTFAnimation::Track track;
- if (property_track_i) {
- track = property_track_i->value;
- }
- track = _convert_animation_track(p_state, track, animation, track_i, node_i);
- gltf_animation->get_tracks()[node_i] = track;
- }
- }
- } else if (!String(final_track_path).contains(":")) {
- ERR_CONTINUE(!animation_base_node);
- Node *godot_node = animation_base_node->get_node_or_null(final_track_path);
- ERR_CONTINUE_MSG(!godot_node, vformat("Cannot get the node from a skeleton path %s.", final_track_path));
- for (const KeyValue<GLTFNodeIndex, Node *> &scene_node_i : p_state->scene_nodes) {
- if (scene_node_i.value == godot_node) {
- GLTFNodeIndex node_i = scene_node_i.key;
- HashMap<int, GLTFAnimation::Track>::Iterator node_track_i = gltf_animation->get_tracks().find(node_i);
- GLTFAnimation::Track track;
- if (node_track_i) {
- track = node_track_i->value;
- }
- track = _convert_animation_track(p_state, track, animation, track_i, node_i);
- gltf_animation->get_tracks()[node_i] = track;
- break;
- }
+ node_tracks[mesh_index] = track;
}
+ continue;
}
- }
- if (gltf_animation->get_tracks().size()) {
+ // If it's not a Blend Shape track, it must either be a TRS track, a property Value track, or something we can't handle.
+ // For the cases we can handle, we will need to know the glTF node index, glTF interpolation, and the times of the track.
+ const Vector<StringName> subnames = track_path.get_subnames();
+ const GLTFNodeIndex node_i = _node_and_or_bone_to_gltf_node_index(p_state, subnames, animated_node);
+ ERR_CONTINUE_MSG(node_i == -1, "glTF: Cannot get glTF node index for animated track using path: " + String(track_path));
+ const int anim_key_count = animation->track_get_key_count(track_index);
+ Vector<double> times;
+ times.resize(anim_key_count);
+ for (int32_t key_i = 0; key_i < anim_key_count; key_i++) {
+ times.write[key_i] = animation->track_get_key_time(track_index, key_i);
+ }
+ // Try converting the track to a TRS glTF node track. This will only succeed if the Godot animation is a TRS track.
+ const HashMap<int, GLTFAnimation::NodeTrack>::Iterator node_track_iter = node_tracks.find(node_i);
+ GLTFAnimation::NodeTrack track;
+ if (node_track_iter) {
+ track = node_track_iter->value;
+ }
+ if (_convert_animation_node_track(p_state, track, animation, track_index, times)) {
+ // If the track was successfully converted, save it and continue to the next track.
+ node_tracks[node_i] = track;
+ continue;
+ }
+ // If the track wasn't a TRS track or Blend Shape track, it might be a Value track animating a property.
+ // Then this is something that we need to handle with KHR_animation_pointer.
+ Ref<GLTFObjectModelProperty> obj_model_prop = export_object_model_property(p_state, track_path, animated_node, node_i);
+ if (obj_model_prop.is_valid() && obj_model_prop->has_json_pointers()) {
+ // Insert the property track into the KHR_animation_pointer pointer tracks.
+ GLTFAnimation::Channel<Variant> channel;
+ channel.interpolation = gltf_interpolation;
+ channel.times = times;
+ channel.values.resize(anim_key_count);
+ // If using an expression, determine the base instance to pass to the expression.
+ const Ref<Expression> godot_to_gltf_expr = obj_model_prop->get_godot_to_gltf_expression();
+ const bool is_godot_to_gltf_expr_valid = godot_to_gltf_expr.is_valid();
+ Object *base_instance = nullptr;
+ if (is_godot_to_gltf_expr_valid) {
+ Ref<Resource> resource;
+ Vector<StringName> leftover_subpath;
+ base_instance = anim_player_parent->get_node_and_resource(track_path, resource, leftover_subpath);
+ if (resource.is_valid()) {
+ base_instance = resource.ptr();
+ }
+ }
+ // Convert the Godot animation values into glTF animation values (still Variant).
+ for (int32_t key_i = 0; key_i < anim_key_count; key_i++) {
+ Variant value = animation->track_get_key_value(track_index, key_i);
+ if (is_godot_to_gltf_expr_valid) {
+ Array inputs;
+ inputs.append(value);
+ value = godot_to_gltf_expr->execute(inputs, base_instance);
+ }
+ channel.values.write[key_i] = value;
+ }
+ // Use the JSON pointer to insert the property track into the pointer tracks. There will usually be just one JSON pointer.
+ HashMap<String, GLTFAnimation::Channel<Variant>> &pointer_tracks = gltf_animation->get_pointer_tracks();
+ Vector<PackedStringArray> split_json_pointers = obj_model_prop->get_json_pointers();
+ for (const PackedStringArray &split_json_pointer : split_json_pointers) {
+ String json_pointer_str = "/" + String("/").join(split_json_pointer);
+ p_state->object_model_properties[json_pointer_str] = obj_model_prop;
+ pointer_tracks[json_pointer_str] = channel;
+ }
+ }
+ }
+ if (!gltf_animation->is_empty_of_tracks()) {
p_state->animations.push_back(gltf_animation);
}
}
@@ -7075,6 +8142,9 @@ void GLTFDocument::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lossy_quality"), "set_lossy_quality", "get_lossy_quality");
ADD_PROPERTY(PropertyInfo(Variant::INT, "root_node_mode"), "set_root_node_mode", "get_root_node_mode");
+ ClassDB::bind_static_method("GLTFDocument", D_METHOD("import_object_model_property", "state", "json_pointer"), &GLTFDocument::import_object_model_property);
+ ClassDB::bind_static_method("GLTFDocument", D_METHOD("export_object_model_property", "state", "node_path", "godot_node", "gltf_node_index"), &GLTFDocument::export_object_model_property);
+
ClassDB::bind_static_method("GLTFDocument", D_METHOD("register_gltf_document_extension", "extension", "first_priority"),
&GLTFDocument::register_gltf_document_extension, DEFVAL(false));
ClassDB::bind_static_method("GLTFDocument", D_METHOD("unregister_gltf_document_extension", "extension"),
@@ -7136,6 +8206,7 @@ HashSet<String> GLTFDocument::get_supported_gltf_extensions_hashset() {
// If the extension is supported directly in GLTFDocument, list it here.
// Other built-in extensions are supported by GLTFDocumentExtension classes.
supported_extensions.insert("GODOT_single_root");
+ supported_extensions.insert("KHR_animation_pointer");
supported_extensions.insert("KHR_lights_punctual");
supported_extensions.insert("KHR_materials_emissive_strength");
supported_extensions.insert("KHR_materials_pbrSpecularGlossiness");
@@ -7313,6 +8384,10 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se
err = SkinTool::_determine_skeletons(p_state->skins, p_state->nodes, p_state->skeletons, p_state->get_import_as_skeleton_bones() ? p_state->root_nodes : Vector<GLTFNodeIndex>());
ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
+ /* ASSIGN SCENE NODE NAMES */
+ // This must be run AFTER determining skeletons, and BEFORE parsing animations.
+ _assign_node_names(p_state);
+
/* PARSE MESHES (we have enough info now) */
err = _parse_meshes(p_state);
ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
@@ -7329,9 +8404,6 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se
err = _parse_animations(p_state);
ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
- /* ASSIGN SCENE NAMES */
- _assign_node_names(p_state);
-
return OK;
}
@@ -7350,7 +8422,7 @@ PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> p_state) {
Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_path) {
Ref<GLTFState> state = p_state;
ERR_FAIL_COND_V(state.is_null(), ERR_INVALID_PARAMETER);
- state->base_path = p_path.get_base_dir();
+ state->set_base_path(p_path.get_base_dir());
state->filename = p_path.get_file();
Error err = _serialize(state);
if (err != OK) {
@@ -7463,7 +8535,7 @@ Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_pa
Ref<FileAccessMemory> file_access;
file_access.instantiate();
file_access->open_custom(p_bytes.ptr(), p_bytes.size());
- state->base_path = p_base_path.get_base_dir();
+ state->set_base_path(p_base_path.get_base_dir());
err = _parse(p_state, state->base_path, file_access);
ERR_FAIL_COND_V(err != OK, err);
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
@@ -7480,7 +8552,7 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint
if (state == Ref<GLTFState>()) {
state.instantiate();
}
- state->filename = p_path.get_file().get_basename();
+ state->set_filename(p_path.get_file().get_basename());
state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS;
state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS;
state->force_generate_tangents = p_flags & GLTF_IMPORT_GENERATE_TANGENT_ARRAYS;
@@ -7494,7 +8566,7 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint
if (base_path.is_empty()) {
base_path = p_path.get_base_dir();
}
- state->base_path = base_path;
+ state->set_base_path(base_path);
err = _parse(p_state, base_path, file);
ERR_FAIL_COND_V(err != OK, err);
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index d347d49102..a6d6caa3f0 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -56,13 +56,6 @@ public:
enum {
ARRAY_BUFFER = 34962,
ELEMENT_ARRAY_BUFFER = 34963,
-
- COMPONENT_TYPE_BYTE = 5120,
- COMPONENT_TYPE_UNSIGNED_BYTE = 5121,
- COMPONENT_TYPE_SHORT = 5122,
- COMPONENT_TYPE_UNSIGNED_SHORT = 5123,
- COMPONENT_TYPE_INT = 5125,
- COMPONENT_TYPE_FLOAT = 5126,
};
enum {
TEXTURE_TYPE_GENERIC = 0,
@@ -95,6 +88,10 @@ public:
static Vector<String> get_supported_gltf_extensions();
static HashSet<String> get_supported_gltf_extensions_hashset();
+ static NodePath _find_material_node_path(Ref<GLTFState> p_state, Ref<Material> p_material);
+ static Ref<GLTFObjectModelProperty> import_object_model_property(Ref<GLTFState> p_state, const String &p_json_pointer);
+ static Ref<GLTFObjectModelProperty> export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index);
+
void set_naming_version(int p_version);
int get_naming_version() const;
void set_image_format(const String &p_image_format);
@@ -109,8 +106,8 @@ private:
void _build_parent_hierachy(Ref<GLTFState> p_state);
double _filter_number(double p_float);
void _round_min_max_components(Vector<double> &r_type_min, Vector<double> &r_type_max);
- String _get_component_type_name(const uint32_t p_component);
- int _get_component_type_size(const int p_component_type);
+ String _get_component_type_name(const GLTFAccessor::GLTFComponentType p_component_type);
+ int _get_component_type_size(const GLTFAccessor::GLTFComponentType p_component_type);
Error _parse_scenes(Ref<GLTFState> p_state);
Error _parse_nodes(Ref<GLTFState> p_state);
String _get_accessor_type_name(const GLTFAccessor::GLTFAccessorType p_accessor_type);
@@ -140,7 +137,7 @@ private:
const int p_skip_every, const int p_skip_bytes,
const int p_element_size, const int p_count,
const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_count,
- const int p_component_type, const int p_component_size,
+ const GLTFAccessor::GLTFComponentType p_component_type, const int p_component_size,
const bool p_normalized, const int p_byte_offset,
const bool p_for_vertex);
Vector<double> _decode_accessor(Ref<GLTFState> p_state,
@@ -178,6 +175,15 @@ private:
Vector<Transform3D> _decode_accessor_as_xform(Ref<GLTFState> p_state,
const GLTFAccessorIndex p_accessor,
const bool p_for_vertex);
+ Vector<Variant> _decode_accessor_as_variant(Ref<GLTFState> p_state,
+ const GLTFAccessorIndex p_accessor,
+ Variant::Type p_variant_type,
+ GLTFAccessor::GLTFAccessorType p_accessor_type);
+ GLTFAccessorIndex _encode_accessor_as_variant(Ref<GLTFState> p_state,
+ Vector<Variant> p_attribs,
+ Variant::Type p_variant_type,
+ GLTFAccessor::GLTFAccessorType p_accessor_type,
+ GLTFAccessor::GLTFComponentType p_component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT);
Error _parse_meshes(Ref<GLTFState> p_state);
Error _serialize_textures(Ref<GLTFState> p_state);
Error _serialize_texture_samplers(Ref<GLTFState> p_state);
@@ -205,6 +211,7 @@ private:
Error _parse_cameras(Ref<GLTFState> p_state);
Error _parse_lights(Ref<GLTFState> p_state);
Error _parse_animations(Ref<GLTFState> p_state);
+ void _parse_animation_pointer(Ref<GLTFState> p_state, const String &p_animation_json_pointer, const Ref<GLTFAnimation> p_gltf_animation, const GLTFAnimation::Interpolation p_interp, const Vector<double> &p_times, const int p_output_value_accessor_index);
Error _serialize_animations(Ref<GLTFState> p_state);
BoneAttachment3D *_generate_bone_attachment(Ref<GLTFState> p_state,
Skeleton3D *p_skeleton,
@@ -216,7 +223,7 @@ private:
Node3D *_generate_spatial(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index);
void _assign_node_names(Ref<GLTFState> p_state);
template <typename T>
- T _interpolate_track(const Vector<real_t> &p_times, const Vector<T> &p_values,
+ T _interpolate_track(const Vector<double> &p_times, const Vector<T> &p_values,
const float p_time,
const GLTFAnimation::Interpolation p_interp);
GLTFAccessorIndex _encode_accessor_as_quaternions(Ref<GLTFState> p_state,
@@ -229,7 +236,7 @@ private:
const Vector<Color> p_attribs,
const bool p_for_vertex);
GLTFAccessorIndex _encode_accessor_as_floats(Ref<GLTFState> p_state,
- const Vector<real_t> p_attribs,
+ const Vector<double> p_attribs,
const bool p_for_vertex);
GLTFAccessorIndex _encode_accessor_as_vec2(Ref<GLTFState> p_state,
const Vector<Vector2> p_attribs,
@@ -269,7 +276,7 @@ private:
const bool p_for_vertex);
Error _encode_buffer_view(Ref<GLTFState> p_state, const double *p_src,
const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type,
- const int p_component_type, const bool p_normalized,
+ const GLTFAccessor::GLTFComponentType p_component_type, const bool p_normalized,
const int p_byte_offset, const bool p_for_vertex,
GLTFBufferViewIndex &r_accessor, const bool p_for_indices = false);
@@ -280,11 +287,6 @@ private:
Error _serialize_nodes(Ref<GLTFState> p_state);
Error _serialize_scenes(Ref<GLTFState> p_state);
String interpolation_to_string(const GLTFAnimation::Interpolation p_interp);
- GLTFAnimation::Track _convert_animation_track(Ref<GLTFState> p_state,
- GLTFAnimation::Track p_track,
- Ref<Animation> p_animation,
- int32_t p_track_i,
- GLTFNodeIndex p_node_i);
Error _encode_buffer_bins(Ref<GLTFState> p_state, const String &p_path);
Error _encode_buffer_glb(Ref<GLTFState> p_state, const String &p_path);
PackedByteArray _serialize_glb_buffer(Ref<GLTFState> p_state, Error *r_err);
@@ -342,11 +344,6 @@ public:
void _convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeIndex p_gltf_parent, Ref<GLTFNode> p_gltf_node, Ref<GLTFState> p_state);
#endif // MODULE_CSG_ENABLED
- void _convert_animation_player_to_gltf(
- AnimationPlayer *p_animation_player, Ref<GLTFState> p_state,
- GLTFNodeIndex p_gltf_current,
- GLTFNodeIndex p_gltf_root_index,
- Ref<GLTFNode> p_gltf_node, Node *p_scene_parent);
void _check_visibility(Node *p_node, bool &r_retflag);
void _convert_camera_to_gltf(Camera3D *p_camera, Ref<GLTFState> p_state,
Ref<GLTFNode> p_gltf_node);
@@ -377,7 +374,15 @@ public:
Ref<GLTFNode> p_gltf_node);
GLTFMeshIndex _convert_mesh_to_gltf(Ref<GLTFState> p_state,
MeshInstance3D *p_mesh_instance);
- void _convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, String p_animation_track_name);
+
+ GLTFNodeIndex _node_and_or_bone_to_gltf_node_index(Ref<GLTFState> p_state, const Vector<StringName> &p_node_subpath, const Node *p_godot_node);
+ bool _convert_animation_node_track(Ref<GLTFState> p_state,
+ GLTFAnimation::NodeTrack &p_gltf_node_track,
+ const Ref<Animation> &p_godot_animation,
+ int32_t p_godot_anim_track_index,
+ Vector<double> &p_times);
+ void _convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, const String &p_animation_track_name);
+
Error _serialize(Ref<GLTFState> p_state);
Error _parse(Ref<GLTFState> p_state, String p_path, Ref<FileAccess> p_file);
};
diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp
index 7763874d02..2488e73d08 100644
--- a/modules/gltf/gltf_state.cpp
+++ b/modules/gltf/gltf_state.cpp
@@ -397,8 +397,27 @@ String GLTFState::get_base_path() {
return base_path;
}
-void GLTFState::set_base_path(String p_base_path) {
+void GLTFState::set_base_path(const String &p_base_path) {
base_path = p_base_path;
+ if (extract_path.is_empty()) {
+ extract_path = p_base_path;
+ }
+}
+
+String GLTFState::get_extract_path() {
+ return extract_path;
+}
+
+void GLTFState::set_extract_path(const String &p_extract_path) {
+ extract_path = p_extract_path;
+}
+
+String GLTFState::get_extract_prefix() {
+ return extract_prefix;
+}
+
+void GLTFState::set_extract_prefix(const String &p_extract_prefix) {
+ extract_prefix = p_extract_prefix;
}
String GLTFState::get_filename() const {
@@ -407,6 +426,9 @@ String GLTFState::get_filename() const {
void GLTFState::set_filename(const String &p_filename) {
filename = p_filename;
+ if (extract_prefix.is_empty()) {
+ extract_prefix = p_filename.get_basename();
+ }
}
Variant GLTFState::get_additional_data(const StringName &p_extension_name) {
diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h
index 7954049192..d667cf8858 100644
--- a/modules/gltf/gltf_state.h
+++ b/modules/gltf/gltf_state.h
@@ -38,6 +38,7 @@
#include "structures/gltf_camera.h"
#include "structures/gltf_mesh.h"
#include "structures/gltf_node.h"
+#include "structures/gltf_object_model_property.h"
#include "structures/gltf_skeleton.h"
#include "structures/gltf_skin.h"
#include "structures/gltf_texture.h"
@@ -48,9 +49,12 @@
class GLTFState : public Resource {
GDCLASS(GLTFState, Resource);
friend class GLTFDocument;
+ friend class GLTFNode;
protected:
String base_path;
+ String extract_path;
+ String extract_prefix;
String filename;
Dictionary json;
int major_version = 0;
@@ -100,6 +104,7 @@ protected:
Vector<Ref<GLTFAnimation>> animations;
HashMap<GLTFNodeIndex, Node *> scene_nodes;
HashMap<GLTFNodeIndex, ImporterMeshInstance3D *> scene_mesh_instances;
+ HashMap<String, Ref<GLTFObjectModelProperty>> object_model_properties;
HashMap<ObjectID, GLTFSkeletonIndex> skeleton3d_to_gltf_skeleton;
HashMap<ObjectID, HashMap<ObjectID, GLTFSkinIndex>> skin_and_skeleton3d_to_gltf_skin;
@@ -186,7 +191,13 @@ public:
void set_scene_name(String p_scene_name);
String get_base_path();
- void set_base_path(String p_base_path);
+ void set_base_path(const String &p_base_path);
+
+ String get_extract_path();
+ void set_extract_path(const String &p_extract_path);
+
+ String get_extract_prefix();
+ void set_extract_prefix(const String &p_extract_prefix);
String get_filename() const;
void set_filename(const String &p_filename);
diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp
index 53e9f2b84c..fbc3ae611c 100644
--- a/modules/gltf/register_types.cpp
+++ b/modules/gltf/register_types.cpp
@@ -37,6 +37,7 @@
#include "extensions/physics/gltf_document_extension_physics.h"
#include "gltf_document.h"
#include "gltf_state.h"
+#include "structures/gltf_object_model_property.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_import_blend_runner.h"
@@ -112,6 +113,7 @@ void initialize_gltf_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(GLTFLight);
GDREGISTER_CLASS(GLTFMesh);
GDREGISTER_CLASS(GLTFNode);
+ GDREGISTER_CLASS(GLTFObjectModelProperty);
GDREGISTER_CLASS(GLTFPhysicsBody);
GDREGISTER_CLASS(GLTFPhysicsShape);
GDREGISTER_CLASS(GLTFSkeleton);
diff --git a/modules/gltf/structures/gltf_accessor.cpp b/modules/gltf/structures/gltf_accessor.cpp
index 1ebc00a514..300fce09ff 100644
--- a/modules/gltf/structures/gltf_accessor.cpp
+++ b/modules/gltf/structures/gltf_accessor.cpp
@@ -39,6 +39,19 @@ void GLTFAccessor::_bind_methods() {
BIND_ENUM_CONSTANT(TYPE_MAT3);
BIND_ENUM_CONSTANT(TYPE_MAT4);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_NONE);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_SIGNED_BYTE);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_UNSIGNED_BYTE);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_SIGNED_SHORT);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_UNSIGNED_SHORT);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_SIGNED_INT);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_UNSIGNED_INT);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_SINGLE_FLOAT);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_DOUBLE_FLOAT);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_HALF_FLOAT);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_SIGNED_LONG);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_UNSIGNED_LONG);
+
ClassDB::bind_method(D_METHOD("get_buffer_view"), &GLTFAccessor::get_buffer_view);
ClassDB::bind_method(D_METHOD("set_buffer_view", "buffer_view"), &GLTFAccessor::set_buffer_view);
ClassDB::bind_method(D_METHOD("get_byte_offset"), &GLTFAccessor::get_byte_offset);
@@ -108,7 +121,7 @@ int GLTFAccessor::get_component_type() {
}
void GLTFAccessor::set_component_type(int p_component_type) {
- component_type = p_component_type;
+ component_type = (GLTFComponentType)p_component_type;
}
bool GLTFAccessor::get_normalized() {
@@ -188,7 +201,7 @@ int GLTFAccessor::get_sparse_indices_component_type() {
}
void GLTFAccessor::set_sparse_indices_component_type(int p_sparse_indices_component_type) {
- sparse_indices_component_type = p_sparse_indices_component_type;
+ sparse_indices_component_type = (GLTFComponentType)p_sparse_indices_component_type;
}
int GLTFAccessor::get_sparse_values_buffer_view() {
diff --git a/modules/gltf/structures/gltf_accessor.h b/modules/gltf/structures/gltf_accessor.h
index 1a3a2cb494..b00e6a0f92 100644
--- a/modules/gltf/structures/gltf_accessor.h
+++ b/modules/gltf/structures/gltf_accessor.h
@@ -50,10 +50,25 @@ public:
TYPE_MAT4,
};
+ enum GLTFComponentType {
+ COMPONENT_TYPE_NONE = 0,
+ COMPONENT_TYPE_SIGNED_BYTE = 5120,
+ COMPONENT_TYPE_UNSIGNED_BYTE = 5121,
+ COMPONENT_TYPE_SIGNED_SHORT = 5122,
+ COMPONENT_TYPE_UNSIGNED_SHORT = 5123,
+ COMPONENT_TYPE_SIGNED_INT = 5124,
+ COMPONENT_TYPE_UNSIGNED_INT = 5125,
+ COMPONENT_TYPE_SINGLE_FLOAT = 5126,
+ COMPONENT_TYPE_DOUBLE_FLOAT = 5130,
+ COMPONENT_TYPE_HALF_FLOAT = 5131,
+ COMPONENT_TYPE_SIGNED_LONG = 5134,
+ COMPONENT_TYPE_UNSIGNED_LONG = 5135,
+ };
+
private:
GLTFBufferViewIndex buffer_view = -1;
int byte_offset = 0;
- int component_type = 0;
+ GLTFComponentType component_type = COMPONENT_TYPE_NONE;
bool normalized = false;
int count = 0;
GLTFAccessorType accessor_type = GLTFAccessorType::TYPE_SCALAR;
@@ -62,7 +77,7 @@ private:
int sparse_count = 0;
int sparse_indices_buffer_view = 0;
int sparse_indices_byte_offset = 0;
- int sparse_indices_component_type = 0;
+ GLTFComponentType sparse_indices_component_type = COMPONENT_TYPE_NONE;
int sparse_values_buffer_view = 0;
int sparse_values_byte_offset = 0;
@@ -117,5 +132,6 @@ public:
};
VARIANT_ENUM_CAST(GLTFAccessor::GLTFAccessorType);
+VARIANT_ENUM_CAST(GLTFAccessor::GLTFComponentType);
#endif // GLTF_ACCESSOR_H
diff --git a/modules/gltf/structures/gltf_animation.cpp b/modules/gltf/structures/gltf_animation.cpp
index 94fda8e2f5..adc0354c4b 100644
--- a/modules/gltf/structures/gltf_animation.cpp
+++ b/modules/gltf/structures/gltf_animation.cpp
@@ -42,6 +42,34 @@ void GLTFAnimation::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "get_loop"); // bool
}
+GLTFAnimation::Interpolation GLTFAnimation::godot_to_gltf_interpolation(const Ref<Animation> &p_godot_animation, int32_t p_godot_anim_track_index) {
+ Animation::InterpolationType interpolation = p_godot_animation->track_get_interpolation_type(p_godot_anim_track_index);
+ switch (interpolation) {
+ case Animation::INTERPOLATION_LINEAR:
+ case Animation::INTERPOLATION_LINEAR_ANGLE:
+ return INTERP_LINEAR;
+ case Animation::INTERPOLATION_NEAREST:
+ return INTERP_STEP;
+ case Animation::INTERPOLATION_CUBIC:
+ case Animation::INTERPOLATION_CUBIC_ANGLE:
+ return INTERP_CUBIC_SPLINE;
+ }
+ return INTERP_LINEAR;
+}
+
+Animation::InterpolationType GLTFAnimation::gltf_to_godot_interpolation(Interpolation p_gltf_interpolation) {
+ switch (p_gltf_interpolation) {
+ case INTERP_LINEAR:
+ return Animation::INTERPOLATION_LINEAR;
+ case INTERP_STEP:
+ return Animation::INTERPOLATION_NEAREST;
+ case INTERP_CATMULLROMSPLINE:
+ case INTERP_CUBIC_SPLINE:
+ return Animation::INTERPOLATION_CUBIC;
+ }
+ return Animation::INTERPOLATION_LINEAR;
+}
+
String GLTFAnimation::get_original_name() {
return original_name;
}
@@ -58,8 +86,16 @@ void GLTFAnimation::set_loop(bool p_val) {
loop = p_val;
}
-HashMap<int, GLTFAnimation::Track> &GLTFAnimation::get_tracks() {
- return tracks;
+HashMap<int, GLTFAnimation::NodeTrack> &GLTFAnimation::get_node_tracks() {
+ return node_tracks;
+}
+
+HashMap<String, GLTFAnimation::Channel<Variant>> &GLTFAnimation::get_pointer_tracks() {
+ return pointer_tracks;
+}
+
+bool GLTFAnimation::is_empty_of_tracks() const {
+ return node_tracks.is_empty() && pointer_tracks.is_empty();
}
GLTFAnimation::GLTFAnimation() {
diff --git a/modules/gltf/structures/gltf_animation.h b/modules/gltf/structures/gltf_animation.h
index afc9784895..6b692d06e6 100644
--- a/modules/gltf/structures/gltf_animation.h
+++ b/modules/gltf/structures/gltf_animation.h
@@ -50,33 +50,41 @@ public:
template <typename T>
struct Channel {
Interpolation interpolation = INTERP_LINEAR;
- Vector<real_t> times;
+ Vector<double> times;
Vector<T> values;
};
- struct Track {
+ struct NodeTrack {
Channel<Vector3> position_track;
Channel<Quaternion> rotation_track;
Channel<Vector3> scale_track;
Vector<Channel<real_t>> weight_tracks;
};
+ String original_name;
+ bool loop = false;
+ HashMap<int, NodeTrack> node_tracks;
+ HashMap<String, Channel<Variant>> pointer_tracks;
+ Dictionary additional_data;
+
public:
+ static Interpolation godot_to_gltf_interpolation(const Ref<Animation> &p_godot_animation, int32_t p_godot_anim_track_index);
+ static Animation::InterpolationType gltf_to_godot_interpolation(Interpolation p_gltf_interpolation);
+
String get_original_name();
void set_original_name(String p_name);
bool get_loop() const;
void set_loop(bool p_val);
- HashMap<int, GLTFAnimation::Track> &get_tracks();
+
+ HashMap<int, GLTFAnimation::NodeTrack> &get_node_tracks();
+ HashMap<String, GLTFAnimation::Channel<Variant>> &get_pointer_tracks();
+ bool is_empty_of_tracks() const;
+
Variant get_additional_data(const StringName &p_extension_name);
void set_additional_data(const StringName &p_extension_name, Variant p_additional_data);
- GLTFAnimation();
-private:
- String original_name;
- bool loop = false;
- HashMap<int, Track> tracks;
- Dictionary additional_data;
+ GLTFAnimation();
};
#endif // GLTF_ANIMATION_H
diff --git a/modules/gltf/structures/gltf_camera.cpp b/modules/gltf/structures/gltf_camera.cpp
index 863e1df967..2960ec351d 100644
--- a/modules/gltf/structures/gltf_camera.cpp
+++ b/modules/gltf/structures/gltf_camera.cpp
@@ -30,6 +30,7 @@
#include "gltf_camera.h"
+#include "gltf_object_model_property.h"
#include "scene/3d/camera_3d.h"
void GLTFCamera::_bind_methods() {
@@ -57,6 +58,21 @@ void GLTFCamera::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth_near"), "set_depth_near", "get_depth_near");
}
+void GLTFCamera::set_fov_conversion_expressions(Ref<GLTFObjectModelProperty> &r_obj_model_prop) {
+ // Expression to convert glTF yfov in radians to Godot fov in degrees.
+ Ref<Expression> gltf_to_godot_expr;
+ gltf_to_godot_expr.instantiate();
+ PackedStringArray gltf_to_godot_args = { "yfov_rad" };
+ gltf_to_godot_expr->parse("rad_to_deg(yfov_rad)", gltf_to_godot_args);
+ r_obj_model_prop->set_gltf_to_godot_expression(gltf_to_godot_expr);
+ // Expression to convert Godot fov in degrees to glTF yfov in radians.
+ Ref<Expression> godot_to_gltf_expr;
+ godot_to_gltf_expr.instantiate();
+ PackedStringArray godot_to_gltf_args = { "fov_deg" };
+ godot_to_gltf_expr->parse("deg_to_rad(fov_deg)", godot_to_gltf_args);
+ r_obj_model_prop->set_godot_to_gltf_expression(godot_to_gltf_expr);
+}
+
Ref<GLTFCamera> GLTFCamera::from_node(const Camera3D *p_camera) {
Ref<GLTFCamera> c;
c.instantiate();
diff --git a/modules/gltf/structures/gltf_camera.h b/modules/gltf/structures/gltf_camera.h
index 1a583c82cc..497b6cd4f1 100644
--- a/modules/gltf/structures/gltf_camera.h
+++ b/modules/gltf/structures/gltf_camera.h
@@ -34,6 +34,7 @@
#include "core/io/resource.h"
class Camera3D;
+class GLTFObjectModelProperty;
// Reference and test file:
// https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_015_SimpleCameras.md
@@ -54,6 +55,8 @@ protected:
static void _bind_methods();
public:
+ static void set_fov_conversion_expressions(Ref<GLTFObjectModelProperty> &r_obj_model_prop);
+
bool get_perspective() const { return perspective; }
void set_perspective(bool p_val) { perspective = p_val; }
real_t get_fov() const { return fov; }
diff --git a/modules/gltf/structures/gltf_node.cpp b/modules/gltf/structures/gltf_node.cpp
index ccee5e8ca4..1626313551 100644
--- a/modules/gltf/structures/gltf_node.cpp
+++ b/modules/gltf/structures/gltf_node.cpp
@@ -30,6 +30,8 @@
#include "gltf_node.h"
+#include "../gltf_state.h"
+
void GLTFNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_original_name"), &GLTFNode::get_original_name);
ClassDB::bind_method(D_METHOD("set_original_name", "original_name"), &GLTFNode::set_original_name);
@@ -60,6 +62,7 @@ void GLTFNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_light", "light"), &GLTFNode::set_light);
ClassDB::bind_method(D_METHOD("get_additional_data", "extension_name"), &GLTFNode::get_additional_data);
ClassDB::bind_method(D_METHOD("set_additional_data", "extension_name", "additional_data"), &GLTFNode::set_additional_data);
+ ClassDB::bind_method(D_METHOD("get_scene_node_path", "gltf_state", "handle_skeletons"), &GLTFNode::get_scene_node_path, DEFVAL(true));
ADD_PROPERTY(PropertyInfo(Variant::STRING, "original_name"), "set_original_name", "get_original_name"); // String
ADD_PROPERTY(PropertyInfo(Variant::INT, "parent"), "set_parent", "get_parent"); // GLTFNodeIndex
@@ -187,6 +190,48 @@ Variant GLTFNode::get_additional_data(const StringName &p_extension_name) {
return additional_data[p_extension_name];
}
+bool GLTFNode::has_additional_data(const StringName &p_extension_name) {
+ return additional_data.has(p_extension_name);
+}
+
void GLTFNode::set_additional_data(const StringName &p_extension_name, Variant p_additional_data) {
additional_data[p_extension_name] = p_additional_data;
}
+
+NodePath GLTFNode::get_scene_node_path(Ref<GLTFState> p_state, bool p_handle_skeletons) {
+ Vector<StringName> path;
+ Vector<StringName> subpath;
+ Ref<GLTFNode> current_gltf_node = this;
+ const int gltf_node_count = p_state->nodes.size();
+ if (p_handle_skeletons && skeleton != -1) {
+ // Special case for skeleton nodes, skip all bones so that the path is to the Skeleton3D node.
+ // A path that would otherwise be `A/B/C/Bone1/Bone2/Bone3` becomes `A/B/C/Skeleton3D:Bone3`.
+ subpath.append(get_name());
+ // The generated Skeleton3D node will be named Skeleton3D, so add it to the path.
+ path.append("Skeleton3D");
+ do {
+ const int parent_index = current_gltf_node->get_parent();
+ ERR_FAIL_INDEX_V(parent_index, gltf_node_count, NodePath());
+ current_gltf_node = p_state->nodes[parent_index];
+ } while (current_gltf_node->skeleton != -1);
+ }
+ const bool is_godot_single_root = p_state->extensions_used.has("GODOT_single_root");
+ while (true) {
+ const int parent_index = current_gltf_node->get_parent();
+ if (is_godot_single_root && parent_index == -1) {
+ // For GODOT_single_root scenes, the root glTF node becomes the Godot scene root, so it
+ // should not be included in the path. Ex: A/B/C, A is single root, we want B/C only.
+ break;
+ }
+ path.insert(0, current_gltf_node->get_name());
+ if (!is_godot_single_root && parent_index == -1) {
+ break;
+ }
+ ERR_FAIL_INDEX_V(parent_index, gltf_node_count, NodePath());
+ current_gltf_node = p_state->nodes[parent_index];
+ }
+ if (unlikely(path.is_empty())) {
+ path.append(".");
+ }
+ return NodePath(path, subpath, false);
+}
diff --git a/modules/gltf/structures/gltf_node.h b/modules/gltf/structures/gltf_node.h
index f3f6bfa2f1..f72b65a003 100644
--- a/modules/gltf/structures/gltf_node.h
+++ b/modules/gltf/structures/gltf_node.h
@@ -103,7 +103,10 @@ public:
void set_light(GLTFLightIndex p_light);
Variant get_additional_data(const StringName &p_extension_name);
+ bool has_additional_data(const StringName &p_extension_name);
void set_additional_data(const StringName &p_extension_name, Variant p_additional_data);
+
+ NodePath get_scene_node_path(Ref<GLTFState> p_state, bool p_handle_skeletons = true);
};
#endif // GLTF_NODE_H
diff --git a/modules/gltf/structures/gltf_object_model_property.cpp b/modules/gltf/structures/gltf_object_model_property.cpp
new file mode 100644
index 0000000000..d405c362db
--- /dev/null
+++ b/modules/gltf/structures/gltf_object_model_property.cpp
@@ -0,0 +1,173 @@
+/**************************************************************************/
+/* gltf_object_model_property.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 "gltf_object_model_property.h"
+
+#include "../gltf_template_convert.h"
+
+void GLTFObjectModelProperty::_bind_methods() {
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_UNKNOWN);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_BOOL);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT_ARRAY);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT2);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT4);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT2X2);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT3X3);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT4X4);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_INT);
+
+ ClassDB::bind_method(D_METHOD("append_node_path", "node_path"), &GLTFObjectModelProperty::append_node_path);
+ ClassDB::bind_method(D_METHOD("append_path_to_property", "node_path", "prop_name"), &GLTFObjectModelProperty::append_path_to_property);
+
+ ClassDB::bind_method(D_METHOD("get_accessor_type"), &GLTFObjectModelProperty::get_accessor_type);
+ ClassDB::bind_method(D_METHOD("get_gltf_to_godot_expression"), &GLTFObjectModelProperty::get_gltf_to_godot_expression);
+ ClassDB::bind_method(D_METHOD("set_gltf_to_godot_expression", "gltf_to_godot_expr"), &GLTFObjectModelProperty::set_gltf_to_godot_expression);
+ ClassDB::bind_method(D_METHOD("get_godot_to_gltf_expression"), &GLTFObjectModelProperty::get_godot_to_gltf_expression);
+ ClassDB::bind_method(D_METHOD("set_godot_to_gltf_expression", "godot_to_gltf_expr"), &GLTFObjectModelProperty::set_godot_to_gltf_expression);
+ ClassDB::bind_method(D_METHOD("get_node_paths"), &GLTFObjectModelProperty::get_node_paths);
+ ClassDB::bind_method(D_METHOD("has_node_paths"), &GLTFObjectModelProperty::has_node_paths);
+ ClassDB::bind_method(D_METHOD("set_node_paths", "node_paths"), &GLTFObjectModelProperty::set_node_paths);
+ ClassDB::bind_method(D_METHOD("get_object_model_type"), &GLTFObjectModelProperty::get_object_model_type);
+ ClassDB::bind_method(D_METHOD("set_object_model_type", "type"), &GLTFObjectModelProperty::set_object_model_type);
+ ClassDB::bind_method(D_METHOD("get_json_pointers"), &GLTFObjectModelProperty::get_json_pointers_bind);
+ ClassDB::bind_method(D_METHOD("has_json_pointers"), &GLTFObjectModelProperty::has_json_pointers);
+ ClassDB::bind_method(D_METHOD("set_json_pointers", "json_pointers"), &GLTFObjectModelProperty::set_json_pointers_bind);
+ ClassDB::bind_method(D_METHOD("get_variant_type"), &GLTFObjectModelProperty::get_variant_type);
+ ClassDB::bind_method(D_METHOD("set_variant_type", "variant_type"), &GLTFObjectModelProperty::set_variant_type);
+ ClassDB::bind_method(D_METHOD("set_types", "variant_type", "obj_model_type"), &GLTFObjectModelProperty::set_types);
+
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gltf_to_godot_expression", PROPERTY_HINT_RESOURCE_TYPE, "Expression"), "set_gltf_to_godot_expression", "get_gltf_to_godot_expression"); // Ref<Expression>
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "godot_to_gltf_expression", PROPERTY_HINT_RESOURCE_TYPE, "Expression"), "set_godot_to_gltf_expression", "get_godot_to_gltf_expression"); // Ref<Expression>
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "node_paths", PROPERTY_HINT_TYPE_STRING, "NodePath"), "set_node_paths", "get_node_paths"); // TypedArray<NodePath>
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "object_model_type"), "set_object_model_type", "get_object_model_type"); // GLTFObjectModelType
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "json_pointers"), "set_json_pointers", "get_json_pointers"); // TypedArray<PackedStringArray>
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "variant_type"), "set_variant_type", "get_variant_type"); // Variant::Type
+}
+
+void GLTFObjectModelProperty::append_node_path(const NodePath &p_node_path) {
+ node_paths.push_back(p_node_path);
+}
+
+void GLTFObjectModelProperty::append_path_to_property(const NodePath &p_node_path, const StringName &p_prop_name) {
+ Vector<StringName> node_names = p_node_path.get_names();
+ Vector<StringName> subpath = p_node_path.get_subnames();
+ subpath.append(p_prop_name);
+ node_paths.push_back(NodePath(node_names, subpath, false));
+}
+
+GLTFAccessor::GLTFAccessorType GLTFObjectModelProperty::get_accessor_type() const {
+ switch (object_model_type) {
+ case GLTF_OBJECT_MODEL_TYPE_FLOAT2:
+ return GLTFAccessor::TYPE_VEC2;
+ case GLTF_OBJECT_MODEL_TYPE_FLOAT3:
+ return GLTFAccessor::TYPE_VEC3;
+ case GLTF_OBJECT_MODEL_TYPE_FLOAT4:
+ return GLTFAccessor::TYPE_VEC4;
+ case GLTF_OBJECT_MODEL_TYPE_FLOAT2X2:
+ return GLTFAccessor::TYPE_MAT2;
+ case GLTF_OBJECT_MODEL_TYPE_FLOAT3X3:
+ return GLTFAccessor::TYPE_MAT3;
+ case GLTF_OBJECT_MODEL_TYPE_FLOAT4X4:
+ return GLTFAccessor::TYPE_MAT4;
+ default:
+ return GLTFAccessor::TYPE_SCALAR;
+ }
+}
+
+Ref<Expression> GLTFObjectModelProperty::get_gltf_to_godot_expression() const {
+ return gltf_to_godot_expr;
+}
+
+void GLTFObjectModelProperty::set_gltf_to_godot_expression(Ref<Expression> p_gltf_to_godot_expr) {
+ gltf_to_godot_expr = p_gltf_to_godot_expr;
+}
+
+Ref<Expression> GLTFObjectModelProperty::get_godot_to_gltf_expression() const {
+ return godot_to_gltf_expr;
+}
+
+void GLTFObjectModelProperty::set_godot_to_gltf_expression(Ref<Expression> p_godot_to_gltf_expr) {
+ godot_to_gltf_expr = p_godot_to_gltf_expr;
+}
+
+TypedArray<NodePath> GLTFObjectModelProperty::get_node_paths() const {
+ return node_paths;
+}
+
+bool GLTFObjectModelProperty::has_node_paths() const {
+ return !node_paths.is_empty();
+}
+
+void GLTFObjectModelProperty::set_node_paths(TypedArray<NodePath> p_node_paths) {
+ node_paths = p_node_paths;
+}
+
+GLTFObjectModelProperty::GLTFObjectModelType GLTFObjectModelProperty::get_object_model_type() const {
+ return object_model_type;
+}
+
+void GLTFObjectModelProperty::set_object_model_type(GLTFObjectModelType p_type) {
+ object_model_type = p_type;
+}
+
+Vector<PackedStringArray> GLTFObjectModelProperty::get_json_pointers() const {
+ return json_pointers;
+}
+
+bool GLTFObjectModelProperty::has_json_pointers() const {
+ return !json_pointers.is_empty();
+}
+
+void GLTFObjectModelProperty::set_json_pointers(const Vector<PackedStringArray> &p_json_pointers) {
+ json_pointers = p_json_pointers;
+}
+
+TypedArray<PackedStringArray> GLTFObjectModelProperty::get_json_pointers_bind() const {
+ return GLTFTemplateConvert::to_array(json_pointers);
+}
+
+void GLTFObjectModelProperty::set_json_pointers_bind(const TypedArray<PackedStringArray> &p_json_pointers) {
+ GLTFTemplateConvert::set_from_array(json_pointers, p_json_pointers);
+}
+
+Variant::Type GLTFObjectModelProperty::get_variant_type() const {
+ return variant_type;
+}
+
+void GLTFObjectModelProperty::set_variant_type(Variant::Type p_variant_type) {
+ variant_type = p_variant_type;
+}
+
+void GLTFObjectModelProperty::set_types(Variant::Type p_variant_type, GLTFObjectModelType p_obj_model_type) {
+ variant_type = p_variant_type;
+ object_model_type = p_obj_model_type;
+}
diff --git a/modules/gltf/structures/gltf_object_model_property.h b/modules/gltf/structures/gltf_object_model_property.h
new file mode 100644
index 0000000000..d8a4ed420a
--- /dev/null
+++ b/modules/gltf/structures/gltf_object_model_property.h
@@ -0,0 +1,104 @@
+/**************************************************************************/
+/* gltf_object_model_property.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 GLTF_OBJECT_MODEL_PROPERTY_H
+#define GLTF_OBJECT_MODEL_PROPERTY_H
+
+#include "core/math/expression.h"
+#include "core/variant/typed_array.h"
+#include "gltf_accessor.h"
+
+// Object model: https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/ObjectModel.adoc
+// KHR_animation_pointer: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_animation_pointer
+
+class GLTFObjectModelProperty : public RefCounted {
+ GDCLASS(GLTFObjectModelProperty, RefCounted);
+
+public:
+ enum GLTFObjectModelType {
+ GLTF_OBJECT_MODEL_TYPE_UNKNOWN,
+ GLTF_OBJECT_MODEL_TYPE_BOOL,
+ GLTF_OBJECT_MODEL_TYPE_FLOAT,
+ GLTF_OBJECT_MODEL_TYPE_FLOAT_ARRAY,
+ GLTF_OBJECT_MODEL_TYPE_FLOAT2,
+ GLTF_OBJECT_MODEL_TYPE_FLOAT3,
+ GLTF_OBJECT_MODEL_TYPE_FLOAT4,
+ GLTF_OBJECT_MODEL_TYPE_FLOAT2X2,
+ GLTF_OBJECT_MODEL_TYPE_FLOAT3X3,
+ GLTF_OBJECT_MODEL_TYPE_FLOAT4X4,
+ GLTF_OBJECT_MODEL_TYPE_INT,
+ };
+
+private:
+ Ref<Expression> gltf_to_godot_expr;
+ Ref<Expression> godot_to_gltf_expr;
+ TypedArray<NodePath> node_paths;
+ GLTFObjectModelType object_model_type = GLTF_OBJECT_MODEL_TYPE_UNKNOWN;
+ Vector<PackedStringArray> json_pointers;
+ Variant::Type variant_type = Variant::NIL;
+
+protected:
+ static void _bind_methods();
+
+public:
+ void append_node_path(const NodePath &p_node_path);
+ void append_path_to_property(const NodePath &p_node_path, const StringName &p_prop_name);
+
+ GLTFAccessor::GLTFAccessorType get_accessor_type() const;
+
+ Ref<Expression> get_gltf_to_godot_expression() const;
+ void set_gltf_to_godot_expression(Ref<Expression> p_gltf_to_godot_expr);
+
+ Ref<Expression> get_godot_to_gltf_expression() const;
+ void set_godot_to_gltf_expression(Ref<Expression> p_godot_to_gltf_expr);
+
+ TypedArray<NodePath> get_node_paths() const;
+ bool has_node_paths() const;
+ void set_node_paths(TypedArray<NodePath> p_node_paths);
+
+ GLTFObjectModelType get_object_model_type() const;
+ void set_object_model_type(GLTFObjectModelType p_type);
+
+ Vector<PackedStringArray> get_json_pointers() const;
+ bool has_json_pointers() const;
+ void set_json_pointers(const Vector<PackedStringArray> &p_json_pointers);
+
+ TypedArray<PackedStringArray> get_json_pointers_bind() const;
+ void set_json_pointers_bind(const TypedArray<PackedStringArray> &p_json_pointers);
+
+ Variant::Type get_variant_type() const;
+ void set_variant_type(Variant::Type p_variant_type);
+
+ void set_types(Variant::Type p_variant_type, GLTFObjectModelType p_obj_model_type);
+};
+
+VARIANT_ENUM_CAST(GLTFObjectModelProperty::GLTFObjectModelType);
+
+#endif // GLTF_OBJECT_MODEL_PROPERTY_H
diff --git a/modules/godot_physics_2d/godot_joints_2d.cpp b/modules/godot_physics_2d/godot_joints_2d.cpp
index 5c76eb9dad..d5a779ebb5 100644
--- a/modules/godot_physics_2d/godot_joints_2d.cpp
+++ b/modules/godot_physics_2d/godot_joints_2d.cpp
@@ -311,7 +311,7 @@ bool GodotPinJoint2D::get_flag(PhysicsServer2D::PinJointFlag p_flag) const {
return motor_enabled;
}
}
- ERR_FAIL_V(0);
+ ERR_FAIL_V(false);
}
GodotPinJoint2D::GodotPinJoint2D(const Vector2 &p_pos, GodotBody2D *p_body_a, GodotBody2D *p_body_b) :
diff --git a/modules/godot_physics_2d/godot_physics_server_2d.cpp b/modules/godot_physics_2d/godot_physics_server_2d.cpp
index 71d1d3b6b1..2516ed3616 100644
--- a/modules/godot_physics_2d/godot_physics_server_2d.cpp
+++ b/modules/godot_physics_2d/godot_physics_server_2d.cpp
@@ -1169,8 +1169,8 @@ void GodotPhysicsServer2D::pin_joint_set_flag(RID p_joint, PinJointFlag p_flag,
bool GodotPhysicsServer2D::pin_joint_get_flag(RID p_joint, PinJointFlag p_flag) const {
GodotJoint2D *joint = joint_owner.get_or_null(p_joint);
- ERR_FAIL_NULL_V(joint, 0);
- ERR_FAIL_COND_V(joint->get_type() != JOINT_TYPE_PIN, 0);
+ ERR_FAIL_NULL_V(joint, false);
+ ERR_FAIL_COND_V(joint->get_type() != JOINT_TYPE_PIN, false);
GodotPinJoint2D *pin_joint = static_cast<GodotPinJoint2D *>(joint);
return pin_joint->get_flag(p_flag);
diff --git a/modules/godot_physics_2d/godot_space_2d.cpp b/modules/godot_physics_2d/godot_space_2d.cpp
index 2966818beb..df3c9d8265 100644
--- a/modules/godot_physics_2d/godot_space_2d.cpp
+++ b/modules/godot_physics_2d/godot_space_2d.cpp
@@ -342,7 +342,7 @@ bool GodotPhysicsDirectSpaceState2D::collide_shape(const ShapeParameters &p_para
}
GodotShape2D *shape = GodotPhysicsServer2D::godot_singleton->shape_owner.get_or_null(p_parameters.shape_rid);
- ERR_FAIL_NULL_V(shape, 0);
+ ERR_FAIL_NULL_V(shape, false);
Rect2 aabb = p_parameters.transform.xform(shape->get_aabb());
aabb = aabb.merge(Rect2(aabb.position + p_parameters.motion, aabb.size)); //motion
@@ -439,7 +439,7 @@ static void _rest_cbk_result(const Vector2 &p_point_A, const Vector2 &p_point_B,
bool GodotPhysicsDirectSpaceState2D::rest_info(const ShapeParameters &p_parameters, ShapeRestInfo *r_info) {
GodotShape2D *shape = GodotPhysicsServer2D::godot_singleton->shape_owner.get_or_null(p_parameters.shape_rid);
- ERR_FAIL_NULL_V(shape, 0);
+ ERR_FAIL_NULL_V(shape, false);
real_t margin = MAX(p_parameters.margin, TEST_MOTION_MARGIN_MIN_VALUE);
diff --git a/modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp b/modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp
index c53c8481f4..2adbb51297 100644
--- a/modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp
+++ b/modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp
@@ -76,8 +76,9 @@ struct _CollectorCallback {
Vector3 *prev_axis = nullptr;
_FORCE_INLINE_ void call(const Vector3 &p_point_A, const Vector3 &p_point_B, Vector3 p_normal) {
- if (p_normal.dot(p_point_B - p_point_A) < 0)
+ if (p_normal.dot(p_point_B - p_point_A) < 0) {
p_normal = -p_normal;
+ }
if (swap) {
callback(p_point_B, 0, p_point_A, 0, -p_normal, userdata);
} else {
@@ -175,10 +176,11 @@ static void _generate_contacts_edge_edge(const Vector3 *p_points_A, int p_point_
// The normal should be perpendicular to both edges.
Vector3 normal = rel_A.cross(rel_B);
real_t normal_len = normal.length();
- if (normal_len > 1e-3)
+ if (normal_len > 1e-3) {
normal /= normal_len;
- else
+ } else {
normal = p_callback->normal;
+ }
p_callback->call(closest_A, closest_B, normal);
}
@@ -784,8 +786,9 @@ static void analytic_sphere_collision(const Vector3 &p_origin_a, real_t p_radius
// Calculate the sphere overlap, and bail if not overlapping
real_t overlap = p_radius_a + p_radius_b - b_to_a_len;
- if (overlap < 0)
+ if (overlap < 0) {
return;
+ }
// Report collision
p_collector->collided = true;
diff --git a/modules/godot_physics_3d/godot_physics_server_3d.cpp b/modules/godot_physics_3d/godot_physics_server_3d.cpp
index 43f8d2658d..ad55e415e6 100644
--- a/modules/godot_physics_3d/godot_physics_server_3d.cpp
+++ b/modules/godot_physics_3d/godot_physics_server_3d.cpp
@@ -826,7 +826,7 @@ void GodotPhysicsServer3D::body_set_axis_lock(RID p_body, BodyAxis p_axis, bool
bool GodotPhysicsServer3D::body_is_axis_locked(RID p_body, BodyAxis p_axis) const {
const GodotBody3D *body = body_owner.get_or_null(p_body);
- ERR_FAIL_NULL_V(body, 0);
+ ERR_FAIL_NULL_V(body, false);
return body->is_axis_locked(p_axis);
}
diff --git a/modules/godot_physics_3d/godot_shape_3d.cpp b/modules/godot_physics_3d/godot_shape_3d.cpp
index 70b6bcf19e..a7cccc5cb8 100644
--- a/modules/godot_physics_3d/godot_shape_3d.cpp
+++ b/modules/godot_physics_3d/godot_shape_3d.cpp
@@ -1133,8 +1133,9 @@ void GodotConvexPolygonShape3D::_setup(const Vector<Vector3> &p_vertices) {
max_support = s;
}
}
- if (!extreme_vertices.has(best_vertex))
+ if (!extreme_vertices.has(best_vertex)) {
extreme_vertices.push_back(best_vertex);
+ }
}
}
}
diff --git a/modules/godot_physics_3d/godot_space_3d.cpp b/modules/godot_physics_3d/godot_space_3d.cpp
index 9a6ba776b4..9f82a87f85 100644
--- a/modules/godot_physics_3d/godot_space_3d.cpp
+++ b/modules/godot_physics_3d/godot_space_3d.cpp
@@ -385,7 +385,7 @@ bool GodotPhysicsDirectSpaceState3D::collide_shape(const ShapeParameters &p_para
}
GodotShape3D *shape = GodotPhysicsServer3D::godot_singleton->shape_owner.get_or_null(p_parameters.shape_rid);
- ERR_FAIL_NULL_V(shape, 0);
+ ERR_FAIL_NULL_V(shape, false);
AABB aabb = p_parameters.transform.xform(shape->get_aabb());
aabb = aabb.grow(p_parameters.margin);
@@ -511,7 +511,7 @@ static void _rest_cbk_result(const Vector3 &p_point_A, int p_index_A, const Vect
bool GodotPhysicsDirectSpaceState3D::rest_info(const ShapeParameters &p_parameters, ShapeRestInfo *r_info) {
GodotShape3D *shape = GodotPhysicsServer3D::godot_singleton->shape_owner.get_or_null(p_parameters.shape_rid);
- ERR_FAIL_NULL_V(shape, 0);
+ ERR_FAIL_NULL_V(shape, false);
real_t margin = MAX(p_parameters.margin, TEST_MOTION_MARGIN_MIN_VALUE);
diff --git a/modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp b/modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp
index 226f8a0f7f..5f9cf9de49 100644
--- a/modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp
+++ b/modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp
@@ -647,7 +647,7 @@ void GodotGeneric6DOFJoint3D::set_flag(Vector3::Axis p_axis, PhysicsServer3D::G6
}
bool GodotGeneric6DOFJoint3D::get_flag(Vector3::Axis p_axis, PhysicsServer3D::G6DOFJointAxisFlag p_flag) const {
- ERR_FAIL_INDEX_V(p_axis, 3, 0);
+ ERR_FAIL_INDEX_V(p_axis, 3, false);
switch (p_flag) {
case PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_LINEAR_LIMIT: {
return m_linearLimits.enable_limit[p_axis];
diff --git a/modules/interactive_music/doc_classes/AudioStreamInteractive.xml b/modules/interactive_music/doc_classes/AudioStreamInteractive.xml
index 17448724d1..d47d6ecde6 100644
--- a/modules/interactive_music/doc_classes/AudioStreamInteractive.xml
+++ b/modules/interactive_music/doc_classes/AudioStreamInteractive.xml
@@ -119,7 +119,7 @@
<param index="0" name="from_clip" type="int" />
<param index="1" name="to_clip" type="int" />
<description>
- Return true if a given transition exists (was added via [method add_transition]).
+ Returns [code]true[/code] if a given transition exists (was added via [method add_transition]).
</description>
</method>
<method name="is_transition_holding_previous" qualifiers="const">
diff --git a/modules/meshoptimizer/register_types.cpp b/modules/meshoptimizer/register_types.cpp
index 781f928f66..ebfe5d9c5b 100644
--- a/modules/meshoptimizer/register_types.cpp
+++ b/modules/meshoptimizer/register_types.cpp
@@ -40,10 +40,10 @@ void initialize_meshoptimizer_module(ModuleInitializationLevel p_level) {
}
SurfaceTool::optimize_vertex_cache_func = meshopt_optimizeVertexCache;
+ SurfaceTool::optimize_vertex_fetch_remap_func = meshopt_optimizeVertexFetchRemap;
SurfaceTool::simplify_func = meshopt_simplify;
SurfaceTool::simplify_with_attrib_func = meshopt_simplifyWithAttributes;
SurfaceTool::simplify_scale_func = meshopt_simplifyScale;
- SurfaceTool::simplify_sloppy_func = meshopt_simplifySloppy;
SurfaceTool::generate_remap_func = meshopt_generateVertexRemap;
SurfaceTool::remap_vertex_func = meshopt_remapVertexBuffer;
SurfaceTool::remap_index_func = meshopt_remapIndexBuffer;
@@ -55,9 +55,9 @@ void uninitialize_meshoptimizer_module(ModuleInitializationLevel p_level) {
}
SurfaceTool::optimize_vertex_cache_func = nullptr;
+ SurfaceTool::optimize_vertex_fetch_remap_func = nullptr;
SurfaceTool::simplify_func = nullptr;
SurfaceTool::simplify_scale_func = nullptr;
- SurfaceTool::simplify_sloppy_func = nullptr;
SurfaceTool::generate_remap_func = nullptr;
SurfaceTool::remap_vertex_func = nullptr;
SurfaceTool::remap_index_func = nullptr;
diff --git a/modules/minimp3/resource_importer_mp3.h b/modules/minimp3/resource_importer_mp3.h
index 2df44deaea..037756328f 100644
--- a/modules/minimp3/resource_importer_mp3.h
+++ b/modules/minimp3/resource_importer_mp3.h
@@ -59,6 +59,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterMP3();
};
diff --git a/modules/navigation/nav_agent.h b/modules/navigation/nav_agent.h
index e3671504f2..d56e053ac4 100644
--- a/modules/navigation/nav_agent.h
+++ b/modules/navigation/nav_agent.h
@@ -67,7 +67,7 @@ class NavAgent : public NavRid {
uint32_t avoidance_mask = 1;
real_t avoidance_priority = 1.0;
- Callable avoidance_callback = Callable();
+ Callable avoidance_callback;
bool agent_dirty = true;
diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp
index 8df1db533d..04c8a5a943 100644
--- a/modules/navigation/nav_map.cpp
+++ b/modules/navigation/nav_map.cpp
@@ -426,26 +426,42 @@ void NavMap::sync() {
_new_pm_polygon_count = polygon_count;
+ struct ConnectionPair {
+ gd::Edge::Connection connections[2];
+ int size = 0;
+ };
+
// Group all edges per key.
- HashMap<gd::EdgeKey, Vector<gd::Edge::Connection>, gd::EdgeKey> connections;
+ HashMap<gd::EdgeKey, ConnectionPair, gd::EdgeKey> connection_pairs_map;
+ connection_pairs_map.reserve(polygons.size());
+ int free_edges_count = 0; // How many ConnectionPairs have only one Connection.
+
for (gd::Polygon &poly : polygons) {
for (uint32_t p = 0; p < poly.points.size(); p++) {
- int next_point = (p + 1) % poly.points.size();
- gd::EdgeKey ek(poly.points[p].key, poly.points[next_point].key);
+ const int next_point = (p + 1) % poly.points.size();
+ const gd::EdgeKey ek(poly.points[p].key, poly.points[next_point].key);
- HashMap<gd::EdgeKey, Vector<gd::Edge::Connection>, gd::EdgeKey>::Iterator connection = connections.find(ek);
- if (!connection) {
- connections[ek] = Vector<gd::Edge::Connection>();
+ HashMap<gd::EdgeKey, ConnectionPair, gd::EdgeKey>::Iterator pair_it = connection_pairs_map.find(ek);
+ if (!pair_it) {
+ pair_it = connection_pairs_map.insert(ek, ConnectionPair());
_new_pm_edge_count += 1;
+ ++free_edges_count;
}
- if (connections[ek].size() <= 1) {
+ ConnectionPair &pair = pair_it->value;
+ if (pair.size < 2) {
// Add the polygon/edge tuple to this key.
gd::Edge::Connection new_connection;
new_connection.polygon = &poly;
new_connection.edge = p;
new_connection.pathway_start = poly.points[p].pos;
new_connection.pathway_end = poly.points[next_point].pos;
- connections[ek].push_back(new_connection);
+
+ pair.connections[pair.size] = new_connection;
+ ++pair.size;
+ if (pair.size == 2) {
+ --free_edges_count;
+ }
+
} else {
// The edge is already connected with another edge, skip.
ERR_PRINT_ONCE("Navigation map synchronization error. Attempted to merge a navigation mesh polygon edge with another already-merged edge. This is usually caused by crossing edges, overlapping polygons, or a mismatch of the NavigationMesh / NavigationPolygon baked 'cell_size' and navigation map 'cell_size'. If you're certain none of above is the case, change 'navigation/3d/merge_rasterizer_cell_scale' to 0.001.");
@@ -453,20 +469,23 @@ void NavMap::sync() {
}
}
- Vector<gd::Edge::Connection> free_edges;
- for (KeyValue<gd::EdgeKey, Vector<gd::Edge::Connection>> &E : connections) {
- if (E.value.size() == 2) {
+ LocalVector<gd::Edge::Connection> free_edges;
+ free_edges.reserve(free_edges_count);
+
+ for (const KeyValue<gd::EdgeKey, ConnectionPair> &pair_it : connection_pairs_map) {
+ const ConnectionPair &pair = pair_it.value;
+ if (pair.size == 2) {
// Connect edge that are shared in different polygons.
- gd::Edge::Connection &c1 = E.value.write[0];
- gd::Edge::Connection &c2 = E.value.write[1];
+ const gd::Edge::Connection &c1 = pair.connections[0];
+ const gd::Edge::Connection &c2 = pair.connections[1];
c1.polygon->edges[c1.edge].connections.push_back(c2);
c2.polygon->edges[c2.edge].connections.push_back(c1);
// Note: The pathway_start/end are full for those connection and do not need to be modified.
_new_pm_edge_merge_count += 1;
} else {
- CRASH_COND_MSG(E.value.size() != 1, vformat("Number of connection != 1. Found: %d", E.value.size()));
- if (use_edge_connections && E.value[0].polygon->owner->get_use_edge_connections()) {
- free_edges.push_back(E.value[0]);
+ CRASH_COND_MSG(pair.size != 1, vformat("Number of connection != 1. Found: %d", pair.size));
+ if (use_edge_connections && pair.connections[0].polygon->owner->get_use_edge_connections()) {
+ free_edges.push_back(pair.connections[0]);
}
}
}
@@ -480,14 +499,14 @@ void NavMap::sync() {
// connection, integration and path finding.
_new_pm_edge_free_count = free_edges.size();
- real_t sqr_edge_connection_margin = edge_connection_margin * edge_connection_margin;
+ const real_t edge_connection_margin_squared = edge_connection_margin * edge_connection_margin;
- for (int i = 0; i < free_edges.size(); i++) {
+ for (uint32_t i = 0; i < free_edges.size(); i++) {
const gd::Edge::Connection &free_edge = free_edges[i];
Vector3 edge_p1 = free_edge.polygon->points[free_edge.edge].pos;
Vector3 edge_p2 = free_edge.polygon->points[(free_edge.edge + 1) % free_edge.polygon->points.size()].pos;
- for (int j = 0; j < free_edges.size(); j++) {
+ for (uint32_t j = 0; j < free_edges.size(); j++) {
const gd::Edge::Connection &other_edge = free_edges[j];
if (i == j || free_edge.polygon->owner == other_edge.polygon->owner) {
continue;
@@ -512,7 +531,7 @@ void NavMap::sync() {
} else {
other1 = other_edge_p1.lerp(other_edge_p2, (1.0 - projected_p1_ratio) / (projected_p2_ratio - projected_p1_ratio));
}
- if (other1.distance_squared_to(self1) > sqr_edge_connection_margin) {
+ if (other1.distance_squared_to(self1) > edge_connection_margin_squared) {
continue;
}
@@ -523,7 +542,7 @@ void NavMap::sync() {
} else {
other2 = other_edge_p1.lerp(other_edge_p2, (0.0 - projected_p1_ratio) / (projected_p2_ratio - projected_p1_ratio));
}
- if (other2.distance_squared_to(self2) > sqr_edge_connection_margin) {
+ if (other2.distance_squared_to(self2) > edge_connection_margin_squared) {
continue;
}
diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml
index 341b50065c..cfc7cd4d97 100644
--- a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml
+++ b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml
@@ -29,7 +29,7 @@
<method name="is_natively_supported" qualifiers="const">
<return type="bool" />
<description>
- Returns true if the OpenXR runtime natively supports this composition layer type.
+ Returns [code]true[/code] if the OpenXR runtime natively supports this composition layer type.
[b]Note:[/b] This will only return an accurate result after the OpenXR session has started.
</description>
</method>
diff --git a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml
index 813c9d582e..182fe32f9c 100644
--- a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml
+++ b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml
@@ -90,6 +90,21 @@
Called right after the main swapchains are (re)created.
</description>
</method>
+ <method name="_on_post_draw_viewport" qualifiers="virtual">
+ <return type="void" />
+ <param index="0" name="viewport" type="RID" />
+ <description>
+ Called right after the given viewport is rendered.
+ [b]Note:[/b] The draw commands might only be queued at this point, not executed.
+ </description>
+ </method>
+ <method name="_on_pre_draw_viewport" qualifiers="virtual">
+ <return type="void" />
+ <param index="0" name="viewport" type="RID" />
+ <description>
+ Called right before the given viewport is rendered.
+ </description>
+ </method>
<method name="_on_pre_render" qualifiers="virtual">
<return type="void" />
<description>
diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.cpp b/modules/openxr/extensions/openxr_composition_layer_extension.cpp
index dc30b95b27..2d29b8a82c 100644
--- a/modules/openxr/extensions/openxr_composition_layer_extension.cpp
+++ b/modules/openxr/extensions/openxr_composition_layer_extension.cpp
@@ -341,7 +341,7 @@ XrCompositionLayerBaseHeader *OpenXRViewportCompositionLayerProvider::get_compos
}
XrSwapchainSubImage subimage = {
- 0, // swapchain
+ 0, // swapchain // NOLINT(modernize-use-nullptr) - 32-bit uses non-pointer uint64
{ { 0, 0 }, { 0, 0 } }, // imageRect
0, // imageArrayIndex
};
diff --git a/modules/openxr/extensions/openxr_debug_utils_extension.cpp b/modules/openxr/extensions/openxr_debug_utils_extension.cpp
index 10dbe629f7..cb7f72d02d 100644
--- a/modules/openxr/extensions/openxr_debug_utils_extension.cpp
+++ b/modules/openxr/extensions/openxr_debug_utils_extension.cpp
@@ -173,7 +173,7 @@ void OpenXRDebugUtilsExtension::begin_debug_label_region(const char *p_label_nam
const XrDebugUtilsLabelEXT session_active_region_label = {
XR_TYPE_DEBUG_UTILS_LABEL_EXT, // type
- NULL, // next
+ nullptr, // next
p_label_name, // labelName
};
@@ -199,7 +199,7 @@ void OpenXRDebugUtilsExtension::insert_debug_label(const char *p_label_name) {
const XrDebugUtilsLabelEXT session_active_region_label = {
XR_TYPE_DEBUG_UTILS_LABEL_EXT, // type
- NULL, // next
+ nullptr, // next
p_label_name, // labelName
};
diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
index 07ca476421..fb8d1b9d69 100644
--- a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
+++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
@@ -51,6 +51,8 @@ void OpenXRExtensionWrapperExtension::_bind_methods() {
GDVIRTUAL_BIND(_on_process);
GDVIRTUAL_BIND(_on_pre_render);
GDVIRTUAL_BIND(_on_main_swapchains_created);
+ GDVIRTUAL_BIND(_on_pre_draw_viewport, "viewport");
+ GDVIRTUAL_BIND(_on_post_draw_viewport, "viewport");
GDVIRTUAL_BIND(_on_session_destroyed);
GDVIRTUAL_BIND(_on_state_idle);
GDVIRTUAL_BIND(_on_state_ready);
@@ -208,6 +210,14 @@ void OpenXRExtensionWrapperExtension::on_session_destroyed() {
GDVIRTUAL_CALL(_on_session_destroyed);
}
+void OpenXRExtensionWrapperExtension::on_pre_draw_viewport(RID p_render_target) {
+ GDVIRTUAL_CALL(_on_pre_draw_viewport, p_render_target);
+}
+
+void OpenXRExtensionWrapperExtension::on_post_draw_viewport(RID p_render_target) {
+ GDVIRTUAL_CALL(_on_post_draw_viewport, p_render_target);
+}
+
void OpenXRExtensionWrapperExtension::on_state_idle() {
GDVIRTUAL_CALL(_on_state_idle);
}
@@ -298,8 +308,7 @@ void OpenXRExtensionWrapperExtension::register_extension_wrapper() {
OpenXRAPI::register_extension_wrapper(this);
}
-OpenXRExtensionWrapperExtension::OpenXRExtensionWrapperExtension() :
- Object(), OpenXRExtensionWrapper() {
+OpenXRExtensionWrapperExtension::OpenXRExtensionWrapperExtension() {
openxr_api.instantiate();
}
diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.h b/modules/openxr/extensions/openxr_extension_wrapper_extension.h
index 5cdf288c93..8fc6511277 100644
--- a/modules/openxr/extensions/openxr_extension_wrapper_extension.h
+++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.h
@@ -88,6 +88,8 @@ public:
virtual void on_pre_render() override;
virtual void on_main_swapchains_created() override;
virtual void on_session_destroyed() override;
+ virtual void on_pre_draw_viewport(RID p_render_target) override;
+ virtual void on_post_draw_viewport(RID p_render_target) override;
GDVIRTUAL0(_on_register_metadata);
GDVIRTUAL0(_on_before_instance_created);
@@ -98,6 +100,8 @@ public:
GDVIRTUAL0(_on_pre_render);
GDVIRTUAL0(_on_main_swapchains_created);
GDVIRTUAL0(_on_session_destroyed);
+ GDVIRTUAL1(_on_pre_draw_viewport, RID);
+ GDVIRTUAL1(_on_post_draw_viewport, RID);
virtual void on_state_idle() override;
virtual void on_state_ready() override;
diff --git a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp
index f75da49d87..4fcb3f7b75 100644
--- a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp
+++ b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp
@@ -193,7 +193,7 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex
// spec says to use proper values but runtimes don't care
graphics_binding_gl.visualid = 0;
- graphics_binding_gl.glxFBConfig = 0;
+ graphics_binding_gl.glxFBConfig = nullptr;
#endif
#endif
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index 715d24cfd9..a6fd727290 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -2752,8 +2752,9 @@ void OpenXRAPI::parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_l
}
bool OpenXRAPI::xr_result(XrResult result, const char *format, Array args) const {
- if (XR_SUCCEEDED(result))
+ if (XR_SUCCEEDED(result)) {
return true;
+ }
char resultString[XR_MAX_RESULT_STRING_SIZE];
xrResultToString(instance, result, resultString);
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 1c6e62650a..8d557aae2a 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -5264,42 +5264,44 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
// Find usable fonts, if fonts from the last glyph do not have required chars.
RID dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
- if (!_font_has_char(dot_gl_font_rid, sd->el_char)) {
- const Array &fonts = spans[spans.size() - 1].fonts;
- for (int i = 0; i < fonts.size(); i++) {
- if (_font_has_char(fonts[i], sd->el_char)) {
- dot_gl_font_rid = fonts[i];
- found_el_char = true;
- break;
- }
- }
- if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
- const char32_t u32str[] = { sd->el_char, 0 };
- RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str);
- if (rid.is_valid()) {
- dot_gl_font_rid = rid;
- found_el_char = true;
- }
- }
- } else {
- found_el_char = true;
- }
- if (!found_el_char) {
- bool found_dot_char = false;
- dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
- if (!_font_has_char(dot_gl_font_rid, '.')) {
+ if (add_ellipsis || enforce_ellipsis) {
+ if (!_font_has_char(dot_gl_font_rid, sd->el_char)) {
const Array &fonts = spans[spans.size() - 1].fonts;
for (int i = 0; i < fonts.size(); i++) {
- if (_font_has_char(fonts[i], '.')) {
+ if (_font_has_char(fonts[i], sd->el_char)) {
dot_gl_font_rid = fonts[i];
- found_dot_char = true;
+ found_el_char = true;
break;
}
}
- if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
- RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, ".");
+ if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
+ const char32_t u32str[] = { sd->el_char, 0 };
+ RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str);
if (rid.is_valid()) {
dot_gl_font_rid = rid;
+ found_el_char = true;
+ }
+ }
+ } else {
+ found_el_char = true;
+ }
+ if (!found_el_char) {
+ bool found_dot_char = false;
+ dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
+ if (!_font_has_char(dot_gl_font_rid, '.')) {
+ const Array &fonts = spans[spans.size() - 1].fonts;
+ for (int i = 0; i < fonts.size(); i++) {
+ if (_font_has_char(fonts[i], '.')) {
+ dot_gl_font_rid = fonts[i];
+ found_dot_char = true;
+ break;
+ }
+ }
+ if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
+ RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, ".");
+ if (rid.is_valid()) {
+ dot_gl_font_rid = rid;
+ }
}
}
}
@@ -5315,8 +5317,8 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
}
}
- int32_t dot_gl_idx = dot_gl_font_rid.is_valid() ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, (found_el_char ? sd->el_char : '.'), 0) : -1;
- Vector2 dot_adv = dot_gl_font_rid.is_valid() ? _font_get_glyph_advance(dot_gl_font_rid, last_gl_font_size, dot_gl_idx) : Vector2();
+ int32_t dot_gl_idx = ((add_ellipsis || enforce_ellipsis) && dot_gl_font_rid.is_valid()) ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, (found_el_char ? sd->el_char : '.'), 0) : -1;
+ Vector2 dot_adv = ((add_ellipsis || enforce_ellipsis) && dot_gl_font_rid.is_valid()) ? _font_get_glyph_advance(dot_gl_font_rid, last_gl_font_size, dot_gl_idx) : Vector2();
int32_t whitespace_gl_idx = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_index(whitespace_gl_font_rid, last_gl_font_size, ' ', 0) : -1;
Vector2 whitespace_adv = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_advance(whitespace_gl_font_rid, last_gl_font_size, whitespace_gl_idx) : Vector2();
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index d30b2aae19..ae636a3151 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -4077,42 +4077,44 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
// Find usable fonts, if fonts from the last glyph do not have required chars.
RID dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
- if (!_font_has_char(dot_gl_font_rid, sd->el_char)) {
- const Array &fonts = spans[spans.size() - 1].fonts;
- for (int i = 0; i < fonts.size(); i++) {
- if (_font_has_char(fonts[i], sd->el_char)) {
- dot_gl_font_rid = fonts[i];
- found_el_char = true;
- break;
- }
- }
- if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
- const char32_t u32str[] = { sd->el_char, 0 };
- RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str);
- if (rid.is_valid()) {
- dot_gl_font_rid = rid;
- found_el_char = true;
- }
- }
- } else {
- found_el_char = true;
- }
- if (!found_el_char) {
- bool found_dot_char = false;
- dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
- if (!_font_has_char(dot_gl_font_rid, '.')) {
+ if (add_ellipsis || enforce_ellipsis) {
+ if (!_font_has_char(dot_gl_font_rid, sd->el_char)) {
const Array &fonts = spans[spans.size() - 1].fonts;
for (int i = 0; i < fonts.size(); i++) {
- if (_font_has_char(fonts[i], '.')) {
+ if (_font_has_char(fonts[i], sd->el_char)) {
dot_gl_font_rid = fonts[i];
- found_dot_char = true;
+ found_el_char = true;
break;
}
}
- if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
- RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, ".");
+ if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
+ const char32_t u32str[] = { sd->el_char, 0 };
+ RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str);
if (rid.is_valid()) {
dot_gl_font_rid = rid;
+ found_el_char = true;
+ }
+ }
+ } else {
+ found_el_char = true;
+ }
+ if (!found_el_char) {
+ bool found_dot_char = false;
+ dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
+ if (!_font_has_char(dot_gl_font_rid, '.')) {
+ const Array &fonts = spans[spans.size() - 1].fonts;
+ for (int i = 0; i < fonts.size(); i++) {
+ if (_font_has_char(fonts[i], '.')) {
+ dot_gl_font_rid = fonts[i];
+ found_dot_char = true;
+ break;
+ }
+ }
+ if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
+ RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, ".");
+ if (rid.is_valid()) {
+ dot_gl_font_rid = rid;
+ }
}
}
}
@@ -4128,8 +4130,8 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
}
}
- int32_t dot_gl_idx = dot_gl_font_rid.is_valid() ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, (found_el_char ? sd->el_char : '.'), 0) : -1;
- Vector2 dot_adv = dot_gl_font_rid.is_valid() ? _font_get_glyph_advance(dot_gl_font_rid, last_gl_font_size, dot_gl_idx) : Vector2();
+ int32_t dot_gl_idx = ((add_ellipsis || enforce_ellipsis) && dot_gl_font_rid.is_valid()) ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, (found_el_char ? sd->el_char : '.'), 0) : -1;
+ Vector2 dot_adv = ((add_ellipsis || enforce_ellipsis) && dot_gl_font_rid.is_valid()) ? _font_get_glyph_advance(dot_gl_font_rid, last_gl_font_size, dot_gl_idx) : Vector2();
int32_t whitespace_gl_idx = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_index(whitespace_gl_font_rid, last_gl_font_size, ' ', 0) : -1;
Vector2 whitespace_adv = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_advance(whitespace_gl_font_rid, last_gl_font_size, whitespace_gl_idx) : Vector2();
diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp
index f7c6ea899f..8b2c58acd5 100644
--- a/modules/theora/video_stream_theora.cpp
+++ b/modules/theora/video_stream_theora.cpp
@@ -115,7 +115,7 @@ void VideoStreamPlaybackTheora::video_write() {
format = Image::FORMAT_RGBA8;
}
- Ref<Image> img = memnew(Image(size.x, size.y, 0, Image::FORMAT_RGBA8, frame_data)); //zero copy image creation
+ Ref<Image> img = memnew(Image(size.x, size.y, false, Image::FORMAT_RGBA8, frame_data)); //zero copy image creation
texture->update(img); //zero copy send to rendering server
diff --git a/modules/vorbis/resource_importer_ogg_vorbis.h b/modules/vorbis/resource_importer_ogg_vorbis.h
index 59ae3378a0..f378b80694 100644
--- a/modules/vorbis/resource_importer_ogg_vorbis.h
+++ b/modules/vorbis/resource_importer_ogg_vorbis.h
@@ -65,6 +65,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterOggVorbis();
};
diff --git a/modules/webp/webp_common.cpp b/modules/webp/webp_common.cpp
index 3a2ac5a90e..0284eec574 100644
--- a/modules/webp/webp_common.cpp
+++ b/modules/webp/webp_common.cpp
@@ -149,7 +149,7 @@ Ref<Image> _webp_unpack(const Vector<uint8_t> &p_buffer) {
ERR_FAIL_COND_V_MSG(errdec, Ref<Image>(), "Failed decoding WebP image.");
- Ref<Image> img = memnew(Image(features.width, features.height, 0, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image));
+ Ref<Image> img = memnew(Image(features.width, features.height, false, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image));
return img;
}
diff --git a/platform/SCsub b/platform/SCsub
index 7c9d07f6ef..248b4b88dd 100644
--- a/platform/SCsub
+++ b/platform/SCsub
@@ -60,7 +60,7 @@ register_platform_apis = env.CommandNoCache(
)
env.add_source_files(env.platform_sources, register_platform_apis)
for platform in env.platform_apis:
- env.add_source_files(env.platform_sources, f"{platform}/api/api.cpp")
+ env.add_source_files(env.platform_sources, f"{platform}/api/*.cpp")
lib = env.add_library("platform", env.platform_sources)
env.Prepend(LIBS=[lib])
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index 3c8b44a33f..38f6931c8a 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -203,6 +203,12 @@ void DisplayServerAndroid::emit_file_picker_callback(bool p_ok, const Vector<Str
}
}
+Color DisplayServerAndroid::get_accent_color() const {
+ GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
+ ERR_FAIL_NULL_V(godot_java, Color(0, 0, 0, 0));
+ return godot_java->get_accent_color();
+}
+
TypedArray<Rect2> DisplayServerAndroid::get_display_cutouts() const {
GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
ERR_FAIL_NULL_V(godot_io_java, Array());
@@ -243,14 +249,6 @@ DisplayServer::ScreenOrientation DisplayServerAndroid::screen_get_orientation(in
return (ScreenOrientation)orientation;
}
-int DisplayServerAndroid::screen_get_internal_current_rotation(int p_screen) const {
- GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
- ERR_FAIL_NULL_V(godot_io_java, 0);
-
- const int rotation = godot_io_java->get_internal_current_screen_rotation();
- return rotation;
-}
-
int DisplayServerAndroid::get_screen_count() const {
return 1;
}
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index 30f0f3dbed..1744ad3069 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -125,6 +125,8 @@ public:
virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, const FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override;
void emit_file_picker_callback(bool p_ok, const Vector<String> &p_selected_paths);
+ virtual Color get_accent_color() const override;
+
virtual TypedArray<Rect2> get_display_cutouts() const override;
virtual Rect2i get_display_safe_area() const override;
@@ -133,7 +135,6 @@ public:
virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW) override;
virtual ScreenOrientation screen_get_orientation(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
- virtual int screen_get_internal_current_rotation(int p_screen) const override;
virtual int get_screen_count() const override;
virtual int get_primary_screen() const override;
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index 93ac498ab3..d2b64c74a4 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -49,7 +49,9 @@ void register_android_exporter() {
EDITOR_DEF_BASIC("export/android/debug_keystore_pass", DEFAULT_ANDROID_KEYSTORE_DEBUG_PASSWORD);
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore_pass", PROPERTY_HINT_PASSWORD));
-#ifndef ANDROID_ENABLED
+#ifdef ANDROID_ENABLED
+ EDITOR_DEF_BASIC("export/android/install_exported_apk", true);
+#else
EDITOR_DEF_BASIC("export/android/java_sdk_path", OS::get_singleton()->get_environment("JAVA_HOME"));
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/java_sdk_path", PROPERTY_HINT_GLOBAL_DIR));
EDITOR_DEF_BASIC("export/android/android_sdk_path", OS::get_singleton()->get_environment("ANDROID_HOME"));
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 41f460ca8f..aea09583b7 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -1693,7 +1693,7 @@ void EditorExportPlatformAndroid::load_icon_refs(const Ref<EditorExportPreset> &
path = static_cast<String>(p_preset->get(launcher_adaptive_icon_monochrome_option)).strip_edges();
if (!path.is_empty()) {
print_verbose("Loading adaptive monochrome icon from " + path);
- ImageLoader::load_image(path, background);
+ ImageLoader::load_image(path, monochrome);
}
}
@@ -1778,6 +1778,12 @@ String EditorExportPlatformAndroid::get_export_option_warning(const EditorExport
if (!is_package_name_valid(pn, &pn_err)) {
return TTR("Invalid package name:") + " " + pn_err;
}
+ } else if (p_name == launcher_adaptive_icon_monochrome_option) {
+ String monochrome_icon_path = p_preset->get(launcher_adaptive_icon_monochrome_option);
+
+ if (monochrome_icon_path.is_empty()) {
+ return TTR("No adaptive monochrome icon specified; default Godot monochrome icon will be used.");
+ }
} else if (p_name == "gradle_build/use_gradle_build") {
bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build");
String enabled_plugins_names = _get_plugins_names(Ref<EditorExportPreset>(p_preset));
@@ -2887,6 +2893,14 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_pre
#endif
print_verbose("Successfully completed signing build.");
+
+#ifdef ANDROID_ENABLED
+ bool prompt_apk_install = EDITOR_GET("export/android/install_exported_apk");
+ if (prompt_apk_install) {
+ OS_Android::get_singleton()->shell_open(apk_path);
+ }
+#endif
+
return OK;
}
diff --git a/platform/android/java/editor/src/main/AndroidManifest.xml b/platform/android/java/editor/src/main/AndroidManifest.xml
index a875745860..c1eb03b31f 100644
--- a/platform/android/java/editor/src/main/AndroidManifest.xml
+++ b/platform/android/java/editor/src/main/AndroidManifest.xml
@@ -25,6 +25,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.VIBRATE" />
+ <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:allowBackup="false"
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt
index 7b6d1f6bd1..6aa2ba7195 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt
@@ -390,7 +390,7 @@ abstract class BaseGodotEditor : GodotActivity() {
* If the launch policy is [LaunchPolicy.AUTO], resolve it into a specific policy based on the
* editor setting or device and screen metrics.
*
- * If the launch policy is [LaunchPolicy.PIP] but PIP is not supported, fallback to the default
+ * If the launch policy is [LaunchPolicy.SAME_AND_LAUNCH_IN_PIP_MODE] but PIP is not supported, fallback to the default
* launch policy.
*/
private fun resolveLaunchPolicyIfNeeded(policy: LaunchPolicy): LaunchPolicy {
@@ -453,9 +453,9 @@ abstract class BaseGodotEditor : GodotActivity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Check if we got the MANAGE_EXTERNAL_STORAGE permission
- if (requestCode == PermissionsUtil.REQUEST_MANAGE_EXTERNAL_STORAGE_REQ_CODE) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- if (!Environment.isExternalStorageManager()) {
+ when (requestCode) {
+ PermissionsUtil.REQUEST_MANAGE_EXTERNAL_STORAGE_REQ_CODE -> {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
Toast.makeText(
this,
R.string.denied_storage_permission_error_msg,
@@ -463,6 +463,16 @@ abstract class BaseGodotEditor : GodotActivity() {
).show()
}
}
+
+ PermissionsUtil.REQUEST_INSTALL_PACKAGES_REQ_CODE -> {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !packageManager.canRequestPackageInstalls()) {
+ Toast.makeText(
+ this,
+ R.string.denied_install_packages_permission_error_msg,
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ }
}
}
@@ -514,7 +524,7 @@ abstract class BaseGodotEditor : GodotActivity() {
override fun supportsFeature(featureTag: String): Boolean {
if (featureTag == "xr_editor") {
- return isNativeXRDevice();
+ return isNativeXRDevice()
}
if (featureTag == "horizonos") {
diff --git a/platform/android/java/editor/src/main/res/values/strings.xml b/platform/android/java/editor/src/main/res/values/strings.xml
index 0ad54ac3a1..a25b6c0a2d 100644
--- a/platform/android/java/editor/src/main/res/values/strings.xml
+++ b/platform/android/java/editor/src/main/res/values/strings.xml
@@ -2,5 +2,6 @@
<resources>
<string name="godot_game_activity_name">Godot Play window</string>
<string name="denied_storage_permission_error_msg">Missing storage access permission!</string>
+ <string name="denied_install_packages_permission_error_msg">Missing install packages permission!</string>
<string name="pip_button_description">Button used to toggle picture-in-picture mode for the Play window</string>
</resources>
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
index 898e3e04be..3ad8e6bc9e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -925,6 +925,12 @@ class Godot(private val context: Context) {
}
}
+ @Keep
+ private fun getAccentColor(): Int {
+ val value = TypedValue()
+ context.theme.resolveAttribute(android.R.attr.colorAccent, value, true)
+ return value.data
+ }
/**
* Destroys the Godot Engine and kill the process it's running in.
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
index 5543745444..79751dd58f 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
@@ -296,28 +296,6 @@ public class GodotIO {
}
}
- /**
- This function is used by DisplayServer::screen_get_internal_current_rotation (C++)
- and is used to implement a performance optimization in devices that do not offer
- a HW rotator.
- @return
- Rotation in degrees, in multiples of 90°
- */
- public int getInternalCurrentScreenRotation() {
- int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
-
- switch (rotation) {
- case Surface.ROTATION_90:
- return 90;
- case Surface.ROTATION_180:
- return 180;
- case Surface.ROTATION_270:
- return 270;
- default:
- return 0;
- }
- }
-
public void setEdit(GodotEditText _edit) {
edit = _edit;
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
index c44a6dd472..885873e46d 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
@@ -49,7 +49,6 @@ import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -66,6 +65,7 @@ public final class PermissionsUtil {
public static final int REQUEST_ALL_PERMISSION_REQ_CODE = 1001;
public static final int REQUEST_SINGLE_PERMISSION_REQ_CODE = 1002;
public static final int REQUEST_MANAGE_EXTERNAL_STORAGE_REQ_CODE = 2002;
+ public static final int REQUEST_INSTALL_PACKAGES_REQ_CODE = 3002;
private PermissionsUtil() {
}
@@ -105,6 +105,16 @@ public final class PermissionsUtil {
activity.startActivityForResult(intent, REQUEST_MANAGE_EXTERNAL_STORAGE_REQ_CODE);
}
}
+ } else if (permission.equals(Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !activity.getPackageManager().canRequestPackageInstalls()) {
+ try {
+ Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
+ intent.setData(Uri.parse(String.format("package:%s", activity.getPackageName())));
+ activity.startActivityForResult(intent, REQUEST_INSTALL_PACKAGES_REQ_CODE);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to request permission " + Manifest.permission.REQUEST_INSTALL_PACKAGES);
+ }
+ }
} else {
PermissionInfo permissionInfo = getPermissionInfo(activity, permission);
int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel;
@@ -215,7 +225,7 @@ public final class PermissionsUtil {
try {
manifestPermissions = getManifestPermissions(activity);
} catch (PackageManager.NameNotFoundException e) {
- e.printStackTrace();
+ Log.e(TAG, "Unable to retrieve manifest permissions", e);
return false;
}
@@ -242,7 +252,7 @@ public final class PermissionsUtil {
try {
manifestPermissions = getManifestPermissions(context);
} catch (PackageManager.NameNotFoundException e) {
- e.printStackTrace();
+ Log.e(TAG, "Unable to retrieve manifest permissions", e);
return new String[0];
}
if (manifestPermissions.isEmpty()) {
diff --git a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
index a7d2774db5..a5ecafeb09 100644
--- a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
+++ b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
@@ -21,4 +21,4 @@ target_include_directories(${PROJECT_NAME}
${ANDROID_ROOT_DIR}
${OPENXR_INCLUDE_DIR})
-add_definitions(-DUNIX_ENABLED -DVULKAN_ENABLED -DANDROID_ENABLED -DGLES3_ENABLED -DTOOLS_ENABLED)
+add_definitions(-DUNIX_ENABLED -DVULKAN_ENABLED -DANDROID_ENABLED -DGLES3_ENABLED -DTOOLS_ENABLED -DDEBUG_ENABLED)
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index e58ef50a73..623db39985 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -66,7 +66,6 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
_has_hardware_keyboard = p_env->GetMethodID(cls, "hasHardwareKeyboard", "()Z");
_set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V");
_get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I");
- _get_internal_current_screen_rotation = p_env->GetMethodID(cls, "getInternalCurrentScreenRotation", "()I");
_get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(IZ)Ljava/lang/String;");
}
}
@@ -268,16 +267,6 @@ int GodotIOJavaWrapper::get_screen_orientation() {
}
}
-int GodotIOJavaWrapper::get_internal_current_screen_rotation() {
- if (_get_internal_current_screen_rotation) {
- JNIEnv *env = get_jni_env();
- ERR_FAIL_NULL_V(env, 0);
- return env->CallIntMethod(godot_io_instance, _get_internal_current_screen_rotation);
- } else {
- return 0;
- }
-}
-
String GodotIOJavaWrapper::get_system_dir(int p_dir, bool p_shared_storage) {
if (_get_system_dir) {
JNIEnv *env = get_jni_env();
diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h
index 903bdce4be..0a372641cb 100644
--- a/platform/android/java_godot_io_wrapper.h
+++ b/platform/android/java_godot_io_wrapper.h
@@ -61,7 +61,6 @@ private:
jmethodID _has_hardware_keyboard = 0;
jmethodID _set_screen_orientation = 0;
jmethodID _get_screen_orientation = 0;
- jmethodID _get_internal_current_screen_rotation = 0;
jmethodID _get_system_dir = 0;
public:
@@ -89,7 +88,6 @@ public:
void set_vk_height(int p_height);
void set_screen_orientation(int p_orient);
int get_screen_orientation();
- int get_internal_current_screen_rotation();
String get_system_dir(int p_dir, bool p_shared_storage);
};
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 2811182e29..5ecd789d43 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -64,6 +64,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_alert = p_env->GetMethodID(godot_class, "alert", "(Ljava/lang/String;Ljava/lang/String;)V");
_is_dark_mode_supported = p_env->GetMethodID(godot_class, "isDarkModeSupported", "()Z");
_is_dark_mode = p_env->GetMethodID(godot_class, "isDarkMode", "()Z");
+ _get_accent_color = p_env->GetMethodID(godot_class, "getAccentColor", "()I");
_get_clipboard = p_env->GetMethodID(godot_class, "getClipboard", "()Ljava/lang/String;");
_set_clipboard = p_env->GetMethodID(godot_class, "setClipboard", "(Ljava/lang/String;)V");
_has_clipboard = p_env->GetMethodID(godot_class, "hasClipboard", "()Z");
@@ -214,6 +215,23 @@ bool GodotJavaWrapper::is_dark_mode() {
}
}
+Color GodotJavaWrapper::get_accent_color() {
+ if (_get_accent_color) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, Color(0, 0, 0, 0));
+ int accent_color = env->CallIntMethod(godot_instance, _get_accent_color);
+
+ // Convert ARGB to RGBA.
+ int alpha = (accent_color >> 24) & 0xFF;
+ int red = (accent_color >> 16) & 0xFF;
+ int green = (accent_color >> 8) & 0xFF;
+ int blue = accent_color & 0xFF;
+ return Color(red / 255.0f, green / 255.0f, blue / 255.0f, alpha / 255.0f);
+ } else {
+ return Color(0, 0, 0, 0);
+ }
+}
+
bool GodotJavaWrapper::has_get_clipboard() {
return _get_clipboard != nullptr;
}
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index 1a753c5c59..512779169a 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -34,6 +34,7 @@
#include "java_godot_view_wrapper.h"
#include "string_android.h"
+#include "core/math/color.h"
#include "core/templates/list.h"
#include <android/log.h>
@@ -55,6 +56,7 @@ private:
jmethodID _alert = nullptr;
jmethodID _is_dark_mode_supported = nullptr;
jmethodID _is_dark_mode = nullptr;
+ jmethodID _get_accent_color = nullptr;
jmethodID _get_clipboard = nullptr;
jmethodID _set_clipboard = nullptr;
jmethodID _has_clipboard = nullptr;
@@ -99,6 +101,7 @@ public:
void alert(const String &p_message, const String &p_title);
bool is_dark_mode_supported();
bool is_dark_mode();
+ Color get_accent_color();
bool has_get_clipboard();
String get_clipboard();
bool has_set_clipboard();
diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp
index 6355562feb..b309e8d8eb 100644
--- a/platform/linuxbsd/os_linuxbsd.cpp
+++ b/platform/linuxbsd/os_linuxbsd.cpp
@@ -771,11 +771,11 @@ Vector<String> OS_LinuxBSD::get_system_font_path_for_text(const String &p_font_n
FcLangSetAdd(lang_set, reinterpret_cast<const FcChar8 *>(p_locale.utf8().get_data()));
FcPatternAddLangSet(pattern, FC_LANG, lang_set);
- FcConfigSubstitute(0, pattern, FcMatchPattern);
+ FcConfigSubstitute(nullptr, pattern, FcMatchPattern);
FcDefaultSubstitute(pattern);
FcResult result;
- FcPattern *match = FcFontMatch(0, pattern, &result);
+ FcPattern *match = FcFontMatch(nullptr, pattern, &result);
if (match) {
char *file_name = nullptr;
if (FcPatternGetString(match, FC_FILE, 0, reinterpret_cast<FcChar8 **>(&file_name)) == FcResultMatch) {
@@ -816,11 +816,11 @@ String OS_LinuxBSD::get_system_font_path(const String &p_font_name, int p_weight
FcPatternAddInteger(pattern, FC_WIDTH, _stretch_to_fc(p_stretch));
FcPatternAddInteger(pattern, FC_SLANT, p_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN);
- FcConfigSubstitute(0, pattern, FcMatchPattern);
+ FcConfigSubstitute(nullptr, pattern, FcMatchPattern);
FcDefaultSubstitute(pattern);
FcResult result;
- FcPattern *match = FcFontMatch(0, pattern, &result);
+ FcPattern *match = FcFontMatch(nullptr, pattern, &result);
if (match) {
if (!allow_substitutes) {
char *family_name = nullptr;
diff --git a/platform/windows/SCsub b/platform/windows/SCsub
index 1d17e7b325..30df9df809 100644
--- a/platform/windows/SCsub
+++ b/platform/windows/SCsub
@@ -24,6 +24,7 @@ common_win = [
"gl_manager_windows_angle.cpp",
"wgl_detect_version.cpp",
"rendering_context_driver_vulkan_windows.cpp",
+ "drop_target_windows.cpp",
]
if env.msvc:
diff --git a/platform/windows/crash_handler_windows_signal.cpp b/platform/windows/crash_handler_windows_signal.cpp
index e11a60bdc7..c3a0d08ad6 100644
--- a/platform/windows/crash_handler_windows_signal.cpp
+++ b/platform/windows/crash_handler_windows_signal.cpp
@@ -159,7 +159,7 @@ extern void CrashHandlerException(int signal) {
// Load process and image info to determine ASLR addresses offset.
MODULEINFO mi;
- GetModuleInformation(GetCurrentProcess(), GetModuleHandle(NULL), &mi, sizeof(mi));
+ GetModuleInformation(GetCurrentProcess(), GetModuleHandle(nullptr), &mi, sizeof(mi));
int64_t image_mem_base = reinterpret_cast<int64_t>(mi.lpBaseOfDll);
int64_t image_file_base = get_image_base(_execpath);
data.offset = image_mem_base - image_file_base;
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index ddcd29adc9..e1109db24f 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -68,23 +68,23 @@ def can_build():
def get_mingw_bin_prefix(prefix, arch):
- if not prefix:
- mingw_bin_prefix = ""
- elif prefix[-1] != "/":
- mingw_bin_prefix = prefix + "/bin/"
- else:
- mingw_bin_prefix = prefix + "bin/"
+ bin_prefix = (os.path.normpath(os.path.join(prefix, "bin")) + os.sep) if prefix else ""
+ ARCH_PREFIXES = {
+ "x86_64": "x86_64-w64-mingw32-",
+ "x86_32": "i686-w64-mingw32-",
+ "arm32": "armv7-w64-mingw32-",
+ "arm64": "aarch64-w64-mingw32-",
+ }
+ arch_prefix = ARCH_PREFIXES[arch] if arch else ""
+ return bin_prefix + arch_prefix
- if arch == "x86_64":
- mingw_bin_prefix += "x86_64-w64-mingw32-"
- elif arch == "x86_32":
- mingw_bin_prefix += "i686-w64-mingw32-"
- elif arch == "arm32":
- mingw_bin_prefix += "armv7-w64-mingw32-"
- elif arch == "arm64":
- mingw_bin_prefix += "aarch64-w64-mingw32-"
- return mingw_bin_prefix
+def get_detected(env: "SConsEnvironment", tool: str) -> str:
+ checks = [
+ get_mingw_bin_prefix(env["mingw_prefix"], env["arch"]) + tool,
+ get_mingw_bin_prefix(env["mingw_prefix"], "") + tool,
+ ]
+ return str(env.Detect(checks))
def detect_build_env_arch():
@@ -245,41 +245,6 @@ def get_flags():
}
-def build_res_file(target, source, env: "SConsEnvironment"):
- arch_aliases = {
- "x86_32": "pe-i386",
- "x86_64": "pe-x86-64",
- "arm32": "armv7-w64-mingw32",
- "arm64": "aarch64-w64-mingw32",
- }
- cmdbase = "windres --include-dir . --target=" + arch_aliases[env["arch"]]
-
- mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"])
-
- for x in range(len(source)):
- ok = True
- # Try prefixed executable (MinGW on Linux).
- cmd = mingw_bin_prefix + cmdbase + " -i " + str(source[x]) + " -o " + str(target[x])
- try:
- out = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate()
- if len(out[1]):
- ok = False
- except Exception:
- ok = False
-
- # Try generic executable (MSYS2).
- if not ok:
- cmd = cmdbase + " -i " + str(source[x]) + " -o " + str(target[x])
- try:
- out = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate()
- if len(out[1]):
- return -1
- except Exception:
- return -1
-
- return 0
-
-
def setup_msvc_manual(env: "SConsEnvironment"):
"""Running from VCVARS environment"""
@@ -361,6 +326,10 @@ def setup_mingw(env: "SConsEnvironment"):
print_error("No valid compilers found, use MINGW_PREFIX environment variable to set MinGW path.")
sys.exit(255)
+ env.Tool("mingw")
+ env.AppendUnique(CCFLAGS=env.get("ccflags", "").split())
+ env.AppendUnique(RCFLAGS=env.get("rcflags", "").split())
+
print("Using MinGW, arch %s" % (env["arch"]))
@@ -706,6 +675,13 @@ def configure_mingw(env: "SConsEnvironment"):
# https://www.scons.org/wiki/LongCmdLinesOnWin32
env.use_windows_spawn_fix()
+ # HACK: For some reason, Windows-native shells have their MinGW tools
+ # frequently fail as a result of parsing path separators incorrectly.
+ # For some other reason, this issue is circumvented entirely if the
+ # `mingw_prefix` bin is prepended to PATH.
+ if os.sep == "\\":
+ env.PrependENVPath("PATH", os.path.join(env["mingw_prefix"], "bin"))
+
# In case the command line to AR is too long, use a response file.
env["ARCOM_ORIG"] = env["ARCOM"]
env["ARCOM"] = "${TEMPFILE('$ARCOM_ORIG', '$ARCOMSTR')}"
@@ -743,9 +719,6 @@ def configure_mingw(env: "SConsEnvironment"):
## Compiler configuration
- if os.name != "nt":
- env["PROGSUFFIX"] = env["PROGSUFFIX"] + ".exe" # for linux cross-compilation
-
if env["arch"] == "x86_32":
if env["use_static_cpp"]:
env.Append(LINKFLAGS=["-static"])
@@ -760,29 +733,31 @@ def configure_mingw(env: "SConsEnvironment"):
env.Append(CCFLAGS=["-ffp-contract=off"])
- mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"])
-
if env["use_llvm"]:
- env["CC"] = mingw_bin_prefix + "clang"
- env["CXX"] = mingw_bin_prefix + "clang++"
- if try_cmd("as --version", env["mingw_prefix"], env["arch"]):
- env["AS"] = mingw_bin_prefix + "as"
- env.Append(ASFLAGS=["-c"])
- if try_cmd("ar --version", env["mingw_prefix"], env["arch"]):
- env["AR"] = mingw_bin_prefix + "ar"
- if try_cmd("ranlib --version", env["mingw_prefix"], env["arch"]):
- env["RANLIB"] = mingw_bin_prefix + "ranlib"
+ env["CC"] = get_detected(env, "clang")
+ env["CXX"] = get_detected(env, "clang++")
+ env["AR"] = get_detected(env, "ar")
+ env["RANLIB"] = get_detected(env, "ranlib")
+ env.Append(ASFLAGS=["-c"])
env.extra_suffix = ".llvm" + env.extra_suffix
else:
- env["CC"] = mingw_bin_prefix + "gcc"
- env["CXX"] = mingw_bin_prefix + "g++"
- if try_cmd("as --version", env["mingw_prefix"], env["arch"]):
- env["AS"] = mingw_bin_prefix + "as"
- ar = "ar" if os.name == "nt" else "gcc-ar"
- if try_cmd(f"{ar} --version", env["mingw_prefix"], env["arch"]):
- env["AR"] = mingw_bin_prefix + ar
- if try_cmd("gcc-ranlib --version", env["mingw_prefix"], env["arch"]):
- env["RANLIB"] = mingw_bin_prefix + "gcc-ranlib"
+ env["CC"] = get_detected(env, "gcc")
+ env["CXX"] = get_detected(env, "g++")
+ env["AR"] = get_detected(env, "gcc-ar" if os.name != "nt" else "ar")
+ env["RANLIB"] = get_detected(env, "gcc-ranlib")
+
+ env["RC"] = get_detected(env, "windres")
+ ARCH_TARGETS = {
+ "x86_32": "pe-i386",
+ "x86_64": "pe-x86-64",
+ "arm32": "armv7-w64-mingw32",
+ "arm64": "aarch64-w64-mingw32",
+ }
+ env.AppendUnique(RCFLAGS=f"--target={ARCH_TARGETS[env['arch']]}")
+
+ env["AS"] = get_detected(env, "as")
+ env["OBJCOPY"] = get_detected(env, "objcopy")
+ env["STRIP"] = get_detected(env, "strip")
## LTO
@@ -924,9 +899,6 @@ def configure_mingw(env: "SConsEnvironment"):
env.Append(CPPDEFINES=["MINGW_ENABLED", ("MINGW_HAS_SECURE_API", 1)])
- # resrc
- env.Append(BUILDERS={"RES": env.Builder(action=build_res_file, suffix=".o", src_suffix=".rc")})
-
def configure(env: "SConsEnvironment"):
# Validate arch.
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 3bc32fc218..a6eab1bd29 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -30,6 +30,7 @@
#include "display_server_windows.h"
+#include "drop_target_windows.h"
#include "os_windows.h"
#include "wgl_detect_version.h"
@@ -735,7 +736,7 @@ Error DisplayServerWindows::_file_dialog_with_options_show(const String &p_title
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->hwnd_owner = nullptr;
fd->wrect = Rect2i(CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT);
}
fd->appid = appname;
@@ -1617,11 +1618,17 @@ void DisplayServerWindows::delete_sub_window(WindowID p_window) {
}
#endif
- if ((tablet_get_current_driver() == "wintab") && wintab_available && windows[p_window].wtctx) {
- wintab_WTClose(windows[p_window].wtctx);
- windows[p_window].wtctx = nullptr;
+ if ((tablet_get_current_driver() == "wintab") && wintab_available && wd.wtctx) {
+ wintab_WTClose(wd.wtctx);
+ wd.wtctx = nullptr;
+ }
+
+ if (wd.drop_target != nullptr) {
+ RevokeDragDrop(wd.hWnd);
+ wd.drop_target->Release();
}
- DestroyWindow(windows[p_window].hWnd);
+
+ DestroyWindow(wd.hWnd);
windows.erase(p_window);
if (last_focused_window == p_window) {
@@ -1731,7 +1738,14 @@ void DisplayServerWindows::window_set_drop_files_callback(const Callable &p_call
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
- windows[p_window].drop_files_callback = p_callable;
+ WindowData &window_data = windows[p_window];
+
+ window_data.drop_files_callback = p_callable;
+
+ if (window_data.drop_target == nullptr) {
+ window_data.drop_target = memnew(DropTargetWindows(&window_data));
+ ERR_FAIL_COND(RegisterDragDrop(window_data.hWnd, window_data.drop_target) != S_OK);
+ }
}
void DisplayServerWindows::window_set_title(const String &p_title, WindowID p_window) {
@@ -5320,32 +5334,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
}
}
} break;
- case WM_DROPFILES: {
- HDROP hDropInfo = (HDROP)wParam;
- const int buffsize = 4096;
- WCHAR buf[buffsize];
-
- int fcount = DragQueryFileW(hDropInfo, 0xFFFFFFFF, nullptr, 0);
-
- Vector<String> files;
-
- for (int i = 0; i < fcount; i++) {
- DragQueryFileW(hDropInfo, i, buf, buffsize);
- String file = String::utf16((const char16_t *)buf);
- files.push_back(file);
- }
-
- if (files.size() && windows[window_id].drop_files_callback.is_valid()) {
- Variant v_files = files;
- const Variant *v_args[1] = { &v_files };
- Variant ret;
- Callable::CallError ce;
- windows[window_id].drop_files_callback.callp((const Variant **)&v_args, 1, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute drop files callback: %s.", Variant::get_callable_error_text(windows[window_id].drop_files_callback, v_args, 1, ce)));
- }
- }
- } break;
default: {
if (user_proc) {
return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam);
@@ -5450,7 +5438,7 @@ void DisplayServerWindows::_process_key_events() {
k->set_physical_keycode(physical_keycode);
k->set_key_label(key_label);
k->set_unicode(fix_unicode(unicode));
- if (k->get_unicode() && ke.altgr) {
+ if (k->get_unicode() && ke.altgr && windows[ke.window_id].ime_active) {
k->set_alt_pressed(false);
k->set_ctrl_pressed(false);
}
@@ -5526,7 +5514,7 @@ void DisplayServerWindows::_process_key_events() {
}
k->set_unicode(fix_unicode(unicode));
}
- if (k->get_unicode() && ke.altgr) {
+ if (k->get_unicode() && ke.altgr && windows[ke.window_id].ime_active) {
k->set_alt_pressed(false);
k->set_ctrl_pressed(false);
}
@@ -6174,6 +6162,8 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
FreeLibrary(comctl32);
}
+ OleInitialize(nullptr);
+
memset(&wc, 0, sizeof(WNDCLASSEXW));
wc.cbSize = sizeof(WNDCLASSEXW);
wc.style = CS_OWNDC | CS_DBLCLKS;
@@ -6606,6 +6596,12 @@ DisplayServerWindows::~DisplayServerWindows() {
wintab_WTClose(windows[MAIN_WINDOW_ID].wtctx);
windows[MAIN_WINDOW_ID].wtctx = nullptr;
}
+
+ if (windows[MAIN_WINDOW_ID].drop_target != nullptr) {
+ RevokeDragDrop(windows[MAIN_WINDOW_ID].hWnd);
+ windows[MAIN_WINDOW_ID].drop_target->Release();
+ }
+
DestroyWindow(windows[MAIN_WINDOW_ID].hWnd);
}
@@ -6637,4 +6633,6 @@ DisplayServerWindows::~DisplayServerWindows() {
if (tts) {
memdelete(tts);
}
+
+ OleUninitialize();
}
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index fc72e05b1d..0462d3f8fa 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -357,9 +357,13 @@ typedef enum _SHC_PROCESS_DPI_AWARENESS {
SHC_PROCESS_PER_MONITOR_DPI_AWARE = 2,
} SHC_PROCESS_DPI_AWARENESS;
+class DropTargetWindows;
+
class DisplayServerWindows : public DisplayServer {
// No need to register with GDCLASS, it's platform-specific and nothing is added.
+ friend class DropTargetWindows;
+
_THREAD_SAFE_CLASS_
// UXTheme API
@@ -521,6 +525,9 @@ class DisplayServerWindows : public DisplayServer {
Callable input_text_callback;
Callable drop_files_callback;
+ // OLE API
+ DropTargetWindows *drop_target = nullptr;
+
WindowID transient_parent = INVALID_WINDOW_ID;
HashSet<WindowID> transient_children;
diff --git a/platform/windows/drop_target_windows.cpp b/platform/windows/drop_target_windows.cpp
new file mode 100644
index 0000000000..d04924a9cf
--- /dev/null
+++ b/platform/windows/drop_target_windows.cpp
@@ -0,0 +1,375 @@
+/**************************************************************************/
+/* drop_target_windows.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 "drop_target_windows.h"
+
+#include "core/io/dir_access.h"
+#include "core/math/random_pcg.h"
+#include "core/os/time.h"
+
+#include <fileapi.h>
+
+// Helpers
+
+static String create_temp_dir() {
+ Char16String buf;
+ int bufsize = GetTempPathW(0, nullptr) + 1;
+ buf.resize(bufsize);
+ if (GetTempPathW(bufsize, (LPWSTR)buf.ptrw()) == 0) {
+ return "";
+ }
+
+ String tmp_dir = String::utf16((const char16_t *)buf.ptr());
+ RandomPCG gen(Time::get_singleton()->get_ticks_usec());
+
+ const int attempts = 4;
+
+ for (int i = 0; i < attempts; ++i) {
+ uint32_t rnd = gen.rand();
+ String dirname = "godot_tmp_" + String::num_uint64(rnd);
+ String res_dir = tmp_dir.path_join(dirname);
+ Char16String res_dir16 = res_dir.utf16();
+
+ if (CreateDirectoryW((LPCWSTR)res_dir16.ptr(), nullptr)) {
+ return res_dir;
+ }
+ }
+
+ return "";
+}
+
+static bool remove_dir_recursive(const String &p_dir) {
+ Ref<DirAccess> dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ if (dir_access->change_dir(p_dir) != OK) {
+ return false;
+ }
+ return dir_access->erase_contents_recursive() == OK;
+}
+
+static bool stream2file(IStream *p_stream, FILEDESCRIPTORW *p_desc, const String &p_path) {
+ if (DirAccess::make_dir_recursive_absolute(p_path.get_base_dir()) != OK) {
+ return false;
+ }
+
+ Char16String path16 = p_path.utf16();
+ DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
+
+ if (p_desc->dwFlags & FD_ATTRIBUTES) {
+ dwFlagsAndAttributes = p_desc->dwFileAttributes;
+ }
+
+ HANDLE file = CreateFileW(
+ (LPCWSTR)path16.ptr(),
+ GENERIC_WRITE,
+ 0,
+ nullptr,
+ CREATE_NEW,
+ dwFlagsAndAttributes,
+ nullptr);
+
+ if (!file) {
+ return false;
+ }
+
+ const int bufsize = 4096;
+ char buf[bufsize];
+ ULONG nread = 0;
+ DWORD nwritten = 0;
+ HRESULT read_result = S_OK;
+ bool result = true;
+
+ while (true) {
+ read_result = p_stream->Read(buf, bufsize, &nread);
+ if (read_result != S_OK && read_result != S_FALSE) {
+ result = false;
+ goto cleanup;
+ }
+
+ if (!nread) {
+ break;
+ }
+
+ while (nread > 0) {
+ if (!WriteFile(file, buf, nread, &nwritten, nullptr) || !nwritten) {
+ result = false;
+ goto cleanup;
+ }
+ nread -= nwritten;
+ }
+ }
+
+cleanup:
+ CloseHandle(file);
+ return result;
+}
+
+// DropTargetWindows
+
+bool DropTargetWindows::is_valid_filedescriptor() {
+ return cf_filedescriptor != 0 && cf_filecontents != 0;
+}
+
+HRESULT DropTargetWindows::handle_hdrop_format(Vector<String> *p_files, IDataObject *pDataObj) {
+ FORMATETC fmt = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+ STGMEDIUM stg;
+ HRESULT res = S_OK;
+
+ if (pDataObj->GetData(&fmt, &stg) != S_OK) {
+ return E_UNEXPECTED;
+ }
+
+ HDROP hDropInfo = (HDROP)GlobalLock(stg.hGlobal);
+
+ Char16String buf;
+
+ if (hDropInfo == nullptr) {
+ ReleaseStgMedium(&stg);
+ return E_UNEXPECTED;
+ }
+
+ int fcount = DragQueryFileW(hDropInfo, 0xFFFFFFFF, nullptr, 0);
+
+ for (int i = 0; i < fcount; i++) {
+ int buffsize = DragQueryFileW(hDropInfo, i, nullptr, 0);
+ buf.resize(buffsize + 1);
+ if (DragQueryFileW(hDropInfo, i, (LPWSTR)buf.ptrw(), buffsize + 1) == 0) {
+ res = E_UNEXPECTED;
+ goto cleanup;
+ }
+ String file = String::utf16((const char16_t *)buf.ptr());
+ p_files->push_back(file);
+ }
+
+cleanup:
+ GlobalUnlock(stg.hGlobal);
+ ReleaseStgMedium(&stg);
+
+ return res;
+}
+
+HRESULT DropTargetWindows::handle_filedescriptor_format(Vector<String> *p_files, IDataObject *pDataObj) {
+ FORMATETC fmt = { cf_filedescriptor, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+ STGMEDIUM stg;
+ HRESULT res = S_OK;
+
+ if (pDataObj->GetData(&fmt, &stg) != S_OK) {
+ return E_UNEXPECTED;
+ }
+
+ FILEGROUPDESCRIPTORW *filegroup_desc = (FILEGROUPDESCRIPTORW *)GlobalLock(stg.hGlobal);
+
+ if (!filegroup_desc) {
+ ReleaseStgMedium(&stg);
+ return E_UNEXPECTED;
+ }
+
+ tmp_path = create_temp_dir();
+ Ref<DirAccess> dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ PackedStringArray copied;
+
+ if (dir_access->change_dir(tmp_path) != OK) {
+ res = E_UNEXPECTED;
+ goto cleanup;
+ }
+
+ for (int i = 0; i < (int)filegroup_desc->cItems; ++i) {
+ res = save_as_file(tmp_path, filegroup_desc->fgd + i, pDataObj, i);
+ if (res != S_OK) {
+ res = E_UNEXPECTED;
+ goto cleanup;
+ }
+ }
+
+ copied = dir_access->get_files();
+ for (const String &file : copied) {
+ p_files->push_back(tmp_path.path_join(file));
+ }
+
+ copied = dir_access->get_directories();
+ for (const String &dir : copied) {
+ p_files->push_back(tmp_path.path_join(dir));
+ }
+
+cleanup:
+ GlobalUnlock(filegroup_desc);
+ ReleaseStgMedium(&stg);
+ if (res != S_OK) {
+ remove_dir_recursive(tmp_path);
+ tmp_path.clear();
+ }
+ return res;
+}
+
+HRESULT DropTargetWindows::save_as_file(const String &p_out_dir, FILEDESCRIPTORW *p_file_desc, IDataObject *pDataObj, int p_file_idx) {
+ String relpath = String::utf16((const char16_t *)p_file_desc->cFileName);
+ String fullpath = p_out_dir.path_join(relpath);
+
+ if (p_file_desc->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ if (DirAccess::make_dir_recursive_absolute(fullpath) != OK) {
+ return E_UNEXPECTED;
+ }
+ return S_OK;
+ }
+
+ FORMATETC fmt = { cf_filecontents, nullptr, DVASPECT_CONTENT, p_file_idx, TYMED_ISTREAM };
+ STGMEDIUM stg;
+ HRESULT res = S_OK;
+
+ if (pDataObj->GetData(&fmt, &stg) != S_OK) {
+ return E_UNEXPECTED;
+ }
+
+ IStream *stream = stg.pstm;
+ if (stream == nullptr) {
+ res = E_UNEXPECTED;
+ goto cleanup;
+ }
+
+ if (!stream2file(stream, p_file_desc, fullpath)) {
+ res = E_UNEXPECTED;
+ goto cleanup;
+ }
+
+cleanup:
+ ReleaseStgMedium(&stg);
+ return res;
+}
+
+DropTargetWindows::DropTargetWindows(DisplayServerWindows::WindowData *p_window_data) :
+ ref_count(1), window_data(p_window_data) {
+ cf_filedescriptor = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
+ cf_filecontents = RegisterClipboardFormat(CFSTR_FILECONTENTS);
+}
+
+HRESULT STDMETHODCALLTYPE DropTargetWindows::QueryInterface(REFIID riid, void **ppvObject) {
+ if (riid == IID_IUnknown || riid == IID_IDropTarget) {
+ *ppvObject = static_cast<IDropTarget *>(this);
+ AddRef();
+ return S_OK;
+ }
+ *ppvObject = nullptr;
+ return E_NOINTERFACE;
+}
+
+ULONG STDMETHODCALLTYPE DropTargetWindows::AddRef() {
+ return InterlockedIncrement(&ref_count);
+}
+
+ULONG STDMETHODCALLTYPE DropTargetWindows::Release() {
+ ULONG count = InterlockedDecrement(&ref_count);
+ if (count == 0) {
+ memfree(this);
+ }
+ return count;
+}
+
+HRESULT STDMETHODCALLTYPE DropTargetWindows::DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
+ (void)grfKeyState;
+ (void)pt;
+
+ FORMATETC hdrop_fmt = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+ FORMATETC filedesc_fmt = { cf_filedescriptor, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+
+ if (!window_data->drop_files_callback.is_valid()) {
+ *pdwEffect = DROPEFFECT_NONE;
+ } else if (pDataObj->QueryGetData(&hdrop_fmt) == S_OK) {
+ *pdwEffect = DROPEFFECT_COPY;
+ } else if (is_valid_filedescriptor() && pDataObj->QueryGetData(&filedesc_fmt) == S_OK) {
+ *pdwEffect = DROPEFFECT_COPY;
+ } else {
+ *pdwEffect = DROPEFFECT_NONE;
+ }
+
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE DropTargetWindows::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
+ (void)grfKeyState;
+ (void)pt;
+
+ *pdwEffect = DROPEFFECT_COPY;
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE DropTargetWindows::DragLeave() {
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE DropTargetWindows::Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
+ (void)grfKeyState;
+ (void)pt;
+
+ *pdwEffect = DROPEFFECT_NONE;
+
+ if (!window_data->drop_files_callback.is_valid()) {
+ return S_OK;
+ }
+
+ FORMATETC hdrop_fmt = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+ FORMATETC filedesc_fmt = { cf_filedescriptor, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+ Vector<String> files;
+
+ if (pDataObj->QueryGetData(&hdrop_fmt) == S_OK) {
+ HRESULT res = handle_hdrop_format(&files, pDataObj);
+ if (res != S_OK) {
+ return res;
+ }
+ } else if (pDataObj->QueryGetData(&filedesc_fmt) == S_OK && is_valid_filedescriptor()) {
+ HRESULT res = handle_filedescriptor_format(&files, pDataObj);
+ if (res != S_OK) {
+ return res;
+ }
+ } else {
+ return E_UNEXPECTED;
+ }
+
+ if (!files.size()) {
+ return S_OK;
+ }
+
+ Variant v_files = files;
+ const Variant *v_args[1] = { &v_files };
+ Variant ret;
+ Callable::CallError ce;
+ window_data->drop_files_callback.callp((const Variant **)&v_args, 1, ret, ce);
+
+ if (!tmp_path.is_empty()) {
+ remove_dir_recursive(tmp_path);
+ tmp_path.clear();
+ }
+
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute drop files callback: %s.", Variant::get_callable_error_text(window_data->drop_files_callback, v_args, 1, ce)));
+ return E_UNEXPECTED;
+ }
+
+ *pdwEffect = DROPEFFECT_COPY;
+ return S_OK;
+}
diff --git a/platform/windows/drop_target_windows.h b/platform/windows/drop_target_windows.h
new file mode 100644
index 0000000000..bd0e0270c6
--- /dev/null
+++ b/platform/windows/drop_target_windows.h
@@ -0,0 +1,77 @@
+/**************************************************************************/
+/* drop_target_windows.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 DROP_TARGET_WINDOWS_H
+#define DROP_TARGET_WINDOWS_H
+
+#include "display_server_windows.h"
+
+#include <shlobj.h>
+
+// Silence warning due to a COM API weirdness.
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
+#endif
+
+// https://learn.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-dodragdrop#remarks
+class DropTargetWindows : public IDropTarget {
+ LONG ref_count;
+ DisplayServerWindows::WindowData *window_data = nullptr;
+ CLIPFORMAT cf_filedescriptor = 0;
+ CLIPFORMAT cf_filecontents = 0;
+ String tmp_path;
+
+ bool is_valid_filedescriptor();
+ HRESULT handle_hdrop_format(Vector<String> *p_files, IDataObject *pDataObj);
+ HRESULT handle_filedescriptor_format(Vector<String> *p_files, IDataObject *pDataObj);
+ HRESULT save_as_file(const String &p_out_dir, FILEDESCRIPTORW *p_file_desc, IDataObject *pDataObj, int p_file_idx);
+
+public:
+ DropTargetWindows(DisplayServerWindows::WindowData *p_window_data);
+ virtual ~DropTargetWindows() {}
+
+ // IUnknown
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override;
+ ULONG STDMETHODCALLTYPE AddRef() override;
+ ULONG STDMETHODCALLTYPE Release() override;
+
+ // IDropTarget
+ HRESULT STDMETHODCALLTYPE DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) override;
+ HRESULT STDMETHODCALLTYPE DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) override;
+ HRESULT STDMETHODCALLTYPE DragLeave() override;
+ HRESULT STDMETHODCALLTYPE Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) override;
+};
+
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+#endif // DROP_TARGET_WINDOWS_H
diff --git a/platform/windows/godot.natvis b/platform/windows/godot.natvis
index 14536fa130..fc34ad3cb3 100644
--- a/platform/windows/godot.natvis
+++ b/platform/windows/godot.natvis
@@ -1,5 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
+ <Type Name="Ref&lt;*&gt;">
+ <SmartPointer Usage="Minimal">reference</SmartPointer>
+ <DisplayString Condition="!reference">[empty]</DisplayString>
+ <DisplayString Condition="!!reference">{*reference}</DisplayString>
+ <Expand>
+ <Item Condition="!!reference" Name="[ptr]">reference</Item>
+ <Item Condition="!!reference" Name="[refcount]">reference->refcount.count.value</Item>
+ </Expand>
+ </Type>
+
<Type Name="Vector&lt;*&gt;">
<Expand>
<Item Name="[size]">_cowdata._ptr ? (((const unsigned long long *)(_cowdata._ptr))[-1]) : 0</Item>
@@ -91,6 +101,16 @@
<StringView Condition="_data &amp;&amp; !_data->cname">_data->name,s32b</StringView>
</Type>
+ <Type Name="HashSet&lt;*,*,*&gt;">
+ <Expand>
+ <Item Name="[size]">num_elements</Item>
+ <ArrayItems>
+ <Size>num_elements</Size>
+ <ValuePointer>($T1 *) keys._cowdata._ptr</ValuePointer>
+ </ArrayItems>
+ </Expand>
+ </Type>
+
<Type Name="HashMapElement&lt;*,*&gt;">
<DisplayString>{{Key = {($T1 *) &amp;data.key} Value = {($T2 *) &amp;data.value}}}</DisplayString>
<Expand>
diff --git a/platform/windows/joypad_windows.cpp b/platform/windows/joypad_windows.cpp
index a5f1629cf0..0f55cda5d7 100644
--- a/platform/windows/joypad_windows.cpp
+++ b/platform/windows/joypad_windows.cpp
@@ -122,8 +122,9 @@ bool JoypadWindows::is_xinput_device(const GUID *p_guid) {
memcmp(p_guid, &IID_XOneSWirelessGamepad, sizeof(*p_guid)) == 0 ||
memcmp(p_guid, &IID_XOneSBluetoothGamepad, sizeof(*p_guid)) == 0 ||
memcmp(p_guid, &IID_XOneEliteWirelessGamepad, sizeof(*p_guid)) == 0 ||
- memcmp(p_guid, &IID_XOneElite2WirelessGamepad, sizeof(*p_guid)) == 0)
+ memcmp(p_guid, &IID_XOneElite2WirelessGamepad, sizeof(*p_guid)) == 0) {
return true;
+ }
PRAWINPUTDEVICELIST dev_list = nullptr;
unsigned int dev_list_count = 0;
diff --git a/platform/windows/native_menu_windows.cpp b/platform/windows/native_menu_windows.cpp
index fde55918e4..9fa2849e7b 100644
--- a/platform/windows/native_menu_windows.cpp
+++ b/platform/windows/native_menu_windows.cpp
@@ -158,7 +158,7 @@ Size2 NativeMenuWindows::get_size(const RID &p_rid) const {
int count = GetMenuItemCount(md->menu);
for (int i = 0; i < count; i++) {
RECT rect;
- if (GetMenuItemRect(NULL, md->menu, i, &rect)) {
+ if (GetMenuItemRect(nullptr, md->menu, i, &rect)) {
size.x = MAX(size.x, rect.right - rect.left);
size.y += rect.bottom - rect.top;
}
@@ -992,7 +992,7 @@ void NativeMenuWindows::set_item_submenu(const RID &p_rid, int p_idx, const RID
if (p_submenu_rid.is_valid()) {
item.hSubMenu = md_sub->menu;
} else {
- item.hSubMenu = 0;
+ item.hSubMenu = nullptr;
}
SetMenuItemInfoW(md->menu, p_idx, true, &item);
}
@@ -1095,7 +1095,7 @@ void NativeMenuWindows::set_item_icon(const RID &p_rid, int p_idx, const Ref<Tex
item_data->bmp = _make_bitmap(item_data->img);
} else {
item_data->img = Ref<Image>();
- item_data->bmp = 0;
+ item_data->bmp = nullptr;
}
item.hbmpItem = item_data->bmp;
SetMenuItemInfoW(md->menu, p_idx, true, &item);
diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp
index ce4ebb03f2..bff3443214 100644
--- a/platform/windows/os_windows.cpp
+++ b/platform/windows/os_windows.cpp
@@ -139,13 +139,13 @@ void RedirectIOToConsole() {
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
// Restore redirection (Note: if not redirected it's NULL handles not INVALID_HANDLE_VALUE).
- if (h_stdin != 0) {
+ if (h_stdin != nullptr) {
SetStdHandle(STD_INPUT_HANDLE, h_stdin);
}
- if (h_stdout != 0) {
+ if (h_stdout != nullptr) {
SetStdHandle(STD_OUTPUT_HANDLE, h_stdout);
}
- if (h_stderr != 0) {
+ if (h_stderr != nullptr) {
SetStdHandle(STD_ERROR_HANDLE, h_stderr);
}
@@ -908,9 +908,9 @@ Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String
}
// Create pipes.
- HANDLE pipe_in[2] = { 0, 0 };
- HANDLE pipe_out[2] = { 0, 0 };
- HANDLE pipe_err[2] = { 0, 0 };
+ HANDLE pipe_in[2] = { nullptr, nullptr };
+ HANDLE pipe_out[2] = { nullptr, nullptr };
+ HANDLE pipe_err[2] = { nullptr, nullptr };
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
@@ -981,7 +981,7 @@ Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String
Ref<FileAccessWindowsPipe> err_pipe;
err_pipe.instantiate();
- err_pipe->open_existing(pipe_err[0], 0, p_blocking);
+ err_pipe->open_existing(pipe_err[0], nullptr, p_blocking);
ret["stdio"] = main_pipe;
ret["stderr"] = err_pipe;
diff --git a/platform/windows/platform_windows_builders.py b/platform/windows/platform_windows_builders.py
index 3fd9e1a581..2020e68748 100644
--- a/platform/windows/platform_windows_builders.py
+++ b/platform/windows/platform_windows_builders.py
@@ -2,23 +2,11 @@
import os
-from detect import get_mingw_bin_prefix, try_cmd
-
def make_debug_mingw(target, source, env):
dst = str(target[0])
# Force separate debug symbols if executable size is larger than 1.9 GB.
if env["separate_debug_symbols"] or os.stat(dst).st_size >= 2040109465:
- mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"])
- if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]):
- os.system(mingw_bin_prefix + "objcopy --only-keep-debug {0} {0}.debugsymbols".format(dst))
- else:
- os.system("objcopy --only-keep-debug {0} {0}.debugsymbols".format(dst))
- if try_cmd("strip --version", env["mingw_prefix"], env["arch"]):
- os.system(mingw_bin_prefix + "strip --strip-debug --strip-unneeded {0}".format(dst))
- else:
- os.system("strip --strip-debug --strip-unneeded {0}".format(dst))
- if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]):
- os.system(mingw_bin_prefix + "objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(dst))
- else:
- os.system("objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(dst))
+ os.system("{0} --only-keep-debug {1} {1}.debugsymbols".format(env["OBJCOPY"], dst))
+ os.system("{0} --strip-debug --strip-unneeded {1}".format(env["STRIP"], dst))
+ os.system("{0} --add-gnu-debuglink={1}.debugsymbols {1}".format(env["OBJCOPY"], dst))
diff --git a/platform/windows/windows_utils.cpp b/platform/windows/windows_utils.cpp
index 30743c6900..3b9bfb50f7 100644
--- a/platform/windows/windows_utils.cpp
+++ b/platform/windows/windows_utils.cpp
@@ -67,11 +67,11 @@ Error WindowsUtils::copy_and_rename_pdb(const String &p_dll_path) {
{
// The custom LoadLibraryExW is used instead of open_dynamic_library
// to avoid loading the original PDB into the debugger.
- HMODULE library_ptr = LoadLibraryExW((LPCWSTR)(p_dll_path.utf16().get_data()), NULL, LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE);
+ HMODULE library_ptr = LoadLibraryExW((LPCWSTR)(p_dll_path.utf16().get_data()), nullptr, LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE);
ERR_FAIL_NULL_V_MSG(library_ptr, ERR_FILE_CANT_OPEN, vformat("Failed to load library '%s'.", p_dll_path));
- IMAGE_DEBUG_DIRECTORY *dbg_dir = (IMAGE_DEBUG_DIRECTORY *)ImageDirectoryEntryToDataEx(library_ptr, FALSE, IMAGE_DIRECTORY_ENTRY_DEBUG, &dbg_info_size, NULL);
+ IMAGE_DEBUG_DIRECTORY *dbg_dir = (IMAGE_DEBUG_DIRECTORY *)ImageDirectoryEntryToDataEx(library_ptr, FALSE, IMAGE_DIRECTORY_ENTRY_DEBUG, &dbg_info_size, nullptr);
bool has_debug = dbg_dir && dbg_dir->Type == IMAGE_DEBUG_TYPE_CODEVIEW;
if (has_debug) {
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index c1c992588b..abece303d1 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -183,17 +183,17 @@ bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const {
void Skeleton3D::_get_property_list(List<PropertyInfo> *p_list) const {
for (uint32_t i = 0; i < bones.size(); i++) {
- const String prep = vformat("%s/%d/", PNAME("bones"), i);
- p_list->push_back(PropertyInfo(Variant::STRING, prep + PNAME("name"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
- p_list->push_back(PropertyInfo(Variant::INT, prep + PNAME("parent"), PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1", PROPERTY_USAGE_NO_EDITOR));
- p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prep + PNAME("rest"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
- p_list->push_back(PropertyInfo(Variant::BOOL, prep + PNAME("enabled"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
- p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + PNAME("position"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
- p_list->push_back(PropertyInfo(Variant::QUATERNION, prep + PNAME("rotation"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
- p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + PNAME("scale"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ const String prep = vformat("%s/%d/", "bones", i);
+ p_list->push_back(PropertyInfo(Variant::STRING, prep + "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::INT, prep + "parent", PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prep + "rest", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::BOOL, prep + "enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + "position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::QUATERNION, prep + "rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + "scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
for (const KeyValue<StringName, Variant> &K : bones[i].metadata) {
- PropertyInfo pi = PropertyInfo(bones[i].metadata[K.key].get_type(), prep + PNAME("bone_meta/") + K.key, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR);
+ PropertyInfo pi = PropertyInfo(bones[i].metadata[K.key].get_type(), prep + "bone_meta/" + K.key, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR);
p_list->push_back(pi);
}
}
diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h
index ecfe095f1d..90902f71e2 100644
--- a/scene/3d/skeleton_3d.h
+++ b/scene/3d/skeleton_3d.h
@@ -162,7 +162,7 @@ private:
Vector<int> parentless_bones;
AHashMap<String, int> name_to_bone_index;
- mutable StringName concatenated_bone_names = StringName();
+ mutable StringName concatenated_bone_names;
void _update_bone_names() const;
void _make_dirty();
diff --git a/scene/3d/skeleton_ik_3d.h b/scene/3d/skeleton_ik_3d.h
index 5d6020194e..94145a6915 100644
--- a/scene/3d/skeleton_ik_3d.h
+++ b/scene/3d/skeleton_ik_3d.h
@@ -131,7 +131,7 @@ class SkeletonIK3D : public SkeletonModifier3D {
real_t min_distance = 0.01;
int max_iterations = 10;
- Variant target_node_override_ref = Variant();
+ Variant target_node_override_ref;
FabrikInverseKinematic::Task *task = nullptr;
#ifndef DISABLE_DEPRECATED
diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp
index b87285ed74..1c05271daa 100644
--- a/scene/debugger/scene_debugger.cpp
+++ b/scene/debugger/scene_debugger.cpp
@@ -147,7 +147,6 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra
ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
Transform2D transform = p_args[0];
scene_tree->get_root()->set_canvas_transform_override(transform);
-
runtime_node_select->_queue_selection_update();
#ifndef _3D_DISABLED
@@ -164,16 +163,19 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra
scene_tree->get_root()->set_camera_3d_override_orthogonal(size_or_fov, depth_near, depth_far);
}
scene_tree->get_root()->set_camera_3d_override_transform(transform);
-
runtime_node_select->_queue_selection_update();
#endif // _3D_DISABLED
} else if (p_msg == "set_object_property") {
ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
_set_object_property(p_args[0], p_args[1], p_args[2]);
-
runtime_node_select->_queue_selection_update();
+ } else if (p_msg == "reload_cached_files") {
+ ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
+ PackedStringArray files = p_args[0];
+ reload_cached_files(files);
+
} else if (p_msg.begins_with("live_")) { /// Live Edit
if (p_msg == "live_set_root") {
ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
@@ -413,6 +415,15 @@ void SceneDebugger::remove_from_cache(const String &p_filename, Node *p_node) {
}
}
+void SceneDebugger::reload_cached_files(const PackedStringArray &p_files) {
+ for (const String &file : p_files) {
+ Ref<Resource> res = ResourceCache::get_ref(file);
+ if (res.is_valid()) {
+ res->reload_from_file();
+ }
+ }
+}
+
/// SceneDebuggerObject
SceneDebuggerObject::SceneDebuggerObject(ObjectID p_id) {
id = ObjectID();
diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h
index f9dd6161aa..9e9f61aac5 100644
--- a/scene/debugger/scene_debugger.h
+++ b/scene/debugger/scene_debugger.h
@@ -67,6 +67,7 @@ public:
static Error parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured);
static void add_to_cache(const String &p_filename, Node *p_node);
static void remove_from_cache(const String &p_filename, Node *p_node);
+ static void reload_cached_files(const PackedStringArray &p_files);
#endif
};
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index ab443e95e1..2cd34ec99f 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -251,13 +251,13 @@ private:
Ref<Texture2D> completion_color_bg;
Color breakpoint_color = Color(1, 1, 1);
- Ref<Texture2D> breakpoint_icon = Ref<Texture2D>();
+ Ref<Texture2D> breakpoint_icon;
Color bookmark_color = Color(1, 1, 1);
- Ref<Texture2D> bookmark_icon = Ref<Texture2D>();
+ Ref<Texture2D> bookmark_icon;
Color executing_line_color = Color(1, 1, 1);
- Ref<Texture2D> executing_line_icon = Ref<Texture2D>();
+ Ref<Texture2D> executing_line_icon;
Color line_number_color = Color(1, 1, 1);
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 18864b1289..9eda1a256f 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -1586,6 +1586,7 @@ FileDialog::FileDialog() {
vbox->add_child(hbc);
tree = memnew(Tree);
+ tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
tree->set_hide_root(true);
vbox->add_margin_child(ETR("Directories & Files:"), tree, true);
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index cb495f9d47..b1c47d858d 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -317,7 +317,7 @@ bool GraphEdit::is_node_connected(const StringName &p_from, int p_from_port, con
void GraphEdit::disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port) {
ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
- for (const List<Ref<Connection>>::Element *E = connections.front(); E; E = E->next()) {
+ for (List<Ref<Connection>>::Element *E = connections.front(); E; E = E->next()) {
if (E->get()->from_node == p_from && E->get()->from_port == p_from_port && E->get()->to_node == p_to && E->get()->to_port == p_to_port) {
connection_map[p_from].erase(E->get());
connection_map[p_to].erase(E->get());
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 9967805134..3f979f7c20 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -128,6 +128,7 @@ bool LineEdit::has_ime_text() const {
void LineEdit::cancel_ime() {
if (!has_ime_text()) {
+ _close_ime_window();
return;
}
ime_text = String();
@@ -140,6 +141,7 @@ void LineEdit::cancel_ime() {
void LineEdit::apply_ime() {
if (!has_ime_text()) {
+ _close_ime_window();
return;
}
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 6b5ff23436..b1918ff23f 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -3169,6 +3169,7 @@ bool TextEdit::has_ime_text() const {
void TextEdit::cancel_ime() {
if (!has_ime_text()) {
+ _close_ime_window();
return;
}
ime_text = String();
@@ -3181,6 +3182,7 @@ void TextEdit::cancel_ime() {
void TextEdit::apply_ime() {
if (!has_ime_text()) {
+ _close_ime_window();
return;
}
@@ -5069,7 +5071,7 @@ bool TextEdit::multicaret_edit_ignore_caret(int p_caret) const {
}
bool TextEdit::is_caret_visible(int p_caret) const {
- ERR_FAIL_INDEX_V(p_caret, carets.size(), 0);
+ ERR_FAIL_INDEX_V(p_caret, carets.size(), false);
return carets[p_caret].visible;
}
@@ -5736,7 +5738,7 @@ TextServer::AutowrapMode TextEdit::get_autowrap_mode() const {
}
bool TextEdit::is_line_wrapped(int p_line) const {
- ERR_FAIL_INDEX_V(p_line, text.size(), 0);
+ ERR_FAIL_INDEX_V(p_line, text.size(), false);
if (get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE) {
return false;
}
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index e0d552848d..6b137581f2 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -139,7 +139,7 @@ private:
Variant metadata;
bool clickable = false;
- Ref<Texture2D> icon = Ref<Texture2D>();
+ Ref<Texture2D> icon;
String text = "";
Color color = Color(1, 1, 1);
};
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index aab6f672f0..cfd93a5317 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -5362,14 +5362,16 @@ void Tree::scroll_to_item(TreeItem *p_item, bool p_center_on_item) {
int y_offset = get_item_offset(p_item);
if (y_offset != -1) {
- const int tbh = _get_title_button_height();
- y_offset -= tbh;
+ const int title_button_height = _get_title_button_height();
+ y_offset -= title_button_height;
const int cell_h = compute_item_height(p_item) + theme_cache.v_separation;
- int screen_h = area_size.height - tbh;
+ int screen_h = area_size.height - title_button_height;
if (p_center_on_item) {
- v_scroll->set_value(y_offset - (screen_h - cell_h) / 2.0f);
+ // This makes sure that centering the offset doesn't overflow.
+ const double v_scroll_value = y_offset - MAX((screen_h - cell_h) / 2.0, 0.0);
+ v_scroll->set_value(v_scroll_value);
} else {
if (cell_h > screen_h) { // Screen size is too small, maybe it was not resized yet.
v_scroll->set_value(y_offset);
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 6b1ce2b4ca..315ccaeb8c 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -512,6 +512,9 @@ void register_scene_types() {
GDREGISTER_CLASS(AnimationNodeStateMachine);
GDREGISTER_CLASS(AnimationNodeStateMachinePlayback);
+ GDREGISTER_INTERNAL_CLASS(AnimationNodeStartState);
+ GDREGISTER_INTERNAL_CLASS(AnimationNodeEndState);
+
GDREGISTER_CLASS(AnimationNodeSync);
GDREGISTER_CLASS(AnimationNodeStateMachineTransition);
GDREGISTER_CLASS(AnimationNodeOutput);
diff --git a/scene/resources/2d/skeleton/skeleton_modification_stack_2d.h b/scene/resources/2d/skeleton/skeleton_modification_stack_2d.h
index 0732153997..d1e50cb702 100644
--- a/scene/resources/2d/skeleton/skeleton_modification_stack_2d.h
+++ b/scene/resources/2d/skeleton/skeleton_modification_stack_2d.h
@@ -64,7 +64,7 @@ public:
execution_mode_physics_process
};
- Vector<Ref<SkeletonModification2D>> modifications = Vector<Ref<SkeletonModification2D>>();
+ Vector<Ref<SkeletonModification2D>> modifications;
void setup();
void execute(float p_delta, int p_execution_mode);
diff --git a/scene/resources/2d/tile_set.cpp b/scene/resources/2d/tile_set.cpp
index ca80486363..54a18cf67e 100644
--- a/scene/resources/2d/tile_set.cpp
+++ b/scene/resources/2d/tile_set.cpp
@@ -4220,10 +4220,10 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const {
// Tile Proxies.
// Note: proxies need to be set after sources are set.
- p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Tile Proxies", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
- p_list->push_back(PropertyInfo(Variant::ARRAY, PNAME("tile_proxies/source_level"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
- p_list->push_back(PropertyInfo(Variant::ARRAY, PNAME("tile_proxies/coords_level"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
- p_list->push_back(PropertyInfo(Variant::ARRAY, PNAME("tile_proxies/alternative_level"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::NIL, "Tile Proxies", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/source_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/coords_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/alternative_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
// Patterns.
for (unsigned int pattern_index = 0; pattern_index < patterns.size(); pattern_index++) {
@@ -4931,10 +4931,13 @@ void TileSetAtlasSource::_get_property_list(List<PropertyInfo> *p_list) const {
}
for (const KeyValue<int, TileData *> &E_alternative : E_tile.value.alternatives) {
+ const String formatted_key = itos(E_alternative.key);
+
// Add a dummy property to show the alternative exists.
- tile_property_list.push_back(PropertyInfo(Variant::INT, vformat("%d", E_alternative.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ tile_property_list.push_back(PropertyInfo(Variant::INT, formatted_key, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
// Get the alternative tile's properties and append them to the list of properties.
+ const String alternative_property_info_prefix = formatted_key + '/';
List<PropertyInfo> alternative_property_list;
E_alternative.value->get_property_list(&alternative_property_list);
for (PropertyInfo &alternative_property_info : alternative_property_list) {
@@ -4943,14 +4946,15 @@ void TileSetAtlasSource::_get_property_list(List<PropertyInfo> *p_list) const {
if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) {
alternative_property_info.usage ^= PROPERTY_USAGE_STORAGE;
}
- alternative_property_info.name = vformat("%s/%s", vformat("%d", E_alternative.key), alternative_property_info.name);
+ alternative_property_info.name = alternative_property_info_prefix + alternative_property_info.name;
tile_property_list.push_back(alternative_property_info);
}
}
// Add all alternative.
+ const String property_info_prefix = vformat("%d:%d/", E_tile.key.x, E_tile.key.y);
for (PropertyInfo &tile_property_info : tile_property_list) {
- tile_property_info.name = vformat("%s/%s", vformat("%d:%d", E_tile.key.x, E_tile.key.y), tile_property_info.name);
+ tile_property_info.name = property_info_prefix + tile_property_info.name;
p_list->push_back(tile_property_info);
}
}
diff --git a/scene/resources/2d/tile_set.h b/scene/resources/2d/tile_set.h
index 7979e2ca39..8343c22b91 100644
--- a/scene/resources/2d/tile_set.h
+++ b/scene/resources/2d/tile_set.h
@@ -836,7 +836,7 @@ private:
bool flip_v = false;
bool transpose = false;
Vector2i texture_origin;
- Ref<Material> material = Ref<Material>();
+ Ref<Material> material;
Color modulate = Color(1.0, 1.0, 1.0, 1.0);
int z_index = 0;
int y_sort_origin = 0;
diff --git a/scene/resources/3d/importer_mesh.cpp b/scene/resources/3d/importer_mesh.cpp
index e255cb077f..f040f04cd8 100644
--- a/scene/resources/3d/importer_mesh.cpp
+++ b/scene/resources/3d/importer_mesh.cpp
@@ -168,10 +168,56 @@ void ImporterMesh::set_surface_material(int p_surface, const Ref<Material> &p_ma
mesh.unref();
}
-void ImporterMesh::optimize_indices_for_cache() {
+template <typename T>
+static Vector<T> _remap_array(Vector<T> p_array, const Vector<uint32_t> &p_remap, uint32_t p_vertex_count) {
+ ERR_FAIL_COND_V(p_array.size() % p_remap.size() != 0, p_array);
+ int num_elements = p_array.size() / p_remap.size();
+ T *data = p_array.ptrw();
+ SurfaceTool::remap_vertex_func(data, data, p_remap.size(), sizeof(T) * num_elements, p_remap.ptr());
+ p_array.resize(p_vertex_count * num_elements);
+ return p_array;
+}
+
+static void _remap_arrays(Array &r_arrays, const Vector<uint32_t> &p_remap, uint32_t p_vertex_count) {
+ for (int i = 0; i < r_arrays.size(); i++) {
+ if (i == RS::ARRAY_INDEX) {
+ continue;
+ }
+
+ switch (r_arrays[i].get_type()) {
+ case Variant::NIL:
+ break;
+ case Variant::PACKED_VECTOR3_ARRAY:
+ r_arrays[i] = _remap_array<Vector3>(r_arrays[i], p_remap, p_vertex_count);
+ break;
+ case Variant::PACKED_VECTOR2_ARRAY:
+ r_arrays[i] = _remap_array<Vector2>(r_arrays[i], p_remap, p_vertex_count);
+ break;
+ case Variant::PACKED_FLOAT32_ARRAY:
+ r_arrays[i] = _remap_array<float>(r_arrays[i], p_remap, p_vertex_count);
+ break;
+ case Variant::PACKED_INT32_ARRAY:
+ r_arrays[i] = _remap_array<int32_t>(r_arrays[i], p_remap, p_vertex_count);
+ break;
+ case Variant::PACKED_BYTE_ARRAY:
+ r_arrays[i] = _remap_array<uint8_t>(r_arrays[i], p_remap, p_vertex_count);
+ break;
+ case Variant::PACKED_COLOR_ARRAY:
+ r_arrays[i] = _remap_array<Color>(r_arrays[i], p_remap, p_vertex_count);
+ break;
+ default:
+ ERR_FAIL_MSG("Unhandled array type.");
+ }
+ }
+}
+
+void ImporterMesh::optimize_indices() {
if (!SurfaceTool::optimize_vertex_cache_func) {
return;
}
+ if (!SurfaceTool::optimize_vertex_fetch_remap_func || !SurfaceTool::remap_vertex_func || !SurfaceTool::remap_index_func) {
+ return;
+ }
for (int i = 0; i < surfaces.size(); i++) {
if (surfaces[i].primitive != Mesh::PRIMITIVE_TRIANGLES) {
@@ -188,10 +234,48 @@ void ImporterMesh::optimize_indices_for_cache() {
continue;
}
+ // Optimize indices for vertex cache to establish final triangle order.
int *indices_ptr = indices.ptrw();
SurfaceTool::optimize_vertex_cache_func((unsigned int *)indices_ptr, (const unsigned int *)indices_ptr, index_count, vertex_count);
+ surfaces.write[i].arrays[RS::ARRAY_INDEX] = indices;
+
+ for (int j = 0; j < surfaces[i].lods.size(); ++j) {
+ Surface::LOD &lod = surfaces.write[i].lods.write[j];
+ int *lod_indices_ptr = lod.indices.ptrw();
+ SurfaceTool::optimize_vertex_cache_func((unsigned int *)lod_indices_ptr, (const unsigned int *)lod_indices_ptr, lod.indices.size(), vertex_count);
+ }
+ // Concatenate indices for all LODs in the order of coarse->fine; this establishes the effective order of vertices,
+ // and is important to optimize for vertex fetch (all GPUs) and shading (Mali GPUs)
+ PackedInt32Array merged_indices;
+ for (int j = surfaces[i].lods.size() - 1; j >= 0; --j) {
+ merged_indices.append_array(surfaces[i].lods[j].indices);
+ }
+ merged_indices.append_array(indices);
+
+ // Generate remap array that establishes optimal vertex order according to the order of indices above.
+ Vector<uint32_t> remap;
+ remap.resize(vertex_count);
+ unsigned int new_vertex_count = SurfaceTool::optimize_vertex_fetch_remap_func(remap.ptrw(), (const unsigned int *)merged_indices.ptr(), merged_indices.size(), vertex_count);
+
+ // We need to remap all vertex and index arrays in lockstep according to the remap.
+ SurfaceTool::remap_index_func((unsigned int *)indices_ptr, (const unsigned int *)indices_ptr, index_count, remap.ptr());
surfaces.write[i].arrays[RS::ARRAY_INDEX] = indices;
+
+ for (int j = 0; j < surfaces[i].lods.size(); ++j) {
+ Surface::LOD &lod = surfaces.write[i].lods.write[j];
+ int *lod_indices_ptr = lod.indices.ptrw();
+ SurfaceTool::remap_index_func((unsigned int *)lod_indices_ptr, (const unsigned int *)lod_indices_ptr, lod.indices.size(), remap.ptr());
+ }
+
+ _remap_arrays(surfaces.write[i].arrays, remap, new_vertex_count);
+ for (int j = 0; j < surfaces[i].blend_shape_data.size(); j++) {
+ _remap_arrays(surfaces.write[i].blend_shape_data.write[j].arrays, remap, new_vertex_count);
+ }
+ }
+
+ if (shadow_mesh.is_valid()) {
+ shadow_mesh->optimize_indices();
}
}
@@ -215,9 +299,6 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, Array p_bone_transf
if (!SurfaceTool::simplify_with_attrib_func) {
return;
}
- if (!SurfaceTool::optimize_vertex_cache_func) {
- return;
- }
LocalVector<Transform3D> bone_transform_vector;
for (int i = 0; i < p_bone_transform_array.size(); i++) {
@@ -431,12 +512,6 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, Array p_bone_transf
}
surfaces.write[i].lods.sort_custom<Surface::LODComparator>();
-
- for (int j = 0; j < surfaces.write[i].lods.size(); j++) {
- Surface::LOD &lod = surfaces.write[i].lods.write[j];
- unsigned int *lod_indices_ptr = (unsigned int *)lod.indices.ptrw();
- SurfaceTool::optimize_vertex_cache_func(lod_indices_ptr, lod_indices_ptr, lod.indices.size(), vertex_count);
- }
}
}
@@ -574,10 +649,6 @@ void ImporterMesh::create_shadow_mesh() {
index_wptr[j] = vertex_remap[index];
}
- if (SurfaceTool::optimize_vertex_cache_func && surfaces[i].primitive == Mesh::PRIMITIVE_TRIANGLES) {
- SurfaceTool::optimize_vertex_cache_func((unsigned int *)index_wptr, (const unsigned int *)index_wptr, index_count, new_vertices.size());
- }
-
new_surface[RS::ARRAY_INDEX] = new_indices;
// Make sure the same LODs as the full version are used.
@@ -596,10 +667,6 @@ void ImporterMesh::create_shadow_mesh() {
index_wptr[k] = vertex_remap[index];
}
- if (SurfaceTool::optimize_vertex_cache_func && surfaces[i].primitive == Mesh::PRIMITIVE_TRIANGLES) {
- SurfaceTool::optimize_vertex_cache_func((unsigned int *)index_wptr, (const unsigned int *)index_wptr, index_count, new_vertices.size());
- }
-
lods[surfaces[i].lods[j].distance] = new_indices;
}
}
diff --git a/scene/resources/3d/importer_mesh.h b/scene/resources/3d/importer_mesh.h
index 7776e78f11..2bdf759da6 100644
--- a/scene/resources/3d/importer_mesh.h
+++ b/scene/resources/3d/importer_mesh.h
@@ -113,7 +113,7 @@ public:
void set_surface_material(int p_surface, const Ref<Material> &p_material);
- void optimize_indices_for_cache();
+ void optimize_indices();
void generate_lods(float p_normal_merge_angle, Array p_skin_pose_transform_array);
diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp
index f9787dde2e..539001bf25 100644
--- a/scene/resources/audio_stream_wav.cpp
+++ b/scene/resources/audio_stream_wav.cpp
@@ -624,7 +624,7 @@ Error AudioStreamWAV::save_to_wav(const String &p_path) {
}
String file_path = p_path;
- if (!(file_path.substr(file_path.length() - 4, 4) == ".wav")) {
+ if (file_path.substr(file_path.length() - 4, 4).to_lower() != ".wav") {
file_path += ".wav";
}
diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp
index f8c70c3002..b5e23e9832 100644
--- a/scene/resources/environment.cpp
+++ b/scene/resources/environment.cpp
@@ -130,10 +130,7 @@ int Environment::get_canvas_max_layer() const {
void Environment::set_camera_feed_id(int p_id) {
bg_camera_feed_id = p_id;
-// FIXME: Disabled during Vulkan refactoring, should be ported.
-#if 0
- RS::get_singleton()->environment_set_camera_feed_id(environment, camera_feed_id);
-#endif
+ RS::get_singleton()->environment_set_camera_feed_id(environment, bg_camera_feed_id);
}
int Environment::get_camera_feed_id() const {
diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp
index 5e4136f449..a4677d917d 100644
--- a/scene/resources/font.cpp
+++ b/scene/resources/font.cpp
@@ -647,13 +647,13 @@ void FontFile::_convert_packed_8bit(Ref<Image> &p_source, int p_page, int p_sz)
wa[ofs_dst + 1] = r[ofs_src + 3];
}
}
- Ref<Image> img_r = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_r));
+ Ref<Image> img_r = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_r));
set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 0, img_r);
- Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
+ Ref<Image> img_g = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_g));
set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 1, img_g);
- Ref<Image> img_b = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_b));
+ Ref<Image> img_b = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_b));
set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 2, img_b);
- Ref<Image> img_a = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_a));
+ Ref<Image> img_a = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_a));
set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 3, img_a);
}
@@ -738,22 +738,22 @@ void FontFile::_convert_packed_4bit(Ref<Image> &p_source, int p_page, int p_sz)
}
}
}
- Ref<Image> img_r = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_r));
+ Ref<Image> img_r = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_r));
set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 0, img_r);
- Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
+ Ref<Image> img_g = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_g));
set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 1, img_g);
- Ref<Image> img_b = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_b));
+ Ref<Image> img_b = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_b));
set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 2, img_b);
- Ref<Image> img_a = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_a));
+ Ref<Image> img_a = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_a));
set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 3, img_a);
- Ref<Image> img_ro = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_ro));
+ Ref<Image> img_ro = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_ro));
set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 0, img_ro);
- Ref<Image> img_go = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_go));
+ Ref<Image> img_go = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_go));
set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 1, img_go);
- Ref<Image> img_bo = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_bo));
+ Ref<Image> img_bo = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_bo));
set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 2, img_bo);
- Ref<Image> img_ao = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_ao));
+ Ref<Image> img_ao = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_ao));
set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 3, img_ao);
}
@@ -806,10 +806,10 @@ void FontFile::_convert_rgba_4bit(Ref<Image> &p_source, int p_page, int p_sz) {
}
}
}
- Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_RGBA8, imgdata_g));
+ Ref<Image> img_g = memnew(Image(w, h, false, Image::FORMAT_RGBA8, imgdata_g));
set_texture_image(0, Vector2i(p_sz, 0), p_page, img_g);
- Ref<Image> img_o = memnew(Image(w, h, 0, Image::FORMAT_RGBA8, imgdata_o));
+ Ref<Image> img_o = memnew(Image(w, h, false, Image::FORMAT_RGBA8, imgdata_o));
set_texture_image(0, Vector2i(p_sz, 1), p_page, img_o);
}
@@ -838,7 +838,7 @@ void FontFile::_convert_mono_8bit(Ref<Image> &p_source, int p_page, int p_ch, in
wg[ofs_dst + 1] = r[ofs_src + p_ch];
}
}
- Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
+ Ref<Image> img_g = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_g));
set_texture_image(0, Vector2i(p_sz, p_ol), p_page, img_g);
}
@@ -878,10 +878,10 @@ void FontFile::_convert_mono_4bit(Ref<Image> &p_source, int p_page, int p_ch, in
}
}
}
- Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
+ Ref<Image> img_g = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_g));
set_texture_image(0, Vector2i(p_sz, 0), p_page, img_g);
- Ref<Image> img_o = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_o));
+ Ref<Image> img_o = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_o));
set_texture_image(0, Vector2i(p_sz, p_ol), p_page, img_o);
}
diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp
index 6921885ee0..c230cf1b70 100644
--- a/scene/resources/surface_tool.cpp
+++ b/scene/resources/surface_tool.cpp
@@ -33,10 +33,10 @@
#define EQ_VERTEX_DIST 0.00001
SurfaceTool::OptimizeVertexCacheFunc SurfaceTool::optimize_vertex_cache_func = nullptr;
+SurfaceTool::OptimizeVertexFetchRemapFunc SurfaceTool::optimize_vertex_fetch_remap_func = nullptr;
SurfaceTool::SimplifyFunc SurfaceTool::simplify_func = nullptr;
SurfaceTool::SimplifyWithAttribFunc SurfaceTool::simplify_with_attrib_func = nullptr;
SurfaceTool::SimplifyScaleFunc SurfaceTool::simplify_scale_func = nullptr;
-SurfaceTool::SimplifySloppyFunc SurfaceTool::simplify_sloppy_func = nullptr;
SurfaceTool::GenerateRemapFunc SurfaceTool::generate_remap_func = nullptr;
SurfaceTool::RemapVertexFunc SurfaceTool::remap_vertex_func = nullptr;
SurfaceTool::RemapIndexFunc SurfaceTool::remap_index_func = nullptr;
diff --git a/scene/resources/surface_tool.h b/scene/resources/surface_tool.h
index 3b18e082a1..68dc9e7198 100644
--- a/scene/resources/surface_tool.h
+++ b/scene/resources/surface_tool.h
@@ -90,14 +90,14 @@ public:
typedef void (*OptimizeVertexCacheFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, size_t vertex_count);
static OptimizeVertexCacheFunc optimize_vertex_cache_func;
+ typedef size_t (*OptimizeVertexFetchRemapFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, size_t vertex_count);
+ static OptimizeVertexFetchRemapFunc optimize_vertex_fetch_remap_func;
typedef size_t (*SimplifyFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, unsigned int options, float *r_error);
static SimplifyFunc simplify_func;
typedef size_t (*SimplifyWithAttribFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_data, size_t vertex_count, size_t vertex_stride, const float *attributes, size_t attribute_stride, const float *attribute_weights, size_t attribute_count, const unsigned char *vertex_lock, size_t target_index_count, float target_error, unsigned int options, float *result_error);
static SimplifyWithAttribFunc simplify_with_attrib_func;
typedef float (*SimplifyScaleFunc)(const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
static SimplifyScaleFunc simplify_scale_func;
- typedef size_t (*SimplifySloppyFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, float *out_result_error);
- static SimplifySloppyFunc simplify_sloppy_func;
typedef size_t (*GenerateRemapFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const void *vertices, size_t vertex_count, size_t vertex_size);
static GenerateRemapFunc generate_remap_func;
typedef void (*RemapVertexFunc)(void *destination, const void *vertices, size_t vertex_count, size_t vertex_size, const unsigned int *remap);
@@ -222,7 +222,9 @@ public:
void clear();
- LocalVector<Vertex> &get_vertex_array() { return vertex_array; }
+ LocalVector<Vertex> &get_vertex_array() {
+ return vertex_array;
+ }
void create_from_triangle_arrays(const Array &p_arrays);
void create_from_arrays(const Array &p_arrays, Mesh::PrimitiveType p_primitive_type = Mesh::PRIMITIVE_TRIANGLES);
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 933e990920..847867fa4d 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -1368,7 +1368,7 @@ void VisualShader::disconnect_nodes(Type p_type, int p_from_node, int p_from_por
ERR_FAIL_INDEX(p_type, TYPE_MAX);
Graph *g = &graph[p_type];
- for (const List<Connection>::Element *E = g->connections.front(); E; E = E->next()) {
+ for (List<Connection>::Element *E = g->connections.front(); E; E = E->next()) {
if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) {
g->connections.erase(E);
g->nodes[p_from_node].next_connected_nodes.erase(p_to_node);
@@ -1921,13 +1921,13 @@ void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const {
}
for (const KeyValue<String, Varying> &E : varyings) {
- p_list->push_back(PropertyInfo(Variant::STRING, vformat("%s/%s", PNAME("varyings"), E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::STRING, vformat("%s/%s", "varyings", E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
}
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
for (const KeyValue<String, Variant> &E : preview_params) {
- p_list->push_back(PropertyInfo(Variant::STRING, vformat("%s/%s", PNAME("preview_params"), E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::STRING, vformat("%s/%s", "preview_params", E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
}
}
#endif // TOOLS_ENABLED
diff --git a/scu_builders.py b/scu_builders.py
index 6bf87b9163..e0bfffc320 100644
--- a/scu_builders.py
+++ b/scu_builders.py
@@ -259,10 +259,6 @@ def process_folder(folders, sought_exceptions=[], includes_per_scu=0, extension=
def generate_scu_files(max_includes_per_scu):
- print("=============================")
- print("Single Compilation Unit Build")
- print("=============================")
-
global _max_includes_per_scu
_max_includes_per_scu = max_includes_per_scu
diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp
index 42aa43269b..d5c37c2acc 100644
--- a/servers/audio/audio_stream.cpp
+++ b/servers/audio/audio_stream.cpp
@@ -281,7 +281,7 @@ double AudioStream::get_bpm() const {
}
bool AudioStream::has_loop() const {
- bool ret = 0;
+ bool ret = false;
GDVIRTUAL_CALL(_has_loop, ret);
return ret;
}
diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp
index 14c4c3b7e1..63b2f28e74 100644
--- a/servers/audio_server.cpp
+++ b/servers/audio_server.cpp
@@ -371,10 +371,14 @@ void AudioServer::_mix_step() {
bus->soloed = false;
}
}
+ // This is legacy code from 3.x that allows video players and other audio sources that do not implement AudioStreamPlayback to output audio.
for (CallbackItem *ci : mix_callback_list) {
ci->callback(ci->userdata);
}
+ // Main mixing loop for audio streams.
+ // The basic idea here is to copy the samples returned by the AudioStreamPlayback's mix function into the audio buffers,
+ // while always maintaining a lookahead buffer of size LOOKAHEAD_BUFFER_SIZE to allow fade-outs for sudden stoppages.
for (AudioStreamPlaybackListNode *playback : playback_list) {
// Paused streams are no-ops. Don't even mix audio from the stream playback.
if (playback->state.load() == AudioStreamPlaybackListNode::PAUSED) {
@@ -385,22 +389,26 @@ void AudioServer::_mix_step() {
continue;
}
+ // If `fading_out` is true, we're in the process of fading out the stream playback.
+ // TODO: Currently this sets the volume of the stream to 0 which creates a linear interpolation between its previous volume and silence.
+ // A more punchy option for fading out could be to just use the lookahead buffer.
bool fading_out = playback->state.load() == AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION || playback->state.load() == AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE;
AudioFrame *buf = mix_buffer.ptrw();
- // Copy the lookeahead buffer into the mix buffer.
+ // Copy the old contents of the lookahead buffer into the beginning of the mix buffer.
for (int i = 0; i < LOOKAHEAD_BUFFER_SIZE; i++) {
buf[i] = playback->lookahead[i];
}
- // Mix the audio stream
+ // Mix the audio stream.
unsigned int mixed_frames = playback->stream_playback->mix(&buf[LOOKAHEAD_BUFFER_SIZE], playback->pitch_scale.get(), buffer_size);
if (tag_used_audio_streams && playback->stream_playback->is_playing()) {
playback->stream_playback->tag_used_streams();
}
+ // Check to see if the stream has run out of samples.
if (mixed_frames != buffer_size) {
// We know we have at least the size of our lookahead buffer for fade-out purposes.
@@ -416,42 +424,52 @@ void AudioServer::_mix_step() {
new_state = AudioStreamPlaybackListNode::AWAITING_DELETION;
playback->state.store(new_state);
} else {
- // Move the last little bit of what we just mixed into our lookahead buffer.
+ // Move the last little bit of what we just mixed into our lookahead buffer for the next call to _mix_step.
for (int i = 0; i < LOOKAHEAD_BUFFER_SIZE; i++) {
playback->lookahead[i] = buf[buffer_size + i];
}
}
- AudioStreamPlaybackBusDetails *ptr = playback->bus_details.load();
- ERR_FAIL_NULL(ptr);
- // By putting null into the bus details pointers, we're taking ownership of their memory for the duration of this mix.
- AudioStreamPlaybackBusDetails bus_details = *ptr;
+ // Get the bus details for this playback. This contains information about which buses the playback is assigned to and the volume of the playback on each bus.
+ AudioStreamPlaybackBusDetails *bus_details_ptr = playback->bus_details.load();
+ ERR_FAIL_NULL(bus_details_ptr);
+ // Make a copy of the bus details so we can modify it without worrying about other threads.
+ AudioStreamPlaybackBusDetails bus_details = *bus_details_ptr;
// Mix to any active buses.
for (int idx = 0; idx < MAX_BUSES_PER_PLAYBACK; idx++) {
if (!bus_details.bus_active[idx]) {
continue;
}
+ // This is the AudioServer-internal index of the bus we're mixing to in this step of the loop. Not to be confused with `idx` which is an index into `AudioStreamPlaybackBusDetails` member var arrays.
int bus_idx = thread_find_bus_index(bus_details.bus[idx]);
+ // It's important to know whether or not this bus was active in the previous mix step of this stream. If it was, we need to perform volume interpolation to avoid pops.
int prev_bus_idx = -1;
for (int search_idx = 0; search_idx < MAX_BUSES_PER_PLAYBACK; search_idx++) {
if (!playback->prev_bus_details->bus_active[search_idx]) {
continue;
}
+ // If the StringNames of the buses match, we've found the previous bus index. This indicates that this playback mixed to `prev_bus_details->bus[prev_bus_index]` in the previous mix step, which gives us a way to look up the playback's previous volume.
if (playback->prev_bus_details->bus[search_idx].hash() == bus_details.bus[idx].hash()) {
prev_bus_idx = search_idx;
+ break;
}
}
+ // It's now time to mix to the bus. We do this by going through each channel of the bus and mixing to it.
+ // The channels correspond to output channels of the audio device, e.g. stereo or 5.1. To reduce needless nesting, this is done with a helper method named `_mix_step_for_channel`.
for (int channel_idx = 0; channel_idx < channel_count; channel_idx++) {
AudioFrame *channel_buf = thread_get_channel_mix_buffer(bus_idx, channel_idx);
+ // TODO: This `fading_out` check could be replaced with with an exponential fadeout of the samples from the lookahead buffer for more punchy results.
if (fading_out) {
bus_details.volume[idx][channel_idx] = AudioFrame(0, 0);
}
AudioFrame channel_vol = bus_details.volume[idx][channel_idx];
- AudioFrame prev_channel_vol = AudioFrame(0, 0);
+ // If this bus was not active in the previous mix step, we want to start playback at the full volume to avoid crushing transients.
+ AudioFrame prev_channel_vol = channel_vol;
+ // If this bus was active in the previous mix step, we need to interpolate between the previous volume and the current volume to avoid pops. Set `prev_channel_volume` accordingly.
if (prev_bus_idx != -1) {
prev_channel_vol = playback->prev_bus_details->volume[prev_bus_idx][channel_idx];
}
@@ -480,7 +498,7 @@ void AudioServer::_mix_step() {
for (int channel_idx = 0; channel_idx < channel_count; channel_idx++) {
AudioFrame *channel_buf = thread_get_channel_mix_buffer(bus_idx, channel_idx);
AudioFrame prev_channel_vol = playback->prev_bus_details->volume[idx][channel_idx];
- // Fade out to silence
+ // Fade out to silence. This could be replaced with an exponential fadeout of the samples from the lookahead buffer for more punchy results.
_mix_step_for_channel(channel_buf, buf, prev_channel_vol, AudioFrame(0, 0), playback->attenuation_filter_cutoff_hz.get(), playback->highshelf_gain.get(), &playback->filter_process[channel_idx * 2], &playback->filter_process[channel_idx * 2 + 1]);
}
}
@@ -501,15 +519,12 @@ void AudioServer::_mix_step() {
switch (playback->state.load()) {
case AudioStreamPlaybackListNode::AWAITING_DELETION:
case AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION:
+ // Remove the playback from the list.
_delete_stream_playback_list_node(playback);
break;
case AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE: {
// Pause the stream.
- AudioStreamPlaybackListNode::PlaybackState old_state, new_state;
- do {
- old_state = playback->state.load();
- new_state = AudioStreamPlaybackListNode::PAUSED;
- } while (!playback->state.compare_exchange_strong(/* expected= */ old_state, new_state));
+ playback->state.store(AudioStreamPlaybackListNode::PAUSED);
} break;
case AudioStreamPlaybackListNode::PLAYING:
case AudioStreamPlaybackListNode::PAUSED:
@@ -518,13 +533,13 @@ void AudioServer::_mix_step() {
}
}
+ // Now that all of the buses have their audio sources mixed into them, we can process the effects and bus sends.
for (int i = buses.size() - 1; i >= 0; i--) {
- //go bus by bus
Bus *bus = buses[i];
for (int k = 0; k < bus->channels.size(); k++) {
if (bus->channels[k].active && !bus->channels[k].used) {
- //buffer was not used, but it's still active, so it must be cleaned
+ // Buffer was not used, but it's still active, so it must be cleaned.
AudioFrame *buf = bus->channels.write[k].buffer.ptrw();
for (uint32_t j = 0; j < buffer_size; j++) {
@@ -533,7 +548,7 @@ void AudioServer::_mix_step() {
}
}
- //process effects
+ // Process effects.
if (!bus->bypass) {
for (int j = 0; j < bus->effects.size(); j++) {
if (!bus->effects[j].enabled) {
@@ -551,7 +566,7 @@ void AudioServer::_mix_step() {
bus->channels.write[k].effect_instances.write[j]->process(bus->channels[k].buffer.ptr(), temp_buffer.write[k].ptrw(), buffer_size);
}
- //swap buffers, so internal buffer always has the right data
+ // Swap buffers, so internal buffer always has the right data.
for (int k = 0; k < bus->channels.size(); k++) {
if (!(buses[i]->channels[k].active || bus->channels[k].effect_instances[j]->process_silence())) {
continue;
@@ -565,17 +580,17 @@ void AudioServer::_mix_step() {
}
}
- //process send
+ // Process send.
Bus *send = nullptr;
if (i > 0) {
- //everything has a send save for master bus
+ // Everything has a send except for the master bus.
if (!bus_map.has(bus->send)) {
send = buses[0];
} else {
send = bus_map[bus->send];
- if (send->index_cache >= bus->index_cache) { //invalid, send to master
+ if (send->index_cache >= bus->index_cache) { // Invalid, send to master.
send = buses[0];
}
}
@@ -603,7 +618,7 @@ void AudioServer::_mix_step() {
}
}
- //apply volume and compute peak
+ // Apply volume and compute peak.
for (uint32_t j = 0; j < buffer_size; j++) {
buf[j] *= volume;
@@ -620,7 +635,7 @@ void AudioServer::_mix_step() {
bus->channels.write[k].peak_volume = AudioFrame(Math::linear_to_db(peak.left + AUDIO_PEAK_OFFSET), Math::linear_to_db(peak.right + AUDIO_PEAK_OFFSET));
if (!bus->channels[k].used) {
- //see if any audio is contained, because channel was not used
+ // See if any audio is contained, because channel was not used.
if (MAX(peak.right, peak.left) > Math::db_to_linear(channel_disable_threshold_db)) {
bus->channels.write[k].last_mix_with_audio = mix_frames;
@@ -631,7 +646,7 @@ void AudioServer::_mix_step() {
}
if (send) {
- //if not master bus, send
+ // If not master bus, send.
AudioFrame *target_buf = thread_get_channel_mix_buffer(send->index_cache, k);
for (uint32_t j = 0; j < buffer_size; j++) {
@@ -646,6 +661,7 @@ void AudioServer::_mix_step() {
}
void AudioServer::_mix_step_for_channel(AudioFrame *p_out_buf, AudioFrame *p_source_buf, AudioFrame p_vol_start, AudioFrame p_vol_final, float p_attenuation_filter_cutoff_hz, float p_highshelf_gain, AudioFilterSW::Processor *p_processor_l, AudioFilterSW::Processor *p_processor_r) {
+ // TODO: In the future it could be nice to replace all of these hardcoded effects with something a bit cleaner and more flexible, but for now this is what we do to support 3D audio players.
if (p_highshelf_gain != 0) {
AudioFilterSW filter;
filter.set_mode(AudioFilterSW::HIGHSHELF);
@@ -665,7 +681,7 @@ void AudioServer::_mix_step_for_channel(AudioFrame *p_out_buf, AudioFrame *p_sou
p_processor_r->update_coeffs(buffer_size);
for (unsigned int frame_idx = 0; frame_idx < buffer_size; frame_idx++) {
- // Make this buffer size invariant if buffer_size ever becomes a project setting.
+ // TODO: Make lerp speed buffer-size-invariant if buffer_size ever becomes a project setting to avoid very small buffer sizes causing pops due to too-fast lerps.
float lerp_param = (float)frame_idx / buffer_size;
AudioFrame vol = p_vol_final * lerp_param + (1 - lerp_param) * p_vol_start;
AudioFrame mixed = vol * p_source_buf[frame_idx];
@@ -676,7 +692,7 @@ void AudioServer::_mix_step_for_channel(AudioFrame *p_out_buf, AudioFrame *p_sou
} else {
for (unsigned int frame_idx = 0; frame_idx < buffer_size; frame_idx++) {
- // Make this buffer size invariant if buffer_size ever becomes a project setting.
+ // TODO: Make lerp speed buffer-size-invariant if buffer_size ever becomes a project setting to avoid very small buffer sizes causing pops due to too-fast lerps.
float lerp_param = (float)frame_idx / buffer_size;
p_out_buf[frame_idx] += (p_vol_final * lerp_param + (1 - lerp_param) * p_vol_start) * p_source_buf[frame_idx];
}
@@ -701,6 +717,7 @@ void AudioServer::_delete_stream_playback(Ref<AudioStreamPlayback> p_playback) {
}
void AudioServer::_delete_stream_playback_list_node(AudioStreamPlaybackListNode *p_playback_node) {
+ // Remove the playback from the list, registering a destructor to be run on the main thread.
playback_list.erase(p_playback_node, [](AudioStreamPlaybackListNode *p) {
delete p->prev_bus_details;
delete p->bus_details.load();
@@ -1471,7 +1488,9 @@ void AudioServer::init_channels_and_buffers() {
void AudioServer::init() {
channel_disable_threshold_db = GLOBAL_DEF_RST("audio/buses/channel_disable_threshold_db", -60.0);
channel_disable_frames = float(GLOBAL_DEF_RST(PropertyInfo(Variant::FLOAT, "audio/buses/channel_disable_time", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater"), 2.0)) * get_mix_rate();
- buffer_size = 512; //hardcoded for now
+ // TODO: Buffer size is hardcoded for now. This would be really nice to have as a project setting because currently it limits audio latency to an absolute minimum of 11ms with default mix rate, but there's some additional work required to make that happen. See TODOs in `_mix_step_for_channel`.
+ // When this becomes a project setting, it should be specified in milliseconds rather than raw sample count, because 512 samples at 192khz is shorter than it is at 48khz, for example.
+ buffer_size = 512;
init_channels_and_buffers();
diff --git a/servers/audio_server.h b/servers/audio_server.h
index 8978ad438e..9a80882dd7 100644
--- a/servers/audio_server.h
+++ b/servers/audio_server.h
@@ -271,6 +271,14 @@ private:
};
struct AudioStreamPlaybackListNode {
+ // The state machine for audio stream playbacks is as follows:
+ // 1. The playback is created and added to the playback list in the playing state.
+ // 2. The playback is (maybe) paused, and the state is set to FADE_OUT_TO_PAUSE.
+ // 2.1. The playback is mixed after being paused, and the audio server thread atomically sets the state to PAUSED after performing a brief fade-out.
+ // 3. The playback is (maybe) deleted, and the state is set to FADE_OUT_TO_DELETION.
+ // 3.1. The playback is mixed after being deleted, and the audio server thread atomically sets the state to AWAITING_DELETION after performing a brief fade-out.
+ // NOTE: The playback is not deallocated at this time because allocation and deallocation are not realtime-safe.
+ // 4. The playback is removed and deallocated on the main thread using the SafeList maybe_cleanup method.
enum PlaybackState {
PAUSED = 0, // Paused. Keep this stream playback around though so it can be restarted.
PLAYING = 1, // Playing. Fading may still be necessary if volume changes!
diff --git a/servers/camera/camera_feed.cpp b/servers/camera/camera_feed.cpp
index 4021d9564b..e93ab3a544 100644
--- a/servers/camera/camera_feed.cpp
+++ b/servers/camera/camera_feed.cpp
@@ -50,6 +50,8 @@ void CameraFeed::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_rgb_image", "rgb_image"), &CameraFeed::set_rgb_image);
ClassDB::bind_method(D_METHOD("set_ycbcr_image", "ycbcr_image"), &CameraFeed::set_ycbcr_image);
+ ClassDB::bind_method(D_METHOD("set_external", "width", "height"), &CameraFeed::set_external);
+ ClassDB::bind_method(D_METHOD("get_texture_tex_id", "feed_image_type"), &CameraFeed::get_texture_tex_id);
ClassDB::bind_method(D_METHOD("get_datatype"), &CameraFeed::get_datatype);
@@ -68,6 +70,7 @@ void CameraFeed::_bind_methods() {
BIND_ENUM_CONSTANT(FEED_RGB);
BIND_ENUM_CONSTANT(FEED_YCBCR);
BIND_ENUM_CONSTANT(FEED_YCBCR_SEP);
+ BIND_ENUM_CONSTANT(FEED_EXTERNAL);
BIND_ENUM_CONSTANT(FEED_UNSPECIFIED);
BIND_ENUM_CONSTANT(FEED_FRONT);
@@ -137,6 +140,10 @@ RID CameraFeed::get_texture(CameraServer::FeedImage p_which) {
return texture[p_which];
}
+uint64_t CameraFeed::get_texture_tex_id(CameraServer::FeedImage p_which) {
+ return RenderingServer::get_singleton()->texture_get_native_handle(texture[p_which]);
+}
+
CameraFeed::CameraFeed() {
// initialize our feed
id = CameraServer::get_singleton()->get_free_id();
@@ -252,6 +259,19 @@ void CameraFeed::set_ycbcr_images(const Ref<Image> &p_y_img, const Ref<Image> &p
}
}
+void CameraFeed::set_external(int p_width, int p_height) {
+ if ((base_width != p_width) || (base_height != p_height)) {
+ // We're assuming here that our camera image doesn't change around formats etc, allocate the whole lot...
+ base_width = p_width;
+ base_height = p_height;
+
+ auto new_texture = RenderingServer::get_singleton()->texture_external_create(p_width, p_height, 0);
+ RenderingServer::get_singleton()->texture_replace(texture[CameraServer::FEED_YCBCR_IMAGE], new_texture);
+ }
+
+ datatype = CameraFeed::FEED_EXTERNAL;
+}
+
bool CameraFeed::activate_feed() {
// nothing to do here
return true;
diff --git a/servers/camera/camera_feed.h b/servers/camera/camera_feed.h
index 492a909239..365ed7c635 100644
--- a/servers/camera/camera_feed.h
+++ b/servers/camera/camera_feed.h
@@ -49,7 +49,8 @@ public:
FEED_NOIMAGE, // we don't have an image yet
FEED_RGB, // our texture will contain a normal RGB texture that can be used directly
FEED_YCBCR, // our texture will contain a YCbCr texture that needs to be converted to RGB before output
- FEED_YCBCR_SEP // our camera is split into two textures, first plane contains Y data, second plane contains CbCr data
+ FEED_YCBCR_SEP, // our camera is split into two textures, first plane contains Y data, second plane contains CbCr data
+ FEED_EXTERNAL, // specific for android atm, camera feed is managed externally, assumed RGB for now
};
enum FeedPosition {
@@ -104,6 +105,7 @@ public:
void set_transform(const Transform2D &p_transform);
RID get_texture(CameraServer::FeedImage p_which);
+ uint64_t get_texture_tex_id(CameraServer::FeedImage p_which);
CameraFeed();
CameraFeed(String p_name, FeedPosition p_position = CameraFeed::FEED_UNSPECIFIED);
@@ -113,6 +115,7 @@ public:
void set_rgb_image(const Ref<Image> &p_rgb_img);
void set_ycbcr_image(const Ref<Image> &p_ycbcr_img);
void set_ycbcr_images(const Ref<Image> &p_y_img, const Ref<Image> &p_cbcr_img);
+ void set_external(int p_width, int p_height);
virtual bool set_format(int p_index, const Dictionary &p_parameters);
virtual Array get_formats() const;
diff --git a/servers/display_server.h b/servers/display_server.h
index f983145193..916c006f01 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -361,13 +361,6 @@ public:
virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW);
virtual ScreenOrientation screen_get_orientation(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
- // Note: The "internal" current orientation is not necessarily the current orientation and will often be 0 for most platforms.
- //
- // Some Android GPUs come with a HW-based rotator which means the screen gets rotated for free to
- // whatever orientation the device is currently facing. But many Android GPUs emulate it via SW instead,
- // which costs performance and power. This value is an optimization that tells Godot's compositor how to
- // rotate the render texture before presenting to screen so that Android's compositor doesn't have to.
- virtual int screen_get_internal_current_rotation(int p_screen = SCREEN_OF_MAIN_WINDOW) const { return 0; }
virtual void screen_set_keep_on(bool p_enable); //disable screensaver
virtual bool screen_is_kept_on() const;
diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp
index 701b4da8f8..d01976acf2 100644
--- a/servers/rendering/renderer_canvas_cull.cpp
+++ b/servers/rendering/renderer_canvas_cull.cpp
@@ -612,8 +612,9 @@ void RendererCanvasCull::canvas_item_set_visibility_layer(RID p_item, uint32_t p
uint32_t RendererCanvasCull::canvas_item_get_visibility_layer(RID p_item) {
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
- if (!canvas_item)
+ if (!canvas_item) {
return 0;
+ }
return canvas_item->visibility_layer;
}
diff --git a/servers/rendering/renderer_rd/effects/copy_effects.cpp b/servers/rendering/renderer_rd/effects/copy_effects.cpp
index c7a7532d76..808c82f712 100644
--- a/servers/rendering/renderer_rd/effects/copy_effects.cpp
+++ b/servers/rendering/renderer_rd/effects/copy_effects.cpp
@@ -1002,15 +1002,19 @@ void CopyEffects::copy_cubemap_to_dp(RID p_source_rd_texture, RID p_dst_framebuf
MaterialStorage *material_storage = MaterialStorage::get_singleton();
ERR_FAIL_NULL(material_storage);
+ Rect2i screen_rect;
+ float atlas_width = p_dst_size.width / p_rect.size.width;
+ float atlas_height = p_dst_size.height / p_rect.size.height;
+ screen_rect.position.x = (int32_t)(Math::round(p_rect.position.x * atlas_width));
+ screen_rect.position.y = (int32_t)(Math::round(p_rect.position.y * atlas_height));
+ screen_rect.size.width = (int32_t)(Math::round(p_dst_size.width));
+ screen_rect.size.height = (int32_t)(Math::round(p_dst_size.height));
+
CopyToDPPushConstant push_constant;
- push_constant.screen_rect[0] = p_rect.position.x;
- push_constant.screen_rect[1] = p_rect.position.y;
- push_constant.screen_rect[2] = p_rect.size.width;
- push_constant.screen_rect[3] = p_rect.size.height;
push_constant.z_far = p_z_far;
push_constant.z_near = p_z_near;
- push_constant.texel_size[0] = 1.0f / p_dst_size.x;
- push_constant.texel_size[1] = 1.0f / p_dst_size.y;
+ push_constant.texel_size[0] = 1.0f / p_dst_size.width;
+ push_constant.texel_size[1] = 1.0f / p_dst_size.height;
push_constant.texel_size[0] *= p_dp_flip ? -1.0f : 1.0f; // Encode dp flip as x size sign
// setup our uniforms
@@ -1021,7 +1025,7 @@ void CopyEffects::copy_cubemap_to_dp(RID p_source_rd_texture, RID p_dst_framebuf
RID shader = cube_to_dp.shader.version_get_shader(cube_to_dp.shader_version, 0);
ERR_FAIL_COND(shader.is_null());
- RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(p_dst_framebuffer, RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE);
+ RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(p_dst_framebuffer, RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, Vector<Color>(), 1.0f, 0, screen_rect);
RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, cube_to_dp.pipeline.get_render_pipeline(RD::INVALID_ID, RD::get_singleton()->framebuffer_get_format(p_dst_framebuffer)));
RD::get_singleton()->draw_list_bind_uniform_set(draw_list, uniform_set_cache->get_cache(shader, 0, u_source_rd_texture), 0);
RD::get_singleton()->draw_list_bind_index_array(draw_list, material_storage->get_quad_index_array());
diff --git a/servers/rendering/renderer_rd/effects/copy_effects.h b/servers/rendering/renderer_rd/effects/copy_effects.h
index 014f78e2b9..b6fc95d0f1 100644
--- a/servers/rendering/renderer_rd/effects/copy_effects.h
+++ b/servers/rendering/renderer_rd/effects/copy_effects.h
@@ -217,7 +217,6 @@ private:
float z_far;
float z_near;
float texel_size[2];
- float screen_rect[4];
};
struct CopyToDP {
diff --git a/servers/rendering/renderer_rd/environment/gi.cpp b/servers/rendering/renderer_rd/environment/gi.cpp
index 12ff28d7b0..235aa9f828 100644
--- a/servers/rendering/renderer_rd/environment/gi.cpp
+++ b/servers/rendering/renderer_rd/environment/gi.cpp
@@ -350,7 +350,7 @@ bool GI::voxel_gi_is_using_two_bounces(RID p_voxel_gi) const {
bool GI::voxel_gi_is_interior(RID p_voxel_gi) const {
VoxelGI *voxel_gi = voxel_gi_owner.get_or_null(p_voxel_gi);
- ERR_FAIL_NULL_V(voxel_gi, 0);
+ ERR_FAIL_NULL_V(voxel_gi, false);
return voxel_gi->interior;
}
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 76d8972ad9..eb73a9d7e6 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -395,6 +395,10 @@ void RenderForwardClustered::_render_list_template(RenderingDevice::DrawListID p
RID xforms_uniform_set = surf->owner->transforms_uniform_set;
SceneShaderForwardClustered::ShaderSpecialization pipeline_specialization = p_params->base_specialization;
+ pipeline_specialization.multimesh = bool(surf->owner->base_flags & INSTANCE_DATA_FLAG_MULTIMESH);
+ pipeline_specialization.multimesh_format_2d = bool(surf->owner->base_flags & INSTANCE_DATA_FLAG_MULTIMESH_FORMAT_2D);
+ pipeline_specialization.multimesh_has_color = bool(surf->owner->base_flags & INSTANCE_DATA_FLAG_MULTIMESH_HAS_COLOR);
+ pipeline_specialization.multimesh_has_custom_data = bool(surf->owner->base_flags & INSTANCE_DATA_FLAG_MULTIMESH_HAS_CUSTOM_DATA);
if constexpr (p_pass_mode == PASS_MODE_COLOR) {
pipeline_specialization.use_light_soft_shadows = element_info.uses_softshadow;
@@ -1961,7 +1965,13 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
sky.setup_sky(p_render_data->environment, rb, *p_render_data->lights, p_render_data->camera_attributes, 1, &projection, &eye_offset, p_render_data->scene_data->cam_transform, projection, screen_size, Vector2(0.0f, 0.0f), this);
} else {
- sky.setup_sky(p_render_data->environment, rb, *p_render_data->lights, p_render_data->camera_attributes, p_render_data->scene_data->view_count, p_render_data->scene_data->view_projection, p_render_data->scene_data->view_eye_offset, p_render_data->scene_data->cam_transform, p_render_data->scene_data->cam_projection, screen_size, p_render_data->scene_data->taa_jitter, this);
+ Projection projection = p_render_data->scene_data->cam_projection;
+ if (p_render_data->scene_data->cam_frustum) {
+ // Sky is drawn upside down, the frustum offset doesn't know the image is upside down so needs a flip.
+ projection[2].y = -projection[2].y;
+ }
+
+ sky.setup_sky(p_render_data->environment, rb, *p_render_data->lights, p_render_data->camera_attributes, p_render_data->scene_data->view_count, &projection, p_render_data->scene_data->view_eye_offset, p_render_data->scene_data->cam_transform, projection, screen_size, p_render_data->scene_data->taa_jitter, this);
}
sky_energy_multiplier *= bg_energy_multiplier;
diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
index 532b5b6484..00a70e3690 100644
--- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
@@ -374,6 +374,10 @@ void SceneShaderForwardClustered::ShaderData::_create_pipeline(PipelineKey p_pip
sc.type = RD::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_INT;
specialization_constants.push_back(sc);
+ sc.constant_id = 1;
+ sc.int_value = p_pipeline_key.shader_specialization.packed_1;
+ specialization_constants.push_back(sc);
+
RID shader_rid = get_shader_variant(p_pipeline_key.version, p_pipeline_key.color_pass_flags, p_pipeline_key.ubershader);
ERR_FAIL_COND(shader_rid.is_null());
diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h
index 8d79539b3d..e1ed85779e 100644
--- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h
+++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h
@@ -117,7 +117,17 @@ public:
uint32_t packed_0;
};
- uint32_t packed_1;
+ union {
+ struct {
+ uint32_t multimesh : 1;
+ uint32_t multimesh_format_2d : 1;
+ uint32_t multimesh_has_color : 1;
+ uint32_t multimesh_has_custom_data : 1;
+ };
+
+ uint32_t packed_1;
+ };
+
uint32_t packed_2;
};
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 978ce097d3..5ad92bd211 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
@@ -911,14 +911,19 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
Color clear_color = p_default_bg_color;
bool load_color = false;
bool copy_canvas = false;
+ bool use_ambient_cubemap = false;
+ bool use_reflection_cubemap = false;
if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_OVERDRAW) {
clear_color = Color(0, 0, 0, 1); //in overdraw mode, BG should always be black
} else if (is_environment(p_render_data->environment)) {
+ RS::EnvironmentAmbientSource ambient_source = environment_get_ambient_source(p_render_data->environment);
RS::EnvironmentBG bg_mode = environment_get_background(p_render_data->environment);
float bg_energy_multiplier = environment_get_bg_energy_multiplier(p_render_data->environment);
bg_energy_multiplier *= environment_get_bg_intensity(p_render_data->environment);
RS::EnvironmentReflectionSource reflection_source = environment_get_reflection_source(p_render_data->environment);
+ use_ambient_cubemap = (ambient_source == RS::ENV_AMBIENT_SOURCE_BG && bg_mode == RS::ENV_BG_SKY) || ambient_source == RS::ENV_AMBIENT_SOURCE_SKY;
+ use_reflection_cubemap = (reflection_source == RS::ENV_REFLECTION_SOURCE_BG && bg_mode == RS::ENV_BG_SKY) || reflection_source == RS::ENV_REFLECTION_SOURCE_SKY;
if (p_render_data->camera_attributes.is_valid()) {
bg_energy_multiplier *= RSG::camera_attributes->camera_attributes_get_exposure_normalization_factor(p_render_data->camera_attributes);
@@ -963,7 +968,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
}
// setup sky if used for ambient, reflections, or background
- if (draw_sky || draw_sky_fog_only || (reflection_source == RS::ENV_REFLECTION_SOURCE_BG && bg_mode == RS::ENV_BG_SKY) || reflection_source == RS::ENV_REFLECTION_SOURCE_SKY || environment_get_ambient_source(p_render_data->environment) == RS::ENV_AMBIENT_SOURCE_SKY) {
+ if (draw_sky || draw_sky_fog_only || (reflection_source == RS::ENV_REFLECTION_SOURCE_BG && bg_mode == RS::ENV_BG_SKY) || reflection_source == RS::ENV_REFLECTION_SOURCE_SKY || ambient_source == RS::ENV_AMBIENT_SOURCE_SKY) {
RENDER_TIMESTAMP("Setup Sky");
RD::get_singleton()->draw_command_begin_label("Setup Sky");
@@ -976,7 +981,13 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
sky.setup_sky(p_render_data->environment, p_render_data->render_buffers, *p_render_data->lights, p_render_data->camera_attributes, 1, &projection, &eye_offset, p_render_data->scene_data->cam_transform, projection, screen_size, Vector2(0.0f, 0.0f), this);
} else {
- sky.setup_sky(p_render_data->environment, p_render_data->render_buffers, *p_render_data->lights, p_render_data->camera_attributes, p_render_data->scene_data->view_count, p_render_data->scene_data->view_projection, p_render_data->scene_data->view_eye_offset, p_render_data->scene_data->cam_transform, p_render_data->scene_data->cam_projection, screen_size, p_render_data->scene_data->taa_jitter, this);
+ Projection projection = p_render_data->scene_data->cam_projection;
+ if (p_render_data->scene_data->cam_frustum) {
+ // Sky is drawn upside down, the frustum offset doesn't know the image is upside down so needs a flip.
+ projection[2].y = -projection[2].y;
+ }
+
+ sky.setup_sky(p_render_data->environment, p_render_data->render_buffers, *p_render_data->lights, p_render_data->camera_attributes, p_render_data->scene_data->view_count, &projection, p_render_data->scene_data->view_eye_offset, p_render_data->scene_data->cam_transform, projection, screen_size, p_render_data->scene_data->taa_jitter, this);
}
sky_energy_multiplier *= bg_energy_multiplier;
@@ -1008,13 +1019,8 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
SceneShaderForwardMobile::ShaderSpecialization base_specialization = scene_shader.default_specialization;
{
- //figure out spec constants
-
- if (p_render_data->directional_light_count > 0) {
- base_specialization.use_directional_soft_shadows = p_render_data->directional_light_soft_shadows;
- } else {
- base_specialization.disable_directional_lights = true;
- }
+ base_specialization.use_directional_soft_shadows = p_render_data->directional_light_count > 0 ? p_render_data->directional_light_soft_shadows : false;
+ base_specialization.directional_lights = p_render_data->directional_light_count;
if (!is_environment(p_render_data->environment) || !environment_get_fog_enabled(p_render_data->environment)) {
base_specialization.disable_fog = true;
@@ -1023,6 +1029,10 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
if (p_render_data->environment.is_valid() && environment_get_fog_mode(p_render_data->environment) == RS::EnvironmentFogMode::ENV_FOG_MODE_DEPTH) {
base_specialization.use_depth_fog = true;
}
+
+ base_specialization.scene_use_ambient_cubemap = use_ambient_cubemap;
+ base_specialization.scene_use_reflection_cubemap = use_reflection_cubemap;
+ base_specialization.scene_roughness_limiter_enabled = p_render_data->render_buffers.is_valid() && screen_space_roughness_limiter_is_active();
}
{
@@ -2144,7 +2154,10 @@ void RenderForwardMobile::_render_list_template(RenderingDevice::DrawListID p_dr
}
SceneShaderForwardMobile::ShaderSpecialization pipeline_specialization = p_params->base_specialization;
- pipeline_specialization.is_multimesh = bool(inst->flags_cache & INSTANCE_DATA_FLAG_MULTIMESH);
+ pipeline_specialization.multimesh = bool(inst->flags_cache & INSTANCE_DATA_FLAG_MULTIMESH);
+ pipeline_specialization.multimesh_format_2d = bool(inst->flags_cache & INSTANCE_DATA_FLAG_MULTIMESH_FORMAT_2D);
+ pipeline_specialization.multimesh_has_color = bool(inst->flags_cache & INSTANCE_DATA_FLAG_MULTIMESH_HAS_COLOR);
+ pipeline_specialization.multimesh_has_custom_data = bool(inst->flags_cache & INSTANCE_DATA_FLAG_MULTIMESH_HAS_CUSTOM_DATA);
SceneState::PushConstant push_constant;
push_constant.base_index = i + p_params->element_offset;
@@ -2165,10 +2178,10 @@ void RenderForwardMobile::_render_list_template(RenderingDevice::DrawListID p_dr
} else {
pipeline_specialization.use_light_projector = inst->use_projector;
pipeline_specialization.use_light_soft_shadows = inst->use_soft_shadow;
- pipeline_specialization.disable_omni_lights = inst->omni_light_count == 0;
- pipeline_specialization.disable_spot_lights = inst->spot_light_count == 0;
- pipeline_specialization.disable_reflection_probes = inst->reflection_probe_count == 0;
- pipeline_specialization.disable_decals = inst->decals_count == 0;
+ pipeline_specialization.omni_lights = inst->omni_light_count;
+ pipeline_specialization.spot_lights = inst->spot_light_count;
+ pipeline_specialization.reflection_probes = inst->reflection_probe_count;
+ pipeline_specialization.decals = inst->decals_count;
#ifdef DEBUG_ENABLED
if (unlikely(get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_LIGHTING)) {
diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
index 6323d7dd6a..4525b50b6e 100644
--- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
@@ -237,6 +237,7 @@ void SceneShaderForwardMobile::ShaderData::_create_pipeline(PipelineKey p_pipeli
"VERSION:", p_pipeline_key.version,
"SPEC PACKED #0:", p_pipeline_key.shader_specialization.packed_0,
"SPEC PACKED #1:", p_pipeline_key.shader_specialization.packed_1,
+ "SPEC PACKED #2:", p_pipeline_key.shader_specialization.packed_2,
"RENDER PASS:", p_pipeline_key.render_pass,
"WIREFRAME:", p_pipeline_key.wireframe);
#endif
diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h
index a27da6c72d..f0afeebe79 100644
--- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h
+++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h
@@ -65,25 +65,35 @@ public:
uint32_t use_directional_soft_shadows : 1;
uint32_t decal_use_mipmaps : 1;
uint32_t projector_use_mipmaps : 1;
- uint32_t disable_omni_lights : 1;
- uint32_t disable_spot_lights : 1;
- uint32_t disable_reflection_probes : 1;
- uint32_t disable_directional_lights : 1;
- uint32_t disable_decals : 1;
uint32_t disable_fog : 1;
uint32_t use_depth_fog : 1;
- uint32_t is_multimesh : 1;
uint32_t use_lightmap_bicubic_filter : 1;
+ uint32_t multimesh : 1;
+ uint32_t multimesh_format_2d : 1;
+ uint32_t multimesh_has_color : 1;
+ uint32_t multimesh_has_custom_data : 1;
+ uint32_t scene_use_ambient_cubemap : 1;
+ uint32_t scene_use_reflection_cubemap : 1;
+ uint32_t scene_roughness_limiter_enabled : 1;
+ uint32_t padding : 5;
uint32_t soft_shadow_samples : 6;
uint32_t penumbra_shadow_samples : 6;
- uint32_t directional_soft_shadow_samples : 6;
};
uint32_t packed_0;
};
union {
- uint32_t directional_penumbra_shadow_samples : 6;
+ struct {
+ uint32_t directional_soft_shadow_samples : 6;
+ uint32_t directional_penumbra_shadow_samples : 6;
+ uint32_t omni_lights : 4;
+ uint32_t spot_lights : 4;
+ uint32_t reflection_probes : 4;
+ uint32_t directional_lights : 4;
+ uint32_t decals : 4;
+ };
+
uint32_t packed_1;
};
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index c4a732fef1..0dcdb90948 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -993,36 +993,43 @@ void RendererCanvasRenderRD::light_update_shadow(RID p_rid, int p_shadow_index,
Vector<Color> cc;
cc.push_back(Color(p_far, p_far, p_far, 1.0));
- for (int i = 0; i < 4; i++) {
- //make sure it remains orthogonal, makes easy to read angle later
+ Projection projection;
+ {
+ real_t fov = 90;
+ real_t nearp = p_near;
+ real_t farp = p_far;
+ real_t aspect = 1.0;
- //light.basis.scale(Vector3(to_light.elements[0].length(),to_light.elements[1].length(),1));
+ real_t ymax = nearp * Math::tan(Math::deg_to_rad(fov * 0.5));
+ real_t ymin = -ymax;
+ real_t xmin = ymin * aspect;
+ real_t xmax = ymax * aspect;
- Rect2i rect((state.shadow_texture_size / 4) * i, p_shadow_index * 2, (state.shadow_texture_size / 4), 2);
- RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(state.shadow_fb, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, cc, 1.0, 0, rect);
+ projection.set_frustum(xmin, xmax, ymin, ymax, nearp, farp);
+ }
- Projection projection;
- {
- real_t fov = 90;
- real_t nearp = p_near;
- real_t farp = p_far;
- real_t aspect = 1.0;
+ // Precomputed:
+ // Vector3 cam_target = Basis::from_euler(Vector3(0, 0, Math_TAU * ((i + 3) / 4.0))).xform(Vector3(0, 1, 0));
+ // projection = projection * Projection(Transform3D().looking_at(cam_targets[i], Vector3(0, 0, -1)).affine_inverse());
+ const Projection projections[4] = {
+ projection * Projection(Vector4(0, 0, -1, 0), Vector4(1, 0, 0, 0), Vector4(0, -1, 0, 0), Vector4(0, 0, 0, 1)),
- real_t ymax = nearp * Math::tan(Math::deg_to_rad(fov * 0.5));
- real_t ymin = -ymax;
- real_t xmin = ymin * aspect;
- real_t xmax = ymax * aspect;
+ projection * Projection(Vector4(-1, 0, 0, 0), Vector4(0, 0, -1, 0), Vector4(0, -1, 0, 0), Vector4(0, 0, 0, 1)),
- projection.set_frustum(xmin, xmax, ymin, ymax, nearp, farp);
- }
+ projection * Projection(Vector4(0, 0, 1, 0), Vector4(-1, 0, 0, 0), Vector4(0, -1, 0, 0), Vector4(0, 0, 0, 1)),
+
+ projection * Projection(Vector4(1, 0, 0, 0), Vector4(0, 0, 1, 0), Vector4(0, -1, 0, 0), Vector4(0, 0, 0, 1))
+
+ };
- Vector3 cam_target = Basis::from_euler(Vector3(0, 0, Math_TAU * ((i + 3) / 4.0))).xform(Vector3(0, 1, 0));
- projection = projection * Projection(Transform3D().looking_at(cam_target, Vector3(0, 0, -1)).affine_inverse());
+ for (int i = 0; i < 4; i++) {
+ Rect2i rect((state.shadow_texture_size / 4) * i, p_shadow_index * 2, (state.shadow_texture_size / 4), 2);
+ RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(state.shadow_fb, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, cc, 1.0, 0, rect);
ShadowRenderPushConstant push_constant;
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
- push_constant.projection[y * 4 + x] = projection.columns[y][x];
+ push_constant.projection[y * 4 + x] = projections[i].columns[y][x];
}
}
static const Vector2 directions[4] = { Vector2(1, 0), Vector2(0, 1), Vector2(-1, 0), Vector2(0, -1) };
diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
index d04285fbb4..ba47508700 100644
--- a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
@@ -67,7 +67,7 @@ void RendererCompositorRD::blit_render_targets_to_screen(DisplayServer::WindowID
RD::get_singleton()->draw_list_bind_uniform_set(draw_list, render_target_descriptors[rd_texture], 0);
// We need to invert the phone rotation.
- int screen_rotation_degrees = -DisplayServer::get_singleton()->screen_get_internal_current_rotation();
+ const int screen_rotation_degrees = -RD::get_singleton()->screen_get_pre_rotation_degrees(p_screen);
float screen_rotation = Math::deg_to_rad((float)screen_rotation_degrees);
blit.push_constant.rotation_cos = Math::cos(screen_rotation);
@@ -238,7 +238,7 @@ void RendererCompositorRD::set_boot_image(const Ref<Image> &p_image, const Color
RD::get_singleton()->draw_list_bind_index_array(draw_list, blit.array);
RD::get_singleton()->draw_list_bind_uniform_set(draw_list, uset, 0);
- int screen_rotation_degrees = DisplayServer::get_singleton()->screen_get_internal_current_rotation();
+ const int screen_rotation_degrees = -RD::get_singleton()->screen_get_pre_rotation_degrees(DisplayServer::MAIN_WINDOW_ID);
float screen_rotation = Math::deg_to_rad((float)screen_rotation_degrees);
blit.push_constant.rotation_cos = Math::cos(screen_rotation);
blit.push_constant.rotation_sin = Math::sin(screen_rotation);
diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
index dc2605b670..4417f6832c 100644
--- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
@@ -1130,6 +1130,7 @@ void RendererSceneRenderRD::render_scene(const Ref<RenderSceneBuffers> &p_render
scene_data.cam_transform = p_camera_data->main_transform;
scene_data.cam_projection = p_camera_data->main_projection;
scene_data.cam_orthogonal = p_camera_data->is_orthogonal;
+ scene_data.cam_frustum = p_camera_data->is_frustum;
scene_data.camera_visible_layers = p_camera_data->visible_layers;
scene_data.taa_jitter = p_camera_data->taa_jitter;
scene_data.taa_frame_count = p_camera_data->taa_frame_count;
diff --git a/servers/rendering/renderer_rd/shaders/effects/cube_to_dp.glsl b/servers/rendering/renderer_rd/shaders/effects/cube_to_dp.glsl
index 3fb93dda35..b45e310b61 100644
--- a/servers/rendering/renderer_rd/shaders/effects/cube_to_dp.glsl
+++ b/servers/rendering/renderer_rd/shaders/effects/cube_to_dp.glsl
@@ -8,7 +8,6 @@ layout(push_constant, std430) uniform Params {
float z_far;
float z_near;
vec2 texel_size;
- vec4 screen_rect;
}
params;
@@ -17,8 +16,7 @@ layout(location = 0) out vec2 uv_interp;
void main() {
vec2 base_arr[4] = vec2[](vec2(0.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 1.0), vec2(1.0, 0.0));
uv_interp = base_arr[gl_VertexIndex];
- vec2 screen_pos = uv_interp * params.screen_rect.zw + params.screen_rect.xy;
- gl_Position = vec4(screen_pos * 2.0 - 1.0, 0.0, 1.0);
+ gl_Position = vec4(uv_interp * 2.0 - 1.0, 0.0, 1.0);
}
#[fragment]
@@ -35,7 +33,6 @@ layout(push_constant, std430) uniform Params {
float z_far;
float z_near;
vec2 texel_size;
- vec4 screen_rect;
}
params;
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 72236dcd9f..81d3d87a22 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
@@ -211,6 +211,13 @@ vec3 double_add_vec3(vec3 base_a, vec3 prec_a, vec3 base_b, vec3 prec_b, out vec
}
#endif
+uint multimesh_stride() {
+ uint stride = sc_multimesh_format_2d() ? 2 : 3;
+ stride += sc_multimesh_has_color() ? 1 : 0;
+ stride += sc_multimesh_has_custom_data() ? 1 : 0;
+ return stride;
+}
+
void vertex_shader(vec3 vertex_input,
#ifdef NORMAL_USED
in vec3 normal_input,
@@ -219,7 +226,7 @@ void vertex_shader(vec3 vertex_input,
in vec3 tangent_input,
in vec3 binormal_input,
#endif
- in uint instance_index, in bool is_multimesh, in uint multimesh_offset, in SceneData scene_data, in mat4 model_matrix, out vec4 screen_pos) {
+ in uint instance_index, in uint multimesh_offset, in SceneData scene_data, in mat4 model_matrix, out vec4 screen_pos) {
vec4 instance_custom = vec4(0.0);
#if defined(COLOR_USED)
color_interp = color_attrib;
@@ -248,7 +255,7 @@ void vertex_shader(vec3 vertex_input,
mat4 matrix;
mat4 read_model_matrix = model_matrix;
- if (is_multimesh) {
+ if (sc_multimesh()) {
//multimesh, instances are for it
#ifdef USE_PARTICLE_TRAILS
@@ -296,25 +303,10 @@ void vertex_shader(vec3 vertex_input,
#endif
#else
- uint stride = 0;
- {
- //TODO implement a small lookup table for the stride
- if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_MULTIMESH_FORMAT_2D)) {
- stride += 2;
- } else {
- stride += 3;
- }
- if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_MULTIMESH_HAS_COLOR)) {
- stride += 1;
- }
- if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_MULTIMESH_HAS_CUSTOM_DATA)) {
- stride += 1;
- }
- }
-
+ uint stride = multimesh_stride();
uint offset = stride * (gl_InstanceIndex + multimesh_offset);
- if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_MULTIMESH_FORMAT_2D)) {
+ if (sc_multimesh_format_2d()) {
matrix = mat4(transforms.data[offset + 0], transforms.data[offset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
offset += 2;
} else {
@@ -322,14 +314,14 @@ void vertex_shader(vec3 vertex_input,
offset += 3;
}
- if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_MULTIMESH_HAS_COLOR)) {
+ if (sc_multimesh_has_color()) {
#ifdef COLOR_USED
color_interp *= transforms.data[offset];
#endif
offset += 1;
}
- if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_MULTIMESH_HAS_CUSTOM_DATA)) {
+ if (sc_multimesh_has_custom_data()) {
instance_custom = transforms.data[offset];
}
@@ -427,7 +419,7 @@ void vertex_shader(vec3 vertex_input,
// Then we combine the translations from the model matrix and the view matrix using emulated doubles.
// We add the result to the vertex and ignore the final lost precision.
vec3 model_origin = model_matrix[3].xyz;
- if (is_multimesh) {
+ if (sc_multimesh()) {
vertex = mat3(matrix) * vertex;
model_origin = double_add_vec3(model_origin, model_precision, matrix[3].xyz, vec3(0.0), model_precision);
}
@@ -708,9 +700,7 @@ void _unpack_vertex_attributes(vec4 p_vertex_in, vec3 p_compressed_aabb_position
void main() {
uint instance_index = draw_call.instance_index;
-
- bool is_multimesh = bool(instances.data[instance_index].flags & INSTANCE_FLAGS_MULTIMESH);
- if (!is_multimesh) {
+ if (!sc_multimesh()) {
instance_index += gl_InstanceIndex;
}
@@ -753,7 +743,7 @@ void main() {
prev_tangent,
prev_binormal,
#endif
- instance_index, is_multimesh, draw_call.multimesh_motion_vectors_previous_offset, scene_data_block.prev_data, instances.data[instance_index].prev_transform, prev_screen_position);
+ instance_index, draw_call.multimesh_motion_vectors_previous_offset, scene_data_block.prev_data, instances.data[instance_index].prev_transform, prev_screen_position);
#else
// Unused output.
vec4 screen_position;
@@ -792,7 +782,7 @@ void main() {
tangent,
binormal,
#endif
- instance_index, is_multimesh, draw_call.multimesh_motion_vectors_current_offset, scene_data_block.data, model_matrix, screen_position);
+ instance_index, draw_call.multimesh_motion_vectors_current_offset, scene_data_block.data, model_matrix, screen_position);
}
#[fragment]
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 7bfcb2fb12..8f153f7ed5 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
@@ -59,6 +59,10 @@ uint sc_packed_0() {
return draw_call.sc_packed_0;
}
+uint sc_packed_1() {
+ return draw_call.sc_packed_1;
+}
+
uint uc_cull_mode() {
return (draw_call.uc_packed_0 >> 0) & 3U;
}
@@ -67,11 +71,16 @@ uint uc_cull_mode() {
// Pull the constants from the pipeline's specialization constants.
layout(constant_id = 0) const uint pso_sc_packed_0 = 0;
+layout(constant_id = 1) const uint pso_sc_packed_1 = 0;
uint sc_packed_0() {
return pso_sc_packed_0;
}
+uint sc_packed_1() {
+ return pso_sc_packed_1;
+}
+
#endif
bool sc_use_forward_gi() {
@@ -122,6 +131,22 @@ uint sc_directional_penumbra_shadow_samples() {
return (sc_packed_0() >> 26) & 63U;
}
+bool sc_multimesh() {
+ return ((sc_packed_1() >> 0) & 1U) != 0;
+}
+
+bool sc_multimesh_format_2d() {
+ return ((sc_packed_1() >> 1) & 1U) != 0;
+}
+
+bool sc_multimesh_has_color() {
+ return ((sc_packed_1() >> 2) & 1U) != 0;
+}
+
+bool sc_multimesh_has_custom_data() {
+ return ((sc_packed_1() >> 3) & 1U) != 0;
+}
+
float sc_luminance_multiplier() {
// Not used in clustered renderer but we share some code with the mobile renderer that requires this.
return 1.0;
@@ -144,10 +169,6 @@ layout(set = 0, binding = 2) uniform sampler shadow_sampler;
#define INSTANCE_FLAGS_USE_SH_LIGHTMAP (1 << 9)
#define INSTANCE_FLAGS_USE_VOXEL_GI (1 << 10)
#define INSTANCE_FLAGS_PARTICLES (1 << 11)
-#define INSTANCE_FLAGS_MULTIMESH (1 << 12)
-#define INSTANCE_FLAGS_MULTIMESH_FORMAT_2D (1 << 13)
-#define INSTANCE_FLAGS_MULTIMESH_HAS_COLOR (1 << 14)
-#define INSTANCE_FLAGS_MULTIMESH_HAS_CUSTOM_DATA (1 << 15)
#define INSTANCE_FLAGS_PARTICLE_TRAIL_SHIFT 16
#define INSTANCE_FLAGS_FADE_SHIFT 24
//3 bits of stride
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 2f0e4e0bea..404f658fa6 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
@@ -178,6 +178,13 @@ vec3 double_add_vec3(vec3 base_a, vec3 prec_a, vec3 base_b, vec3 prec_b, out vec
}
#endif
+uint multimesh_stride() {
+ uint stride = sc_multimesh_format_2d() ? 2 : 3;
+ stride += sc_multimesh_has_color() ? 1 : 0;
+ stride += sc_multimesh_has_custom_data() ? 1 : 0;
+ return stride;
+}
+
void main() {
vec4 instance_custom = vec4(0.0);
#if defined(COLOR_USED)
@@ -208,7 +215,7 @@ void main() {
mat4 matrix;
mat4 read_model_matrix = model_matrix;
- if (sc_is_multimesh()) {
+ if (sc_multimesh()) {
//multimesh, instances are for it
#ifdef USE_PARTICLE_TRAILS
@@ -256,25 +263,10 @@ void main() {
#endif
#else
- uint stride = 0;
- {
- //TODO implement a small lookup table for the stride
- if (bool(instances.data[draw_call.instance_index].flags & INSTANCE_FLAGS_MULTIMESH_FORMAT_2D)) {
- stride += 2;
- } else {
- stride += 3;
- }
- if (bool(instances.data[draw_call.instance_index].flags & INSTANCE_FLAGS_MULTIMESH_HAS_COLOR)) {
- stride += 1;
- }
- if (bool(instances.data[draw_call.instance_index].flags & INSTANCE_FLAGS_MULTIMESH_HAS_CUSTOM_DATA)) {
- stride += 1;
- }
- }
-
+ uint stride = multimesh_stride();
uint offset = stride * gl_InstanceIndex;
- if (bool(instances.data[draw_call.instance_index].flags & INSTANCE_FLAGS_MULTIMESH_FORMAT_2D)) {
+ if (sc_multimesh_format_2d()) {
matrix = mat4(transforms.data[offset + 0], transforms.data[offset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
offset += 2;
} else {
@@ -282,14 +274,14 @@ void main() {
offset += 3;
}
- if (bool(instances.data[draw_call.instance_index].flags & INSTANCE_FLAGS_MULTIMESH_HAS_COLOR)) {
+ if (sc_multimesh_has_color()) {
#ifdef COLOR_USED
color_interp *= transforms.data[offset];
#endif
offset += 1;
}
- if (bool(instances.data[draw_call.instance_index].flags & INSTANCE_FLAGS_MULTIMESH_HAS_CUSTOM_DATA)) {
+ if (sc_multimesh_has_custom_data()) {
instance_custom = transforms.data[offset];
}
@@ -404,7 +396,7 @@ void main() {
// Then we combine the translations from the model matrix and the view matrix using emulated doubles.
// We add the result to the vertex and ignore the final lost precision.
vec3 model_origin = model_matrix[3].xyz;
- if (sc_is_multimesh()) {
+ if (sc_multimesh()) {
vertex = mat3(matrix) * vertex;
model_origin = double_add_vec3(model_origin, model_precision, matrix[3].xyz, vec3(0.0), model_precision);
}
@@ -461,50 +453,24 @@ void main() {
diffuse_light_interp = vec4(0.0);
specular_light_interp = vec4(0.0);
- if (!sc_disable_omni_lights()) {
- uint light_indices = instances.data[draw_call.instance_index].omni_lights.x;
- for (uint i = 0; i < 8; i++) {
- uint light_index = light_indices & 0xFF;
- if (i == 3) {
- light_indices = instances.data[draw_call.instance_index].omni_lights.y;
- } else {
- light_indices = light_indices >> 8;
- }
-
- if (light_index == 0xFF) {
- break;
- }
-
- light_process_omni_vertex(light_index, vertex, view, normal, roughness,
- diffuse_light_interp.rgb, specular_light_interp.rgb);
- }
+ uvec2 omni_light_indices = instances.data[draw_call.instance_index].omni_lights;
+ for (uint i = 0; i < sc_omni_lights(); i++) {
+ uint light_index = (i > 3) ? ((omni_light_indices.y >> ((i - 4) * 8)) & 0xFF) : ((omni_light_indices.x >> (i * 8)) & 0xFF);
+ light_process_omni_vertex(light_index, vertex, view, normal, roughness, diffuse_light_interp.rgb, specular_light_interp.rgb);
}
- if (!sc_disable_spot_lights()) {
- uint light_indices = instances.data[draw_call.instance_index].spot_lights.x;
- for (uint i = 0; i < 8; i++) {
- uint light_index = light_indices & 0xFF;
- if (i == 3) {
- light_indices = instances.data[draw_call.instance_index].spot_lights.y;
- } else {
- light_indices = light_indices >> 8;
- }
-
- if (light_index == 0xFF) {
- break;
- }
-
- light_process_spot_vertex(light_index, vertex, view, normal, roughness,
- diffuse_light_interp.rgb, specular_light_interp.rgb);
- }
+ uvec2 spot_light_indices = instances.data[draw_call.instance_index].spot_lights;
+ for (uint i = 0; i < sc_spot_lights(); i++) {
+ uint light_index = (i > 3) ? ((spot_light_indices.y >> ((i - 4) * 8)) & 0xFF) : ((spot_light_indices.x >> (i * 8)) & 0xFF);
+ light_process_spot_vertex(light_index, vertex, view, normal, roughness, diffuse_light_interp.rgb, specular_light_interp.rgb);
}
- if (!sc_disable_directional_lights()) {
+ if (sc_directional_lights() > 0) {
// We process the first directional light separately as it may have shadows.
vec3 directional_diffuse = vec3(0.0);
vec3 directional_specular = vec3(0.0);
- for (uint i = 0; i < scene_data.directional_light_count; i++) {
+ for (uint i = 0; i < sc_directional_lights(); i++) {
if (!bool(directional_lights.data[i].mask & instances.data[draw_call.instance_index].layer_mask)) {
continue; // Not masked, skip.
}
@@ -823,7 +789,7 @@ vec4 fog_process(vec3 vertex) {
float sun_total = 0.0;
vec3 view = normalize(vertex);
- for (uint i = 0; i < scene_data_block.data.directional_light_count; i++) {
+ for (uint i = 0; i < sc_directional_lights(); i++) {
vec3 light_color = directional_lights.data[i].color * directional_lights.data[i].energy;
float light_amount = pow(max(dot(view, directional_lights.data[i].direction), 0.0), 8.0);
fog_color += light_color * light_amount * scene_data_block.data.fog_sun_scatter;
@@ -1110,97 +1076,83 @@ void main() {
vec3 vertex_ddx = dFdx(vertex);
vec3 vertex_ddy = dFdy(vertex);
- if (!sc_disable_decals()) { //Decals
- // must implement
-
- uint decal_indices = instances.data[draw_call.instance_index].decals.x;
- for (uint i = 0; i < 8; i++) {
- uint decal_index = decal_indices & 0xFF;
- if (i == 3) {
- decal_indices = instances.data[draw_call.instance_index].decals.y;
- } else {
- decal_indices = decal_indices >> 8;
- }
+ uvec2 decal_indices = instances.data[draw_call.instance_index].decals;
+ for (uint i = 0; i < sc_decals(); i++) {
+ uint decal_index = (i > 3) ? ((decal_indices.y >> ((i - 4) * 8)) & 0xFF) : ((decal_indices.x >> (i * 8)) & 0xFF);
+ if (!bool(decals.data[decal_index].mask & instances.data[draw_call.instance_index].layer_mask)) {
+ continue; //not masked
+ }
- if (decal_index == 0xFF) {
- break;
- }
+ vec3 uv_local = (decals.data[decal_index].xform * vec4(vertex, 1.0)).xyz;
+ if (any(lessThan(uv_local, vec3(0.0, -1.0, 0.0))) || any(greaterThan(uv_local, vec3(1.0)))) {
+ continue; //out of decal
+ }
- if (!bool(decals.data[decal_index].mask & instances.data[draw_call.instance_index].layer_mask)) {
- continue; //not masked
- }
+ float fade = pow(1.0 - (uv_local.y > 0.0 ? uv_local.y : -uv_local.y), uv_local.y > 0.0 ? decals.data[decal_index].upper_fade : decals.data[decal_index].lower_fade);
- vec3 uv_local = (decals.data[decal_index].xform * vec4(vertex, 1.0)).xyz;
- if (any(lessThan(uv_local, vec3(0.0, -1.0, 0.0))) || any(greaterThan(uv_local, vec3(1.0)))) {
- continue; //out of decal
- }
+ if (decals.data[decal_index].normal_fade > 0.0) {
+ fade *= smoothstep(decals.data[decal_index].normal_fade, 1.0, dot(normal_interp, decals.data[decal_index].normal) * 0.5 + 0.5);
+ }
- float fade = pow(1.0 - (uv_local.y > 0.0 ? uv_local.y : -uv_local.y), uv_local.y > 0.0 ? decals.data[decal_index].upper_fade : decals.data[decal_index].lower_fade);
+ //we need ddx/ddy for mipmaps, so simulate them
+ vec2 ddx = (decals.data[decal_index].xform * vec4(vertex_ddx, 0.0)).xz;
+ vec2 ddy = (decals.data[decal_index].xform * vec4(vertex_ddy, 0.0)).xz;
- if (decals.data[decal_index].normal_fade > 0.0) {
- fade *= smoothstep(decals.data[decal_index].normal_fade, 1.0, dot(normal_interp, decals.data[decal_index].normal) * 0.5 + 0.5);
+ if (decals.data[decal_index].albedo_rect != vec4(0.0)) {
+ //has albedo
+ vec4 decal_albedo;
+ if (sc_decal_use_mipmaps()) {
+ decal_albedo = textureGrad(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].albedo_rect.zw + decals.data[decal_index].albedo_rect.xy, ddx * decals.data[decal_index].albedo_rect.zw, ddy * decals.data[decal_index].albedo_rect.zw);
+ } else {
+ decal_albedo = textureLod(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].albedo_rect.zw + decals.data[decal_index].albedo_rect.xy, 0.0);
}
+ decal_albedo *= decals.data[decal_index].modulate;
+ decal_albedo.a *= fade;
+ albedo = mix(albedo, decal_albedo.rgb, decal_albedo.a * decals.data[decal_index].albedo_mix);
- //we need ddx/ddy for mipmaps, so simulate them
- vec2 ddx = (decals.data[decal_index].xform * vec4(vertex_ddx, 0.0)).xz;
- vec2 ddy = (decals.data[decal_index].xform * vec4(vertex_ddy, 0.0)).xz;
-
- if (decals.data[decal_index].albedo_rect != vec4(0.0)) {
- //has albedo
- vec4 decal_albedo;
+ if (decals.data[decal_index].normal_rect != vec4(0.0)) {
+ vec3 decal_normal;
if (sc_decal_use_mipmaps()) {
- decal_albedo = textureGrad(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].albedo_rect.zw + decals.data[decal_index].albedo_rect.xy, ddx * decals.data[decal_index].albedo_rect.zw, ddy * decals.data[decal_index].albedo_rect.zw);
+ decal_normal = textureGrad(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].normal_rect.zw + decals.data[decal_index].normal_rect.xy, ddx * decals.data[decal_index].normal_rect.zw, ddy * decals.data[decal_index].normal_rect.zw).xyz;
} else {
- decal_albedo = textureLod(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].albedo_rect.zw + decals.data[decal_index].albedo_rect.xy, 0.0);
- }
- decal_albedo *= decals.data[decal_index].modulate;
- decal_albedo.a *= fade;
- albedo = mix(albedo, decal_albedo.rgb, decal_albedo.a * decals.data[decal_index].albedo_mix);
-
- if (decals.data[decal_index].normal_rect != vec4(0.0)) {
- vec3 decal_normal;
- if (sc_decal_use_mipmaps()) {
- decal_normal = textureGrad(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].normal_rect.zw + decals.data[decal_index].normal_rect.xy, ddx * decals.data[decal_index].normal_rect.zw, ddy * decals.data[decal_index].normal_rect.zw).xyz;
- } else {
- decal_normal = textureLod(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].normal_rect.zw + decals.data[decal_index].normal_rect.xy, 0.0).xyz;
- }
- decal_normal.xy = decal_normal.xy * vec2(2.0, -2.0) - vec2(1.0, -1.0); //users prefer flipped y normal maps in most authoring software
- decal_normal.z = sqrt(max(0.0, 1.0 - dot(decal_normal.xy, decal_normal.xy)));
- //convert to view space, use xzy because y is up
- decal_normal = (decals.data[decal_index].normal_xform * decal_normal.xzy).xyz;
-
- normal = normalize(mix(normal, decal_normal, decal_albedo.a));
+ decal_normal = textureLod(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].normal_rect.zw + decals.data[decal_index].normal_rect.xy, 0.0).xyz;
}
+ decal_normal.xy = decal_normal.xy * vec2(2.0, -2.0) - vec2(1.0, -1.0); //users prefer flipped y normal maps in most authoring software
+ decal_normal.z = sqrt(max(0.0, 1.0 - dot(decal_normal.xy, decal_normal.xy)));
+ //convert to view space, use xzy because y is up
+ decal_normal = (decals.data[decal_index].normal_xform * decal_normal.xzy).xyz;
- if (decals.data[decal_index].orm_rect != vec4(0.0)) {
- vec3 decal_orm;
- if (sc_decal_use_mipmaps()) {
- decal_orm = textureGrad(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].orm_rect.zw + decals.data[decal_index].orm_rect.xy, ddx * decals.data[decal_index].orm_rect.zw, ddy * decals.data[decal_index].orm_rect.zw).xyz;
- } else {
- decal_orm = textureLod(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].orm_rect.zw + decals.data[decal_index].orm_rect.xy, 0.0).xyz;
- }
- ao = mix(ao, decal_orm.r, decal_albedo.a);
- roughness = mix(roughness, decal_orm.g, decal_albedo.a);
- metallic = mix(metallic, decal_orm.b, decal_albedo.a);
- }
+ normal = normalize(mix(normal, decal_normal, decal_albedo.a));
}
- if (decals.data[decal_index].emission_rect != vec4(0.0)) {
- //emission is additive, so its independent from albedo
+ if (decals.data[decal_index].orm_rect != vec4(0.0)) {
+ vec3 decal_orm;
if (sc_decal_use_mipmaps()) {
- emission += textureGrad(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].emission_rect.zw + decals.data[decal_index].emission_rect.xy, ddx * decals.data[decal_index].emission_rect.zw, ddy * decals.data[decal_index].emission_rect.zw).xyz * decals.data[decal_index].emission_energy * fade;
+ decal_orm = textureGrad(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].orm_rect.zw + decals.data[decal_index].orm_rect.xy, ddx * decals.data[decal_index].orm_rect.zw, ddy * decals.data[decal_index].orm_rect.zw).xyz;
} else {
- emission += textureLod(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].emission_rect.zw + decals.data[decal_index].emission_rect.xy, 0.0).xyz * decals.data[decal_index].emission_energy * fade;
+ decal_orm = textureLod(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].orm_rect.zw + decals.data[decal_index].orm_rect.xy, 0.0).xyz;
}
+ ao = mix(ao, decal_orm.r, decal_albedo.a);
+ roughness = mix(roughness, decal_orm.g, decal_albedo.a);
+ metallic = mix(metallic, decal_orm.b, decal_albedo.a);
+ }
+ }
+
+ if (decals.data[decal_index].emission_rect != vec4(0.0)) {
+ //emission is additive, so its independent from albedo
+ if (sc_decal_use_mipmaps()) {
+ emission += textureGrad(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].emission_rect.zw + decals.data[decal_index].emission_rect.xy, ddx * decals.data[decal_index].emission_rect.zw, ddy * decals.data[decal_index].emission_rect.zw).xyz * decals.data[decal_index].emission_energy * fade;
+ } else {
+ emission += textureLod(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].emission_rect.zw + decals.data[decal_index].emission_rect.xy, 0.0).xyz * decals.data[decal_index].emission_energy * fade;
}
}
- } //Decals
+ }
#endif //!MODE_RENDER_DEPTH
/////////////////////// LIGHTING //////////////////////////////
#ifdef NORMAL_USED
- if (scene_data.roughness_limiter_enabled) {
+ if (sc_scene_roughness_limiter_enabled()) {
//https://www.jp.square-enix.com/tech/library/pdf/ImprovedGeometricSpecularAA.pdf
float roughness2 = roughness * roughness;
vec3 dndu = dFdx(normal), dndv = dFdy(normal);
@@ -1225,7 +1177,7 @@ void main() {
#ifndef AMBIENT_LIGHT_DISABLED
- if (scene_data.use_reflection_cubemap) {
+ if (sc_scene_use_reflection_cubemap()) {
#ifdef LIGHT_ANISOTROPY_USED
// https://google.github.io/filament/Filament.html#lighting/imagebasedlights/anisotropy
vec3 anisotropic_direction = anisotropy >= 0.0 ? binormal : tangent;
@@ -1266,7 +1218,7 @@ void main() {
if (scene_data.use_ambient_light) {
ambient_light = scene_data.ambient_light_color_energy.rgb;
- if (scene_data.use_ambient_cubemap) {
+ if (sc_scene_use_ambient_cubemap()) {
vec3 ambient_dir = scene_data.radiance_inverse_xform * normal;
#ifdef USE_RADIANCE_CUBEMAP_ARRAY
vec3 cubemap_ambient = texture(samplerCubeArray(radiance_cubemap, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(ambient_dir, MAX_ROUGHNESS_LOD)).rgb;
@@ -1285,7 +1237,7 @@ void main() {
#endif // CUSTOM_IRRADIANCE_USED
#ifdef LIGHT_CLEARCOAT_USED
- if (scene_data.use_reflection_cubemap) {
+ if (sc_scene_use_reflection_cubemap()) {
vec3 n = normalize(normal_interp); // We want to use geometric normal, not normal_map
float NoV = max(dot(n, view), 0.0001);
vec3 ref_vec = reflect(-view, n);
@@ -1393,12 +1345,10 @@ void main() {
// skipping ssao, do we remove ssao totally?
- if (!sc_disable_reflection_probes()) { //Reflection probes
+ if (sc_reflection_probes() > 0) {
vec4 reflection_accum = vec4(0.0, 0.0, 0.0, 0.0);
vec4 ambient_accum = vec4(0.0, 0.0, 0.0, 0.0);
- uint reflection_indices = instances.data[draw_call.instance_index].reflection_probes.x;
-
#ifdef LIGHT_ANISOTROPY_USED
// https://google.github.io/filament/Filament.html#lighting/imagebasedlights/anisotropy
vec3 anisotropic_direction = anisotropy >= 0.0 ? binormal : tangent;
@@ -1411,18 +1361,9 @@ void main() {
vec3 ref_vec = normalize(reflect(-view, bent_normal));
ref_vec = mix(ref_vec, bent_normal, roughness * roughness);
- for (uint i = 0; i < 8; i++) {
- uint reflection_index = reflection_indices & 0xFF;
- if (i == 3) {
- reflection_indices = instances.data[draw_call.instance_index].reflection_probes.y;
- } else {
- reflection_indices = reflection_indices >> 8;
- }
-
- if (reflection_index == 0xFF) {
- break;
- }
-
+ uvec2 reflection_indices = instances.data[draw_call.instance_index].reflection_probes;
+ for (uint i = 0; i < sc_reflection_probes(); i++) {
+ uint reflection_index = (i > 3) ? ((reflection_indices.y >> ((i - 4) * 8)) & 0xFF) : ((reflection_indices.x >> (i * 8)) & 0xFF);
reflection_process(reflection_index, vertex, ref_vec, bent_normal, roughness, ambient_light, specular_light, ambient_accum, reflection_accum);
}
@@ -1487,7 +1428,7 @@ void main() {
specular_light += specular_light_interp.rgb * f0;
#endif
- if (!sc_disable_directional_lights()) { //directional light
+ if (sc_directional_lights() > 0) {
#ifndef SHADOWS_DISABLED
// Do shadow and lighting in two passes to reduce register pressure
uint shadow0 = 0;
@@ -1497,7 +1438,7 @@ void main() {
// Only process the first light's shadow for vertex lighting.
for (uint i = 0; i < 1; i++) {
#else
- for (uint i = 0; i < scene_data.directional_light_count; i++) {
+ for (uint i = 0; i < sc_directional_lights(); i++) {
#endif
if (!bool(directional_lights.data[i].mask & instances.data[draw_call.instance_index].layer_mask)) {
@@ -1612,7 +1553,7 @@ void main() {
#endif // SHADOWS_DISABLED
#ifndef USE_VERTEX_LIGHTING
- for (uint i = 0; i < scene_data.directional_light_count; i++) {
+ for (uint i = 0; i < sc_directional_lights(); i++) {
if (!bool(directional_lights.data[i].mask & instances.data[draw_call.instance_index].layer_mask)) {
continue; //not masked
}
@@ -1678,95 +1619,72 @@ void main() {
} //directional light
#ifndef USE_VERTEX_LIGHTING
- if (!sc_disable_omni_lights()) { //omni lights
- uint light_indices = instances.data[draw_call.instance_index].omni_lights.x;
- for (uint i = 0; i < 8; i++) {
- uint light_index = light_indices & 0xFF;
- if (i == 3) {
- light_indices = instances.data[draw_call.instance_index].omni_lights.y;
- } else {
- light_indices = light_indices >> 8;
- }
-
- if (light_index == 0xFF) {
- break;
- }
+ uvec2 omni_indices = instances.data[draw_call.instance_index].omni_lights;
+ for (uint i = 0; i < sc_omni_lights(); i++) {
+ uint light_index = (i > 3) ? ((omni_indices.y >> ((i - 4) * 8)) & 0xFF) : ((omni_indices.x >> (i * 8)) & 0xFF);
- float shadow = light_process_omni_shadow(light_index, vertex, normal, scene_data.taa_frame_count);
+ float shadow = light_process_omni_shadow(light_index, vertex, normal, scene_data.taa_frame_count);
- shadow = blur_shadow(shadow);
+ shadow = blur_shadow(shadow);
- // Fragment lighting
- light_process_omni(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow, albedo, alpha,
+ // Fragment lighting
+ light_process_omni(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow, albedo, alpha,
#ifdef LIGHT_BACKLIGHT_USED
- backlight,
+ backlight,
#endif
/*
#ifdef LIGHT_TRANSMITTANCE_USED
- transmittance_color,
- transmittance_depth,
- transmittance_boost,
+ transmittance_color,
+ transmittance_depth,
+ transmittance_boost,
#endif
*/
#ifdef LIGHT_RIM_USED
- rim,
- rim_tint,
+ rim,
+ rim_tint,
#endif
#ifdef LIGHT_CLEARCOAT_USED
- clearcoat, clearcoat_roughness, normalize(normal_interp),
+ clearcoat, clearcoat_roughness, normalize(normal_interp),
#endif
#ifdef LIGHT_ANISOTROPY_USED
- tangent,
- binormal, anisotropy,
+ tangent,
+ binormal, anisotropy,
#endif
- diffuse_light, specular_light);
- }
- } //omni lights
-
- if (!sc_disable_spot_lights()) { //spot lights
-
- uint light_indices = instances.data[draw_call.instance_index].spot_lights.x;
- for (uint i = 0; i < 8; i++) {
- uint light_index = light_indices & 0xFF;
- if (i == 3) {
- light_indices = instances.data[draw_call.instance_index].spot_lights.y;
- } else {
- light_indices = light_indices >> 8;
- }
+ diffuse_light, specular_light);
+ }
- if (light_index == 0xFF) {
- break;
- }
+ uvec2 spot_indices = instances.data[draw_call.instance_index].spot_lights;
+ for (uint i = 0; i < sc_spot_lights(); i++) {
+ uint light_index = (i > 3) ? ((spot_indices.y >> ((i - 4) * 8)) & 0xFF) : ((spot_indices.x >> (i * 8)) & 0xFF);
- float shadow = light_process_spot_shadow(light_index, vertex, normal, scene_data.taa_frame_count);
+ float shadow = light_process_spot_shadow(light_index, vertex, normal, scene_data.taa_frame_count);
- shadow = blur_shadow(shadow);
+ shadow = blur_shadow(shadow);
- light_process_spot(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow, albedo, alpha,
+ light_process_spot(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow, albedo, alpha,
#ifdef LIGHT_BACKLIGHT_USED
- backlight,
+ backlight,
#endif
/*
#ifdef LIGHT_TRANSMITTANCE_USED
- transmittance_color,
- transmittance_depth,
- transmittance_boost,
+ transmittance_color,
+ transmittance_depth,
+ transmittance_boost,
#endif
*/
#ifdef LIGHT_RIM_USED
- rim,
- rim_tint,
+ rim,
+ rim_tint,
#endif
#ifdef LIGHT_CLEARCOAT_USED
- clearcoat, clearcoat_roughness, normalize(normal_interp),
+ clearcoat, clearcoat_roughness, normalize(normal_interp),
#endif
#ifdef LIGHT_ANISOTROPY_USED
- tangent,
- binormal, anisotropy,
+ tangent,
+ binormal, anisotropy,
#endif
- diffuse_light, specular_light);
- }
- } //spot lights
+ diffuse_light, specular_light);
+ }
#endif // !VERTEX_LIGHTING
#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
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 df528973da..2cc86482f6 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
@@ -95,56 +95,80 @@ bool sc_projector_use_mipmaps() {
return ((sc_packed_0() >> 4) & 1U) != 0;
}
-bool sc_disable_omni_lights() {
+bool sc_disable_fog() {
return ((sc_packed_0() >> 5) & 1U) != 0;
}
-bool sc_disable_spot_lights() {
+bool sc_use_depth_fog() {
return ((sc_packed_0() >> 6) & 1U) != 0;
}
-bool sc_disable_reflection_probes() {
+bool sc_use_lightmap_bicubic_filter() {
return ((sc_packed_0() >> 7) & 1U) != 0;
}
-bool sc_disable_directional_lights() {
+bool sc_multimesh() {
return ((sc_packed_0() >> 8) & 1U) != 0;
}
-bool sc_disable_decals() {
+bool sc_multimesh_format_2d() {
return ((sc_packed_0() >> 9) & 1U) != 0;
}
-bool sc_disable_fog() {
+bool sc_multimesh_has_color() {
return ((sc_packed_0() >> 10) & 1U) != 0;
}
-bool sc_use_depth_fog() {
+bool sc_multimesh_has_custom_data() {
return ((sc_packed_0() >> 11) & 1U) != 0;
}
-bool sc_is_multimesh() {
+bool sc_scene_use_ambient_cubemap() {
return ((sc_packed_0() >> 12) & 1U) != 0;
}
-bool sc_use_lightmap_bicubic_filter() {
+bool sc_scene_use_reflection_cubemap() {
return ((sc_packed_0() >> 13) & 1U) != 0;
}
+bool sc_scene_roughness_limiter_enabled() {
+ return ((sc_packed_0() >> 14) & 1U) != 0;
+}
+
uint sc_soft_shadow_samples() {
- return (sc_packed_0() >> 14) & 63U;
+ return (sc_packed_0() >> 20) & 63U;
}
uint sc_penumbra_shadow_samples() {
- return (sc_packed_0() >> 20) & 63U;
+ return (sc_packed_0() >> 26) & 63U;
}
uint sc_directional_soft_shadow_samples() {
- return (sc_packed_0() >> 26) & 63U;
+ return (sc_packed_1() >> 0) & 63U;
}
uint sc_directional_penumbra_shadow_samples() {
- return (sc_packed_1() >> 0) & 63U;
+ return (sc_packed_1() >> 6) & 63U;
+}
+
+uint sc_omni_lights() {
+ return (sc_packed_1() >> 12) & 15U;
+}
+
+uint sc_spot_lights() {
+ return (sc_packed_1() >> 16) & 15U;
+}
+
+uint sc_reflection_probes() {
+ return (sc_packed_1() >> 20) & 15U;
+}
+
+uint sc_directional_lights() {
+ return (sc_packed_1() >> 24) & 15U;
+}
+
+uint sc_decals() {
+ return (sc_packed_1() >> 28) & 15U;
}
float sc_luminance_multiplier() {
@@ -166,10 +190,6 @@ layout(set = 0, binding = 2) uniform sampler shadow_sampler;
#define INSTANCE_FLAGS_USE_SH_LIGHTMAP (1 << 9)
#define INSTANCE_FLAGS_USE_VOXEL_GI (1 << 10)
#define INSTANCE_FLAGS_PARTICLES (1 << 11)
-#define INSTANCE_FLAGS_MULTIMESH (1 << 12)
-#define INSTANCE_FLAGS_MULTIMESH_FORMAT_2D (1 << 13)
-#define INSTANCE_FLAGS_MULTIMESH_HAS_COLOR (1 << 14)
-#define INSTANCE_FLAGS_MULTIMESH_HAS_CUSTOM_DATA (1 << 15)
#define INSTANCE_FLAGS_PARTICLE_TRAIL_SHIFT 16
//3 bits of stride
#define INSTANCE_FLAGS_PARTICLE_TRAIL_MASK 0xFF
diff --git a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp
index 314cbf9aa9..6efb8c176f 100644
--- a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp
@@ -306,9 +306,14 @@ void ParticlesStorage::_particles_free_data(Particles *particles) {
particles->emission_storage_buffer = RID();
}
- if (particles->unused_storage_buffer.is_valid()) {
- RD::get_singleton()->free(particles->unused_storage_buffer);
- particles->unused_storage_buffer = RID();
+ if (particles->unused_emission_storage_buffer.is_valid()) {
+ RD::get_singleton()->free(particles->unused_emission_storage_buffer);
+ particles->unused_emission_storage_buffer = RID();
+ }
+
+ if (particles->unused_trail_storage_buffer.is_valid()) {
+ RD::get_singleton()->free(particles->unused_trail_storage_buffer);
+ particles->unused_trail_storage_buffer = RID();
}
if (RD::get_singleton()->uniform_set_is_valid(particles->particles_material_uniform_set)) {
@@ -534,9 +539,15 @@ void ParticlesStorage::_particles_allocate_emission_buffer(Particles *particles)
}
}
-void ParticlesStorage::_particles_ensure_unused_buffer(Particles *particles) {
- if (particles->unused_storage_buffer.is_null()) {
- particles->unused_storage_buffer = RD::get_singleton()->storage_buffer_create(sizeof(uint32_t) * 4);
+void ParticlesStorage::_particles_ensure_unused_emission_buffer(Particles *particles) {
+ if (particles->unused_emission_storage_buffer.is_null()) {
+ particles->unused_emission_storage_buffer = RD::get_singleton()->storage_buffer_create(sizeof(uint32_t) * 4);
+ }
+}
+
+void ParticlesStorage::_particles_ensure_unused_trail_buffer(Particles *particles) {
+ if (particles->unused_trail_storage_buffer.is_null()) {
+ particles->unused_trail_storage_buffer = RD::get_singleton()->storage_buffer_create(sizeof(uint32_t) * 4);
}
}
@@ -763,8 +774,8 @@ void ParticlesStorage::_particles_process(Particles *p_particles, double p_delta
if (p_particles->emission_storage_buffer.is_valid()) {
u.append_id(p_particles->emission_storage_buffer);
} else {
- _particles_ensure_unused_buffer(p_particles);
- u.append_id(p_particles->unused_storage_buffer);
+ _particles_ensure_unused_emission_buffer(p_particles);
+ u.append_id(p_particles->unused_emission_storage_buffer);
}
uniforms.push_back(u);
}
@@ -779,8 +790,8 @@ void ParticlesStorage::_particles_process(Particles *p_particles, double p_delta
}
u.append_id(sub_emitter->emission_storage_buffer);
} else {
- _particles_ensure_unused_buffer(p_particles);
- u.append_id(p_particles->unused_storage_buffer);
+ _particles_ensure_unused_emission_buffer(p_particles);
+ u.append_id(p_particles->unused_emission_storage_buffer);
}
uniforms.push_back(u);
}
@@ -1481,8 +1492,8 @@ void ParticlesStorage::update_particles() {
if (particles->trail_bind_pose_buffer.is_valid()) {
u.append_id(particles->trail_bind_pose_buffer);
} else {
- _particles_ensure_unused_buffer(particles);
- u.append_id(particles->unused_storage_buffer);
+ _particles_ensure_unused_trail_buffer(particles);
+ u.append_id(particles->unused_trail_storage_buffer);
}
uniforms.push_back(u);
}
diff --git a/servers/rendering/renderer_rd/storage_rd/particles_storage.h b/servers/rendering/renderer_rd/storage_rd/particles_storage.h
index 33f44f3045..ec897c4ea9 100644
--- a/servers/rendering/renderer_rd/storage_rd/particles_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/particles_storage.h
@@ -247,7 +247,8 @@ private:
ParticleEmissionBuffer *emission_buffer = nullptr;
RID emission_storage_buffer;
- RID unused_storage_buffer;
+ RID unused_emission_storage_buffer;
+ RID unused_trail_storage_buffer;
HashSet<RID> collisions;
@@ -265,7 +266,8 @@ private:
void _particles_process(Particles *p_particles, double p_delta);
void _particles_allocate_emission_buffer(Particles *particles);
- void _particles_ensure_unused_buffer(Particles *particles);
+ void _particles_ensure_unused_emission_buffer(Particles *particles);
+ void _particles_ensure_unused_trail_buffer(Particles *particles);
void _particles_free_data(Particles *particles);
void _particles_update_buffers(Particles *particles);
@@ -509,7 +511,7 @@ public:
_FORCE_INLINE_ bool particles_has_collision(RID p_particles) {
Particles *particles = particles_owner.get_or_null(p_particles);
- ERR_FAIL_NULL_V(particles, 0);
+ ERR_FAIL_NULL_V(particles, false);
return particles->has_collision_cache;
}
diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h
index 4a70482d72..59671c3a13 100644
--- a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h
+++ b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h
@@ -51,6 +51,7 @@ public:
float taa_frame_count = 0.0f;
uint32_t camera_visible_layers;
bool cam_orthogonal = false;
+ bool cam_frustum = false;
bool flip_y = false;
// For billboards to cast correct shadows.
diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.h b/servers/rendering/renderer_rd/storage_rd/texture_storage.h
index b9acd0d758..3d281cf98a 100644
--- a/servers/rendering/renderer_rd/storage_rd/texture_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.h
@@ -102,9 +102,9 @@ private:
/* Canvas Texture API */
struct CanvasTextureCache {
- RID diffuse = RID();
- RID normal = RID();
- RID specular = RID();
+ RID diffuse;
+ RID normal;
+ RID specular;
};
class CanvasTexture {
diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp
index 45f980ab41..11ca7de44f 100644
--- a/servers/rendering/renderer_scene_cull.cpp
+++ b/servers/rendering/renderer_scene_cull.cpp
@@ -2738,6 +2738,7 @@ void RendererSceneCull::render_camera(const Ref<RenderSceneBuffers> &p_render_bu
Projection projection;
bool vaspect = camera->vaspect;
bool is_orthogonal = false;
+ bool is_frustum = false;
switch (camera->type) {
case Camera::ORTHOGONAL: {
@@ -2766,10 +2767,11 @@ void RendererSceneCull::render_camera(const Ref<RenderSceneBuffers> &p_render_bu
camera->znear,
camera->zfar,
camera->vaspect);
+ is_frustum = true;
} break;
}
- camera_data.set_camera(transform, projection, is_orthogonal, vaspect, jitter, taa_frame_count, camera->visible_layers);
+ camera_data.set_camera(transform, projection, is_orthogonal, is_frustum, vaspect, jitter, taa_frame_count, camera->visible_layers);
} else {
// Setup our camera for our XR interface.
// We can support multiple views here each with their own camera
@@ -2791,9 +2793,9 @@ void RendererSceneCull::render_camera(const Ref<RenderSceneBuffers> &p_render_bu
}
if (view_count == 1) {
- camera_data.set_camera(transforms[0], projections[0], false, camera->vaspect, jitter, p_jitter_phase_count, camera->visible_layers);
+ camera_data.set_camera(transforms[0], projections[0], false, false, camera->vaspect, jitter, p_jitter_phase_count, camera->visible_layers);
} else if (view_count == 2) {
- camera_data.set_multiview_camera(view_count, transforms, projections, false, camera->vaspect);
+ camera_data.set_multiview_camera(view_count, transforms, projections, false, false, camera->vaspect);
} else {
// this won't be called (see fail check above) but keeping this comment to indicate we may support more then 2 views in the future...
}
@@ -3641,7 +3643,7 @@ void RendererSceneCull::render_empty_scene(const Ref<RenderSceneBuffers> &p_rend
RENDER_TIMESTAMP("Render Empty 3D Scene");
RendererSceneRender::CameraData camera_data;
- camera_data.set_camera(Transform3D(), Projection(), true, false);
+ camera_data.set_camera(Transform3D(), Projection(), true, false, false);
scene_render->render_scene(p_render_buffers, &camera_data, &camera_data, PagedArray<RenderGeometryInstance *>(), PagedArray<RID>(), PagedArray<RID>(), PagedArray<RID>(), PagedArray<RID>(), PagedArray<RID>(), PagedArray<RID>(), environment, RID(), compositor, p_shadow_atlas, RID(), scenario->reflection_atlas, RID(), 0, 0, nullptr, 0, nullptr, 0, nullptr);
#endif
@@ -3719,7 +3721,7 @@ bool RendererSceneCull::_render_reflection_probe_step(Instance *p_instance, int
RENDER_TIMESTAMP("Render ReflectionProbe, Step " + itos(p_step));
RendererSceneRender::CameraData camera_data;
- camera_data.set_camera(xform, cm, false, false);
+ camera_data.set_camera(xform, cm, false, false, false);
Ref<RenderSceneBuffers> render_buffers = RSG::light_storage->reflection_probe_atlas_get_render_buffers(scenario->reflection_atlas);
_render_scene(&camera_data, render_buffers, environment, RID(), RID(), RSG::light_storage->reflection_probe_get_cull_mask(p_instance->base), p_instance->scenario->self, RID(), shadow_atlas, reflection_probe->instance, p_step, mesh_lod_threshold, use_shadows);
diff --git a/servers/rendering/renderer_scene_cull.h b/servers/rendering/renderer_scene_cull.h
index d3f03c4789..82523fd4ef 100644
--- a/servers/rendering/renderer_scene_cull.h
+++ b/servers/rendering/renderer_scene_cull.h
@@ -1256,6 +1256,7 @@ public:
PASS3(environment_set_bg_energy, RID, float, float)
PASS2(environment_set_canvas_max_layer, RID, int)
PASS6(environment_set_ambient_light, RID, const Color &, RS::EnvironmentAmbientSource, float, float, RS::EnvironmentReflectionSource)
+ PASS2(environment_set_camera_feed_id, RID, int)
PASS1RC(RS::EnvironmentBG, environment_get_background, RID)
PASS1RC(RID, environment_get_sky, RID)
diff --git a/servers/rendering/renderer_scene_render.cpp b/servers/rendering/renderer_scene_render.cpp
index 797ba7eaf7..07259f73d2 100644
--- a/servers/rendering/renderer_scene_render.cpp
+++ b/servers/rendering/renderer_scene_render.cpp
@@ -33,9 +33,10 @@
/////////////////////////////////////////////////////////////////////////////
// CameraData
-void RendererSceneRender::CameraData::set_camera(const Transform3D p_transform, const Projection p_projection, bool p_is_orthogonal, bool p_vaspect, const Vector2 &p_taa_jitter, float p_taa_frame_count, const uint32_t p_visible_layers) {
+void RendererSceneRender::CameraData::set_camera(const Transform3D p_transform, const Projection p_projection, bool p_is_orthogonal, bool p_is_frustum, bool p_vaspect, const Vector2 &p_taa_jitter, float p_taa_frame_count, const uint32_t p_visible_layers) {
view_count = 1;
is_orthogonal = p_is_orthogonal;
+ is_frustum = p_is_frustum;
vaspect = p_vaspect;
main_transform = p_transform;
@@ -48,12 +49,13 @@ void RendererSceneRender::CameraData::set_camera(const Transform3D p_transform,
taa_frame_count = p_taa_frame_count;
}
-void RendererSceneRender::CameraData::set_multiview_camera(uint32_t p_view_count, const Transform3D *p_transforms, const Projection *p_projections, bool p_is_orthogonal, bool p_vaspect) {
+void RendererSceneRender::CameraData::set_multiview_camera(uint32_t p_view_count, const Transform3D *p_transforms, const Projection *p_projections, bool p_is_orthogonal, bool p_is_frustum, bool p_vaspect) {
ERR_FAIL_COND_MSG(p_view_count != 2, "Incorrect view count for stereoscopic view");
visible_layers = 0xFFFFFFFF;
view_count = p_view_count;
is_orthogonal = p_is_orthogonal;
+ is_frustum = p_is_frustum;
vaspect = p_vaspect;
Vector<Plane> planes[2];
@@ -349,6 +351,14 @@ RS::EnvironmentReflectionSource RendererSceneRender::environment_get_reflection_
return environment_storage.environment_get_reflection_source(p_env);
}
+void RendererSceneRender::environment_set_camera_feed_id(RID p_env, int p_camera_feed_id) {
+ environment_storage.environment_set_camera_feed_id(p_env, p_camera_feed_id);
+}
+
+int RendererSceneRender::environment_get_camera_feed_id(RID p_env) const {
+ return environment_storage.environment_get_camera_feed_id(p_env);
+}
+
// Tonemap
void RendererSceneRender::environment_set_tonemap(RID p_env, RS::EnvironmentToneMapper p_tone_mapper, float p_exposure, float p_white) {
diff --git a/servers/rendering/renderer_scene_render.h b/servers/rendering/renderer_scene_render.h
index 4d81a9b6a3..99418e0411 100644
--- a/servers/rendering/renderer_scene_render.h
+++ b/servers/rendering/renderer_scene_render.h
@@ -118,10 +118,7 @@ public:
void environment_set_bg_energy(RID p_env, float p_multiplier, float p_exposure_value);
void environment_set_canvas_max_layer(RID p_env, int p_max_layer);
void environment_set_ambient_light(RID p_env, const Color &p_color, RS::EnvironmentAmbientSource p_ambient = RS::ENV_AMBIENT_SOURCE_BG, float p_energy = 1.0, float p_sky_contribution = 0.0, RS::EnvironmentReflectionSource p_reflection_source = RS::ENV_REFLECTION_SOURCE_BG);
-// FIXME: Disabled during Vulkan refactoring, should be ported.
-#if 0
void environment_set_camera_feed_id(RID p_env, int p_camera_feed_id);
-#endif
RS::EnvironmentBG environment_get_background(RID p_env) const;
RID environment_get_sky(RID p_env) const;
@@ -136,6 +133,7 @@ public:
float environment_get_ambient_light_energy(RID p_env) const;
float environment_get_ambient_sky_contribution(RID p_env) const;
RS::EnvironmentReflectionSource environment_get_reflection_source(RID p_env) const;
+ int environment_get_camera_feed_id(RID p_env) const;
// Tonemap
void environment_set_tonemap(RID p_env, RS::EnvironmentToneMapper p_tone_mapper, float p_exposure, float p_white);
@@ -302,6 +300,7 @@ public:
// flags
uint32_t view_count;
bool is_orthogonal;
+ bool is_frustum;
uint32_t visible_layers;
bool vaspect;
@@ -314,8 +313,8 @@ public:
Vector2 taa_jitter;
float taa_frame_count = 0.0f;
- void set_camera(const Transform3D p_transform, const Projection p_projection, bool p_is_orthogonal, bool p_vaspect, const Vector2 &p_taa_jitter = Vector2(), float p_taa_frame_count = 0.0f, uint32_t p_visible_layers = 0xFFFFFFFF);
- void set_multiview_camera(uint32_t p_view_count, const Transform3D *p_transforms, const Projection *p_projections, bool p_is_orthogonal, bool p_vaspect);
+ void set_camera(const Transform3D p_transform, const Projection p_projection, bool p_is_orthogonal, bool p_is_frustum, bool p_vaspect, const Vector2 &p_taa_jitter = Vector2(), float p_taa_frame_count = 0.0f, uint32_t p_visible_layers = 0xFFFFFFFF);
+ void set_multiview_camera(uint32_t p_view_count, const Transform3D *p_transforms, const Projection *p_projections, bool p_is_orthogonal, bool p_is_frustum, bool p_vaspect);
};
virtual void render_scene(const Ref<RenderSceneBuffers> &p_render_buffers, const CameraData *p_camera_data, const CameraData *p_prev_camera_data, const PagedArray<RenderGeometryInstance *> &p_instances, const PagedArray<RID> &p_lights, const PagedArray<RID> &p_reflection_probes, const PagedArray<RID> &p_voxel_gi_instances, const PagedArray<RID> &p_decals, const PagedArray<RID> &p_lightmaps, const PagedArray<RID> &p_fog_volumes, RID p_environment, RID p_camera_attributes, RID p_compositor, RID p_shadow_atlas, RID p_occluder_debug_tex, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, float p_screen_mesh_lod_threshold, const RenderShadowData *p_render_shadows, int p_render_shadow_count, const RenderSDFGIData *p_render_sdfgi_regions, int p_render_sdfgi_region_count, const RenderSDFGIUpdateData *p_sdfgi_update_data = nullptr, RenderingMethod::RenderInfo *r_render_info = nullptr) = 0;
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index dc5a178aaa..cc67873b24 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -3757,6 +3757,15 @@ int RenderingDevice::screen_get_height(DisplayServer::WindowID p_screen) const {
return context->surface_get_height(surface);
}
+int RenderingDevice::screen_get_pre_rotation_degrees(DisplayServer::WindowID p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ HashMap<DisplayServer::WindowID, RDD::SwapChainID>::ConstIterator it = screen_swap_chains.find(p_screen);
+ ERR_FAIL_COND_V_MSG(it == screen_swap_chains.end(), ERR_CANT_CREATE, "A swap chain was not created for the screen.");
+
+ return driver->swap_chain_get_pre_rotation_degrees(it->value);
+}
+
RenderingDevice::FramebufferFormatID RenderingDevice::screen_get_framebuffer_format(DisplayServer::WindowID p_screen) const {
_THREAD_SAFE_METHOD_
diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h
index c7b93f2fc7..9939df976f 100644
--- a/servers/rendering/rendering_device.h
+++ b/servers/rendering/rendering_device.h
@@ -1083,6 +1083,7 @@ public:
Error screen_prepare_for_drawing(DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID);
int screen_get_width(DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID) const;
int screen_get_height(DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID) const;
+ int screen_get_pre_rotation_degrees(DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID) const;
FramebufferFormatID screen_get_framebuffer_format(DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID) const;
Error screen_free(DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID);
diff --git a/servers/rendering/rendering_device_driver.cpp b/servers/rendering/rendering_device_driver.cpp
index 9ff7b83215..c1a3f34af8 100644
--- a/servers/rendering/rendering_device_driver.cpp
+++ b/servers/rendering/rendering_device_driver.cpp
@@ -376,6 +376,8 @@ uint64_t RenderingDeviceDriver::api_trait_get(ApiTrait p_trait) {
return true;
case API_TRAIT_USE_GENERAL_IN_COPY_QUEUES:
return false;
+ case API_TRAIT_BUFFERS_REQUIRE_TRANSITIONS:
+ return false;
default:
ERR_FAIL_V(0);
}
diff --git a/servers/rendering/rendering_device_driver.h b/servers/rendering/rendering_device_driver.h
index 953401e9bd..4861f653fc 100644
--- a/servers/rendering/rendering_device_driver.h
+++ b/servers/rendering/rendering_device_driver.h
@@ -454,6 +454,9 @@ public:
// Retrieve the render pass that can be used to draw on the swap chain's framebuffers.
virtual RenderPassID swap_chain_get_render_pass(SwapChainID p_swap_chain) = 0;
+ // Retrieve the rotation in degrees to apply as a pre-transform. Usually 0 on PC. May be 0, 90, 180 & 270 on Android.
+ virtual int swap_chain_get_pre_rotation_degrees(SwapChainID p_swap_chain) { return 0; }
+
// Retrieve the format used by the swap chain's framebuffers.
virtual DataFormat swap_chain_get_format(SwapChainID p_swap_chain) = 0;
@@ -756,6 +759,7 @@ public:
API_TRAIT_SECONDARY_VIEWPORT_SCISSOR,
API_TRAIT_CLEARS_WITH_COPY_ENGINE,
API_TRAIT_USE_GENERAL_IN_COPY_QUEUES,
+ API_TRAIT_BUFFERS_REQUIRE_TRANSITIONS,
};
enum ShaderChangeInvalidation {
diff --git a/servers/rendering/rendering_device_graph.cpp b/servers/rendering/rendering_device_graph.cpp
index 750753d796..ec2f336f3c 100644
--- a/servers/rendering/rendering_device_graph.cpp
+++ b/servers/rendering/rendering_device_graph.cpp
@@ -140,6 +140,25 @@ RDD::BarrierAccessBits RenderingDeviceGraph::_usage_to_access_bits(ResourceUsage
#endif
}
+bool RenderingDeviceGraph::_check_command_intersection(ResourceTracker *p_resource_tracker, int32_t p_previous_command_index, int32_t p_command_index) const {
+ if (p_resource_tracker->usage != RESOURCE_USAGE_ATTACHMENT_COLOR_READ_WRITE && p_resource_tracker->usage != RESOURCE_USAGE_ATTACHMENT_DEPTH_STENCIL_READ_WRITE) {
+ // We don't check possible intersections for usages that aren't consecutive color or depth writes.
+ return true;
+ }
+
+ const uint32_t previous_command_data_offset = command_data_offsets[p_previous_command_index];
+ const uint32_t current_command_data_offset = command_data_offsets[p_command_index];
+ const RecordedDrawListCommand &previous_draw_list_command = *reinterpret_cast<const RecordedDrawListCommand *>(&command_data[previous_command_data_offset]);
+ const RecordedDrawListCommand &current_draw_list_command = *reinterpret_cast<const RecordedDrawListCommand *>(&command_data[current_command_data_offset]);
+ if (previous_draw_list_command.type != RecordedCommand::TYPE_DRAW_LIST || current_draw_list_command.type != RecordedCommand::TYPE_DRAW_LIST) {
+ // We don't check possible intersections if both commands aren't draw lists.
+ return true;
+ }
+
+ // We check if the region used by both draw lists have an intersection.
+ return previous_draw_list_command.region.intersects(current_draw_list_command.region);
+}
+
int32_t RenderingDeviceGraph::_add_to_command_list(int32_t p_command_index, int32_t p_list_index) {
DEV_ASSERT(p_command_index < int32_t(command_count));
DEV_ASSERT(p_list_index < int32_t(command_list_nodes.size()));
@@ -425,11 +444,9 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr
#if USE_BUFFER_BARRIERS
_add_buffer_barrier_to_command(resource_tracker->buffer_driver_id, resource_tracker->usage_access, new_usage_access, r_command->buffer_barrier_index, r_command->buffer_barrier_count);
#endif
- // FIXME: Memory barriers are currently pushed regardless of whether buffer barriers are being used or not. Refer to the comment on the
- // definition of USE_BUFFER_BARRIERS for the reason behind this. This can be fixed to be one case or the other once it's been confirmed
- // the buffer and memory barrier behavior discrepancy has been solved.
- r_command->memory_barrier.src_access = resource_tracker->usage_access;
- r_command->memory_barrier.dst_access = new_usage_access;
+ // Memory barriers are pushed regardless of buffer barriers being used or not.
+ r_command->memory_barrier.src_access = r_command->memory_barrier.src_access | resource_tracker->usage_access;
+ r_command->memory_barrier.dst_access = r_command->memory_barrier.dst_access | new_usage_access;
} else {
DEV_ASSERT(false && "Resource tracker does not contain a valid buffer or texture ID.");
}
@@ -449,10 +466,12 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr
if (different_usage) {
// Even if the usage of the resource isn't a write usage explicitly, a different usage implies a transition and it should therefore be considered a write.
- write_usage = true;
+ // In the case of buffers however, this is not exactly necessary if the driver does not consider different buffer usages as different states.
+ write_usage = write_usage || bool(resource_tracker->texture_driver_id) || driver_buffers_require_transitions;
resource_tracker->usage = new_resource_usage;
}
+ bool command_intersection_failed = false;
if (search_tracker->write_command_or_list_index >= 0) {
if (search_tracker->write_command_list_enabled) {
// Make this command adjacent to any commands that wrote to this resource and intersect with the slice if it applies.
@@ -464,7 +483,7 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr
if (!resource_has_parent || search_tracker_rect.intersects(write_list_node.subresources)) {
if (write_list_node.command_index == p_command_index) {
ERR_FAIL_COND_MSG(!resource_has_parent, "Command can't have itself as a dependency.");
- } else {
+ } else if (_check_command_intersection(resource_tracker, write_list_node.command_index, p_command_index)) {
// Command is dependent on this command. Add this command to the adjacency list of the write command.
_add_adjacent_command(write_list_node.command_index, p_command_index, r_command);
@@ -480,6 +499,8 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr
write_list_index = write_list_node.next_list_index;
continue;
}
+ } else {
+ command_intersection_failed = true;
}
}
@@ -490,14 +511,16 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr
// The index is just the latest command index that wrote to the resource.
if (search_tracker->write_command_or_list_index == p_command_index) {
ERR_FAIL_MSG("Command can't have itself as a dependency.");
- } else {
+ } else if (_check_command_intersection(resource_tracker, search_tracker->write_command_or_list_index, p_command_index)) {
_add_adjacent_command(search_tracker->write_command_or_list_index, p_command_index, r_command);
+ } else {
+ command_intersection_failed = true;
}
}
}
if (write_usage) {
- if (resource_has_parent) {
+ if (resource_has_parent || command_intersection_failed) {
if (!search_tracker->write_command_list_enabled && search_tracker->write_command_or_list_index >= 0) {
// Write command list was not being used but there was a write command recorded. Add a new node with the entire parent resource's subresources and the recorded command index to the list.
const RDD::TextureSubresourceRange &tracker_subresources = search_tracker->texture_subresources;
@@ -1318,6 +1341,7 @@ void RenderingDeviceGraph::initialize(RDD *p_driver, RenderingContextDriver::Dev
driver_honors_barriers = driver->api_trait_get(RDD::API_TRAIT_HONORS_PIPELINE_BARRIERS);
driver_clears_with_copy_engine = driver->api_trait_get(RDD::API_TRAIT_CLEARS_WITH_COPY_ENGINE);
+ driver_buffers_require_transitions = driver->api_trait_get(RDD::API_TRAIT_BUFFERS_REQUIRE_TRANSITIONS);
}
void RenderingDeviceGraph::finalize() {
diff --git a/servers/rendering/rendering_device_graph.h b/servers/rendering/rendering_device_graph.h
index e52ab0c2f5..9ddd70bc80 100644
--- a/servers/rendering/rendering_device_graph.h
+++ b/servers/rendering/rendering_device_graph.h
@@ -637,6 +637,7 @@ private:
BarrierGroup barrier_group;
bool driver_honors_barriers : 1;
bool driver_clears_with_copy_engine : 1;
+ bool driver_buffers_require_transitions : 1;
WorkaroundsState workarounds_state;
TightLocalVector<Frame> frames;
uint32_t frame = 0;
@@ -648,6 +649,7 @@ private:
static bool _is_write_usage(ResourceUsage p_usage);
static RDD::TextureLayout _usage_to_image_layout(ResourceUsage p_usage);
static RDD::BarrierAccessBits _usage_to_access_bits(ResourceUsage p_usage);
+ bool _check_command_intersection(ResourceTracker *p_resource_tracker, int32_t p_previous_command_index, int32_t p_command_index) const;
int32_t _add_to_command_list(int32_t p_command_index, int32_t p_list_index);
void _add_adjacent_command(int32_t p_previous_command_index, int32_t p_command_index, RecordedCommand *r_command);
int32_t _add_to_slice_read_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index);
diff --git a/servers/rendering/rendering_method.h b/servers/rendering/rendering_method.h
index 4c277ac215..34f11924ce 100644
--- a/servers/rendering/rendering_method.h
+++ b/servers/rendering/rendering_method.h
@@ -168,6 +168,7 @@ public:
virtual void environment_set_bg_energy(RID p_env, float p_multiplier, float p_exposure_value) = 0;
virtual void environment_set_canvas_max_layer(RID p_env, int p_max_layer) = 0;
virtual void environment_set_ambient_light(RID p_env, const Color &p_color, RS::EnvironmentAmbientSource p_ambient = RS::ENV_AMBIENT_SOURCE_BG, float p_energy = 1.0, float p_sky_contribution = 0.0, RS::EnvironmentReflectionSource p_reflection_source = RS::ENV_REFLECTION_SOURCE_BG) = 0;
+ virtual void environment_set_camera_feed_id(RID p_env, int p_camera_feed_id) = 0;
virtual RS::EnvironmentBG environment_get_background(RID p_Env) const = 0;
virtual RID environment_get_sky(RID p_env) const = 0;
diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h
index 766ec8fa96..5c3ee513c7 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -786,10 +786,8 @@ public:
FUNC2(environment_set_canvas_max_layer, RID, int)
FUNC6(environment_set_ambient_light, RID, const Color &, EnvironmentAmbientSource, float, float, EnvironmentReflectionSource)
-// FIXME: Disabled during Vulkan refactoring, should be ported.
-#if 0
FUNC2(environment_set_camera_feed_id, RID, int)
-#endif
+
FUNC6(environment_set_ssr, RID, bool, int, float, float, float)
FUNC1(environment_set_ssr_roughness_quality, EnvironmentSSRRoughnessQuality)
diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h
index ddd4c41059..4dade4d79f 100644
--- a/servers/rendering/shader_language.h
+++ b/servers/rendering/shader_language.h
@@ -1213,7 +1213,7 @@ public:
struct ShaderCompileInfo {
HashMap<StringName, FunctionInfo> functions;
Vector<ModeInfo> render_modes;
- VaryingFunctionNames varying_function_names = VaryingFunctionNames();
+ VaryingFunctionNames varying_function_names;
HashSet<String> shader_types;
GlobalShaderUniformGetTypeFunc global_shader_uniform_type_func = nullptr;
bool is_include = false;
diff --git a/servers/rendering/storage/environment_storage.cpp b/servers/rendering/storage/environment_storage.cpp
index 1bbb5da6bb..e7556f9000 100644
--- a/servers/rendering/storage/environment_storage.cpp
+++ b/servers/rendering/storage/environment_storage.cpp
@@ -189,6 +189,18 @@ RS::EnvironmentReflectionSource RendererEnvironmentStorage::environment_get_refl
return env->reflection_source;
}
+void RendererEnvironmentStorage::environment_set_camera_feed_id(RID p_env, int p_camera_feed_id) {
+ Environment *env = environment_owner.get_or_null(p_env);
+ ERR_FAIL_NULL(env);
+ env->camera_feed_id = p_camera_feed_id;
+}
+
+int RendererEnvironmentStorage::environment_get_camera_feed_id(RID p_env) const {
+ Environment *env = environment_owner.get_or_null(p_env);
+ ERR_FAIL_NULL_V(env, -1);
+ return env->camera_feed_id;
+}
+
// Tonemap
void RendererEnvironmentStorage::environment_set_tonemap(RID p_env, RS::EnvironmentToneMapper p_tone_mapper, float p_exposure, float p_white) {
diff --git a/servers/rendering/storage/environment_storage.h b/servers/rendering/storage/environment_storage.h
index 9f78808ff7..6fdc047ba2 100644
--- a/servers/rendering/storage/environment_storage.h
+++ b/servers/rendering/storage/environment_storage.h
@@ -57,6 +57,7 @@ private:
float ambient_light_energy = 1.0;
float ambient_sky_contribution = 1.0;
RS::EnvironmentReflectionSource reflection_source = RS::ENV_REFLECTION_SOURCE_BG;
+ int camera_feed_id = 0;
// Tonemap
RS::EnvironmentToneMapper tone_mapper;
@@ -181,10 +182,8 @@ public:
void environment_set_bg_energy(RID p_env, float p_multiplier, float p_exposure_value);
void environment_set_canvas_max_layer(RID p_env, int p_max_layer);
void environment_set_ambient_light(RID p_env, const Color &p_color, RS::EnvironmentAmbientSource p_ambient = RS::ENV_AMBIENT_SOURCE_BG, float p_energy = 1.0, float p_sky_contribution = 0.0, RS::EnvironmentReflectionSource p_reflection_source = RS::ENV_REFLECTION_SOURCE_BG);
-// FIXME: Disabled during Vulkan refactoring, should be ported.
-#if 0
void environment_set_camera_feed_id(RID p_env, int p_camera_feed_id);
-#endif
+ int environment_get_camera_feed_id(RID p_env) const;
RS::EnvironmentBG environment_get_background(RID p_env) const;
RID environment_get_sky(RID p_env) const;
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 1008d5706e..272908aa49 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -309,7 +309,7 @@ RID RenderingServer::get_white_texture() {
w[i] = 255;
}
}
- Ref<Image> white = memnew(Image(4, 4, 0, Image::FORMAT_RGB8, wt));
+ Ref<Image> white = memnew(Image(4, 4, false, Image::FORMAT_RGB8, wt));
white_texture = texture_2d_create(white);
return white_texture;
}
@@ -2980,6 +2980,7 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("environment_create"), &RenderingServer::environment_create);
ClassDB::bind_method(D_METHOD("environment_set_background", "env", "bg"), &RenderingServer::environment_set_background);
+ ClassDB::bind_method(D_METHOD("environment_set_camera_id", "env", "id"), &RenderingServer::environment_set_camera_feed_id);
ClassDB::bind_method(D_METHOD("environment_set_sky", "env", "sky"), &RenderingServer::environment_set_sky);
ClassDB::bind_method(D_METHOD("environment_set_sky_custom_fov", "env", "scale"), &RenderingServer::environment_set_sky_custom_fov);
ClassDB::bind_method(D_METHOD("environment_set_sky_orientation", "env", "orientation"), &RenderingServer::environment_set_sky_orientation);
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index 6c1e1274d4..90365a19eb 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -1180,6 +1180,7 @@ public:
virtual void environment_set_bg_energy(RID p_env, float p_multiplier, float p_exposure_value) = 0;
virtual void environment_set_canvas_max_layer(RID p_env, int p_max_layer) = 0;
virtual void environment_set_ambient_light(RID p_env, const Color &p_color, EnvironmentAmbientSource p_ambient = ENV_AMBIENT_SOURCE_BG, float p_energy = 1.0, float p_sky_contribution = 0.0, EnvironmentReflectionSource p_reflection_source = ENV_REFLECTION_SOURCE_BG) = 0;
+ virtual void environment_set_camera_feed_id(RID p_env, int p_camera_feed_id) = 0;
enum EnvironmentGlowBlendMode {
ENV_GLOW_BLEND_MODE_ADDITIVE,
diff --git a/tests/scene/test_primitives.h b/tests/scene/test_primitives.h
index 59f23983e5..7426a9ce29 100644
--- a/tests/scene/test_primitives.h
+++ b/tests/scene/test_primitives.h
@@ -104,8 +104,9 @@ TEST_CASE("[SceneTree][Primitive][Capsule] Capsule Primitive") {
float dist_to_yaxis = 0.f;
for (Vector3 point : points) {
float new_dist_to_y = point.x * point.x + point.z * point.z;
- if (new_dist_to_y > dist_to_yaxis)
+ if (new_dist_to_y > dist_to_yaxis) {
dist_to_yaxis = new_dist_to_y;
+ }
}
CHECK(dist_to_yaxis <= radius * radius);
@@ -114,10 +115,12 @@ TEST_CASE("[SceneTree][Primitive][Capsule] Capsule Primitive") {
float max_y{ 0.f };
float min_y{ 0.f };
for (Vector3 point : points) {
- if (point.y > max_y)
+ if (point.y > max_y) {
max_y = point.y;
- if (point.y < min_y)
+ }
+ if (point.y < min_y) {
min_y = point.y;
+ }
}
CHECK(max_y - min_y <= height);
@@ -196,12 +199,14 @@ TEST_CASE("[SceneTree][Primitive][Box] Box Primitive") {
for (const Vector3 &normal : normals) {
bool add_normal{ true };
for (const Vector3 &vec : distinct_normals) {
- if (vec.is_equal_approx(normal))
+ if (vec.is_equal_approx(normal)) {
add_normal = false;
+ }
}
- if (add_normal)
+ if (add_normal) {
distinct_normals.push_back(normal);
+ }
}
CHECK_MESSAGE(distinct_normals.size() == 6,
@@ -218,8 +223,9 @@ TEST_CASE("[SceneTree][Primitive][Box] Box Primitive") {
break;
}
}
- if (!normal_correct_direction)
+ if (!normal_correct_direction) {
break;
+ }
}
CHECK_MESSAGE(normal_correct_direction,
diff --git a/tests/servers/test_navigation_server_3d.h b/tests/servers/test_navigation_server_3d.h
index 4411b1aae5..1b2a5e0db2 100644
--- a/tests/servers/test_navigation_server_3d.h
+++ b/tests/servers/test_navigation_server_3d.h
@@ -49,7 +49,7 @@ public:
}
unsigned function1_calls{ 0 };
- Variant function1_latest_arg0{};
+ Variant function1_latest_arg0;
};
static inline Array build_array() {
diff --git a/tests/test_macros.h b/tests/test_macros.h
index d32b26f111..9cc075b6d3 100644
--- a/tests/test_macros.h
+++ b/tests/test_macros.h
@@ -474,6 +474,6 @@ public:
for (int i = 0; i < string_list.size(); ++i) { \
CHECK(string_list[i] == m_slices[i]); \
} \
- } while (0)
+ } while (false)
#endif // TEST_MACROS_H
diff --git a/thirdparty/README.md b/thirdparty/README.md
index 87f8782e7f..b473d9e693 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -99,9 +99,14 @@ Files extracted from upstream source:
## certs
- Upstream: Mozilla, via https://github.com/bagder/ca-bundle
-- Version: git (c5a419971b1bec220368c619aaafd0b818aa119f, 2024)
+- Version: git (4d3fe6683f651d96be1bbef316b201e9b33b274d, 2024),
+ generated from mozilla-release changeset b8ea2342548b8571e58f9176d9555ccdb5ec199f
- License: MPL 2.0
+Files extracted from upstream source:
+
+- `ca-bundle.crt` renamed to `ca-certificates.crt`
+
## clipper2
@@ -709,7 +714,7 @@ Collection of single-file libraries used in Godot components.
* Modifications: use `const char*` instead of `char*` for input string
- `smolv.{cpp,h}`
* Upstream: https://github.com/aras-p/smol-v
- * Version: git (4b52c165c13763051a18e80ffbc2ee436314ceb2, 2020)
+ * Version: git (9dd54c379ac29fa148cb1b829bb939ba7381d8f4, 2024)
* License: Public Domain or MIT
- `stb_rect_pack.h`
* Upstream: https://github.com/nothings/stb
diff --git a/thirdparty/certs/ca-certificates.crt b/thirdparty/certs/ca-certificates.crt
index f437587091..c559ab795b 100644
--- a/thirdparty/certs/ca-certificates.crt
+++ b/thirdparty/certs/ca-certificates.crt
@@ -1,7 +1,9 @@
##
## Bundle of CA Root Certificates
##
-## Certificate data from Mozilla as of: Mon Mar 11 15:15:21 2024 GMT
+## Certificate data from Mozilla as of: Sat Oct 19 21:26:09 2024 GMT
+##
+## Find updated versions here: https://curl.se/docs/caextract.html
##
## This is a bundle of X.509 certificates of public Certificate Authorities
## (CA). These were automatically extracted from Mozilla's root certificates
@@ -14,7 +16,7 @@
## Just configure this file as the SSLCACertificateFile.
##
## Conversion done with mk-ca-bundle.pl version 1.29.
-## SHA256: 4d96bd539f4719e9ace493757afbe4a23ee8579de1c97fbebc50bba3c12e8c1e
+## SHA256: 36105b01631f9fc03b1eca779b44a30a1a5890b9bf8dc07ccb001a07301e01cf
##
@@ -2600,36 +2602,6 @@ vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+
CAezNIm8BZ/3Hobui3A=
-----END CERTIFICATE-----
-GLOBALTRUST 2020
-================
------BEGIN CERTIFICATE-----
-MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx
-IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT
-VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh
-BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy
-MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi
-D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO
-VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM
-CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm
-fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA
-A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR
-JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG
-DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU
-clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ
-mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
-AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud
-IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA
-VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw
-4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9
-iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS
-8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2
-HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS
-vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918
-oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF
-YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl
-gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg==
------END CERTIFICATE-----
-
ANF Secure Server Root CA
=========================
-----BEGIN CERTIFICATE-----
@@ -3579,3 +3551,116 @@ wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+ljX273CXE2whJdV/LItM3z7gLfEdxquVeE
HVlNjM7IDiPCtyaaEBRx/pOyiriA8A4QntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0
o82bNSQ3+pCTE4FCxpgmdTdmQRCsu/WU48IxK63nI1bMNSWSs1A=
-----END CERTIFICATE-----
+
+FIRMAPROFESIONAL CA ROOT-A WEB
+==============================
+-----BEGIN CERTIFICATE-----
+MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQswCQYDVQQGEwJF
+UzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UEYQwPVkFURVMtQTYyNjM0MDY4
+MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENBIFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2
+WhcNNDcwMzMxMDkwMTM2WjBuMQswCQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25h
+bCBTQTEYMBYGA1UEYQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFM
+IENBIFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zfe9MEkVz6
+iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6CcyvHZpsKjECcfIr28jlg
+st7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FD
+Y1w8ndYn81LsF7Kpryz3dvgwHQYDVR0OBBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB
+/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgL
+cFBTApFwhVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dGXSaQ
+pYXFuXqUPoeovQA=
+-----END CERTIFICATE-----
+
+TWCA CYBER Root CA
+==================
+-----BEGIN CERTIFICATE-----
+MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQMQswCQYDVQQG
+EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB
+IENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQG
+EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB
+IENZQkVSIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1s
+Ts6P40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxFavcokPFh
+V8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/34bKS1PE2Y2yHer43CdT
+o0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684iJkXXYJndzk834H/nY62wuFm40AZoNWDT
+Nq5xQwTxaWV4fPMf88oon1oglWa0zbfuj3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK
+/c/WMw+f+5eesRycnupfXtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkH
+IuNZW0CP2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDAS9TM
+fAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDAoS/xUgXJP+92ZuJF
+2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzCkHDXShi8fgGwsOsVHkQGzaRP6AzR
+wyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAO
+BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83
+QOGt4A1WNzAdBgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB
+AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0ttGlTITVX1olN
+c79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn68xDiBaiA9a5F/gZbG0jAn/x
+X9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNnTKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDR
+IG4kqIQnoVesqlVYL9zZyvpoBJ7tRCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq
+/p1hvIbZv97Tujqxf36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0R
+FxbIQh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz8ppy6rBe
+Pm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4NxKfKjLji7gh7MMrZQzv
+It6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzXxeSDwWrruoBa3lwtcHb4yOWHh8qgnaHl
+IhInD0Q9HWzq1MKLL295q39QpsQZp6F6t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X
+-----END CERTIFICATE-----
+
+SecureSign Root CA12
+====================
+-----BEGIN CERTIFICATE-----
+MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQELBQAwUTELMAkG
+A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT
+ZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgwNTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJ
+BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU
+U2VjdXJlU2lnbiBSb290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3
+emhFKxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mtp7JIKwcc
+J/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zdJ1M3s6oYwlkm7Fsf0uZl
+fO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gurFzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBF
+EaCeVESE99g2zvVQR9wsMJvuwPWW0v4JhscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1Uef
+NzFJM3IFTQy2VYzxV4+Kh9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
+AQH/BAQDAgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsFAAOC
+AQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6LdmmQOmFxv3Y67ilQi
+LUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJmBClnW8Zt7vPemVV2zfrPIpyMpce
+mik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPS
+vWKErI4cqc1avTc7bgoitPQV55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhga
+aaI5gdka9at/yOPiZwud9AzqVN/Ssq+xIvEg37xEHA==
+-----END CERTIFICATE-----
+
+SecureSign Root CA14
+====================
+-----BEGIN CERTIFICATE-----
+MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEMBQAwUTELMAkG
+A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT
+ZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgwNzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJ
+BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU
+U2VjdXJlU2lnbiBSb290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh
+1oq/FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOgvlIfX8xn
+bacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy6pJxaeQp8E+BgQQ8sqVb
+1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa
+/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9JkdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOE
+kJTRX45zGRBdAuVwpcAQ0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSx
+jVIHvXiby8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac18iz
+ju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs0Wq2XSqypWa9a4X0
+dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIABSMbHdPTGrMNASRZhdCyvjG817XsY
+AFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVLApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQAB
+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeq
+YR3r6/wtbyPk86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E
+rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ibed87hwriZLoA
+ymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopTzfFP7ELyk+OZpDc8h7hi2/Ds
+Hzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHSDCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPG
+FrojutzdfhrGe0K22VoF3Jpf1d+42kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6q
+nsb58Nn4DSEC5MUoFlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/
+OfVyK4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6dB7h7sxa
+OgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtlLor6CZpO2oYofaphNdgO
+pygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB365jJ6UeTo3cKXhZ+PmhIIynJkBugnLN
+eLLIjzwec+fBH7/PzqUqm9tEZDKgu39cJRNItX+S
+-----END CERTIFICATE-----
+
+SecureSign Root CA15
+====================
+-----BEGIN CERTIFICATE-----
+MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMwUTELMAkGA1UE
+BhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRTZWN1
+cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMyNTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNV
+BAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2Vj
+dXJlU2lnbiBSb290IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5G
+dCx4wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSRZHX+AezB
+2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
+AgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT9DAKBggqhkjOPQQDAwNoADBlAjEA2S6J
+fl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJ
+SwdLZrWeqrqgHkHZAXQ6bkU6iYAZezKYVWOr62Nuk22rGwlgMU4=
+-----END CERTIFICATE-----
diff --git a/thirdparty/misc/smolv.cpp b/thirdparty/misc/smolv.cpp
index 26ed7294f9..67b914606c 100644
--- a/thirdparty/misc/smolv.cpp
+++ b/thirdparty/misc/smolv.cpp
@@ -1,5 +1,5 @@
// smol-v - public domain - https://github.com/aras-p/smol-v
-// authored 2016-2020 by Aras Pranckevicius
+// authored 2016-2024 by Aras Pranckevicius
// no warranty implied; use at your own risk
// See end of file for license information.
@@ -1197,8 +1197,8 @@ static bool smolv_CheckGenericHeader(const uint32_t* words, size_t wordCount, ui
if (headerMagic != expectedMagic)
return false;
uint32_t headerVersion = words[1] & versionMask;
- if (headerVersion < 0x00010000 || headerVersion > 0x00010500)
- return false; // only support 1.0 through 1.5
+ if (headerVersion < 0x00010000 || headerVersion > 0x00010600)
+ return false; // only support 1.0 through 1.6
return true;
}
@@ -2071,7 +2071,7 @@ void smolv::StatsPrint(const Stats* stats)
// This software is available under 2 licenses -- choose whichever you prefer.
// ------------------------------------------------------------------------------
// ALTERNATIVE A - MIT License
-// Copyright (c) 2016-2020 Aras Pranckevicius
+// Copyright (c) 2016-2024 Aras Pranckevicius
// 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
diff --git a/thirdparty/misc/smolv.h b/thirdparty/misc/smolv.h
index 798ee4126f..231dfc6f30 100644
--- a/thirdparty/misc/smolv.h
+++ b/thirdparty/misc/smolv.h
@@ -1,5 +1,5 @@
// smol-v - public domain - https://github.com/aras-p/smol-v
-// authored 2016-2020 by Aras Pranckevicius
+// authored 2016-2024 by Aras Pranckevicius
// no warranty implied; use at your own risk
// See end of file for license information.
//
@@ -132,7 +132,7 @@ namespace smolv
// This software is available under 2 licenses -- choose whichever you prefer.
// ------------------------------------------------------------------------------
// ALTERNATIVE A - MIT License
-// Copyright (c) 2016-2020 Aras Pranckevicius
+// Copyright (c) 2016-2024 Aras Pranckevicius
// 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
diff --git a/thirdparty/openxr/patches/use-egl-from-glad.diff b/thirdparty/openxr/patches/use-egl-from-glad.diff
new file mode 100644
index 0000000000..29c746d5c7
--- /dev/null
+++ b/thirdparty/openxr/patches/use-egl-from-glad.diff
@@ -0,0 +1,27 @@
+diff -Nur openxr-orig/src/common/xr_dependencies.h openxr/src/common/xr_dependencies.h
+--- openxr-orig/src/common/xr_dependencies.h 2024-11-04 10:38:11.940682727 -0600
++++ openxr/src/common/xr_dependencies.h 2024-11-04 10:38:46.351415476 -0600
+@@ -65,7 +65,11 @@
+ #endif // XR_USE_GRAPHICS_API_OPENGL
+
+ #ifdef XR_USE_GRAPHICS_API_OPENGL_ES
++#ifdef GLAD_ENABLED
++#include "thirdparty/glad/glad/egl.h"
++#else
+ #include <EGL/egl.h>
++#endif
+ #endif // XR_USE_GRAPHICS_API_OPENGL_ES
+
+ #ifdef XR_USE_GRAPHICS_API_VULKAN
+@@ -77,7 +81,11 @@
+ #endif // XR_USE_PLATFORM_WAYLAND
+
+ #ifdef XR_USE_PLATFORM_EGL
++#ifdef GLAD_ENABLED
++#include "thirdparty/glad/glad/egl.h"
++#else
+ #include <EGL/egl.h>
++#endif
+ #endif // XR_USE_PLATFORM_EGL
+
+ #if defined(XR_USE_PLATFORM_XLIB) || defined(XR_USE_PLATFORM_XCB)
diff --git a/thirdparty/openxr/src/common/xr_dependencies.h b/thirdparty/openxr/src/common/xr_dependencies.h
index 55d93bfbac..a192a2ec6d 100644
--- a/thirdparty/openxr/src/common/xr_dependencies.h
+++ b/thirdparty/openxr/src/common/xr_dependencies.h
@@ -65,7 +65,11 @@
#endif // XR_USE_GRAPHICS_API_OPENGL
#ifdef XR_USE_GRAPHICS_API_OPENGL_ES
+#ifdef GLAD_ENABLED
+#include "thirdparty/glad/glad/egl.h"
+#else
#include <EGL/egl.h>
+#endif
#endif // XR_USE_GRAPHICS_API_OPENGL_ES
#ifdef XR_USE_GRAPHICS_API_VULKAN
@@ -77,7 +81,11 @@
#endif // XR_USE_PLATFORM_WAYLAND
#ifdef XR_USE_PLATFORM_EGL
+#ifdef GLAD_ENABLED
+#include "thirdparty/glad/glad/egl.h"
+#else
#include <EGL/egl.h>
+#endif
#endif // XR_USE_PLATFORM_EGL
#if defined(XR_USE_PLATFORM_XLIB) || defined(XR_USE_PLATFORM_XCB)