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--.pre-commit-config.yaml8
-rw-r--r--COPYRIGHT.txt2
-rw-r--r--core/config/engine.cpp8
-rw-r--r--core/config/engine.h5
-rw-r--r--core/config/project_settings.cpp42
-rw-r--r--core/core_bind.cpp30
-rw-r--r--core/core_bind.h2
-rw-r--r--core/crypto/crypto.cpp2
-rw-r--r--core/crypto/crypto_core.cpp4
-rw-r--r--core/debugger/engine_debugger.cpp18
-rw-r--r--core/debugger/remote_debugger.cpp4
-rw-r--r--core/debugger/remote_debugger_peer.cpp2
-rw-r--r--core/extension/extension_api_dump.cpp10
-rw-r--r--core/extension/gdextension.cpp30
-rw-r--r--core/extension/gdextension_interface.cpp2
-rw-r--r--core/extension/gdextension_library_loader.cpp10
-rw-r--r--core/extension/gdextension_manager.cpp2
-rw-r--r--core/input/input.cpp110
-rw-r--r--core/input/input.h10
-rw-r--r--core/input/input_map.cpp2
-rw-r--r--core/io/dir_access.cpp16
-rw-r--r--core/io/dir_access.h4
-rw-r--r--core/io/file_access.cpp20
-rw-r--r--core/io/file_access_compressed.cpp2
-rw-r--r--core/io/file_access_encrypted.cpp2
-rw-r--r--core/io/file_access_memory.cpp2
-rw-r--r--core/io/file_access_pack.cpp10
-rw-r--r--core/io/file_access_zip.cpp4
-rw-r--r--core/io/http_client.cpp4
-rw-r--r--core/io/image.cpp52
-rw-r--r--core/io/image_loader.cpp4
-rw-r--r--core/io/ip.cpp4
-rw-r--r--core/io/json.cpp2
-rw-r--r--core/io/marshalls.cpp8
-rw-r--r--core/io/pck_packer.cpp2
-rw-r--r--core/io/plist.cpp2
-rw-r--r--core/io/remote_filesystem_client.cpp14
-rw-r--r--core/io/resource.cpp2
-rw-r--r--core/io/resource_format_binary.cpp56
-rw-r--r--core/io/resource_importer.cpp4
-rw-r--r--core/io/resource_importer.h2
-rw-r--r--core/io/resource_loader.cpp6
-rw-r--r--core/io/resource_saver.cpp2
-rw-r--r--core/io/translation_loader_po.cpp30
-rw-r--r--core/io/xml_parser.cpp4
-rw-r--r--core/math/expression.cpp2
-rw-r--r--core/math/rect2.cpp2
-rw-r--r--core/math/vector2.cpp2
-rw-r--r--core/math/vector3.cpp2
-rw-r--r--core/object/class_db.cpp88
-rw-r--r--core/object/message_queue.h2
-rw-r--r--core/object/object.cpp40
-rw-r--r--core/object/script_language.cpp6
-rw-r--r--core/object/undo_redo.cpp2
-rw-r--r--core/os/os.cpp2
-rw-r--r--core/string/node_path.cpp2
-rw-r--r--core/string/translation_po.cpp10
-rw-r--r--core/string/ustring.cpp28
-rw-r--r--core/string/ustring.h1
-rw-r--r--core/typedefs.h2
-rw-r--r--core/variant/array.cpp14
-rw-r--r--core/variant/container_type_validate.h10
-rw-r--r--core/variant/variant.cpp14
-rw-r--r--core/variant/variant.h3
-rw-r--r--core/variant/variant_construct.cpp2
-rw-r--r--core/variant/variant_utility.cpp2
-rw-r--r--doc/classes/CameraFeed.xml18
-rw-r--r--doc/classes/ClassDB.xml2
-rw-r--r--doc/classes/DisplayServer.xml18
-rw-r--r--doc/classes/EditorFeatureProfile.xml5
-rw-r--r--doc/classes/EditorImportPlugin.xml3
-rw-r--r--doc/classes/FileDialog.xml4
-rw-r--r--doc/classes/ProjectSettings.xml4
-rw-r--r--doc/classes/RenderingServer.xml8
-rw-r--r--doc/classes/ResourceLoader.xml2
-rw-r--r--doc/classes/SpringArm3D.xml1
-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/shader_gles3.cpp4
-rw-r--r--drivers/gles3/shaders/SCsub1
-rw-r--r--drivers/gles3/shaders/feed.glsl39
-rw-r--r--drivers/gles3/shaders/stdlib_inc.glsl40
-rw-r--r--drivers/gles3/storage/config.cpp7
-rw-r--r--drivers/gles3/storage/config.h9
-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/editor_debugger_node.cpp8
-rw-r--r--editor/debugger/editor_debugger_node.h8
-rw-r--r--editor/debugger/editor_debugger_tree.cpp38
-rw-r--r--editor/debugger/editor_debugger_tree.h3
-rw-r--r--editor/debugger/script_editor_debugger.cpp89
-rw-r--r--editor/editor_audio_buses.cpp20
-rw-r--r--editor/editor_feature_profile.cpp4
-rw-r--r--editor/editor_feature_profile.h1
-rw-r--r--editor/editor_file_system.cpp4
-rw-r--r--editor/editor_main_screen.h1
-rw-r--r--editor/editor_node.cpp12
-rw-r--r--editor/editor_node.h2
-rw-r--r--editor/filesystem_dock.cpp3
-rw-r--r--editor/filesystem_dock.h1
-rw-r--r--editor/gui/editor_quick_open_dialog.cpp7
-rw-r--r--editor/icons/2DNodes.svg1
-rw-r--r--editor/icons/Camera.svg1
-rw-r--r--editor/icons/Game.svg1
-rw-r--r--editor/icons/MaterialPreviewQuad.svg1
-rw-r--r--editor/icons/NextFrame.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.h2
-rw-r--r--editor/plugins/bone_map_editor_plugin.cpp4
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp43
-rw-r--r--editor/plugins/canvas_item_editor_plugin.h4
-rw-r--r--editor/plugins/font_config_plugin.cpp2
-rw-r--r--editor/plugins/game_view_plugin.cpp478
-rw-r--r--editor/plugins/game_view_plugin.h152
-rw-r--r--editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp15
-rw-r--r--editor/plugins/material_editor_plugin.cpp93
-rw-r--r--editor/plugins/material_editor_plugin.h7
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp98
-rw-r--r--editor/plugins/node_3d_editor_plugin.h10
-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/project_converter_3_to_4.cpp6
-rw-r--r--editor/themes/editor_color_map.cpp2
-rw-r--r--editor/themes/editor_theme_manager.cpp6
-rw-r--r--methods.py92
-rw-r--r--misc/extension_api_validation/4.3-stable.expected8
-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/features/class_inference_is_weak.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd30
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd30
-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/features/export_arrays.out26
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.out6
-rw-r--r--modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/number_separators.out12
-rw-r--r--modules/gdscript/tests/scripts/parser/features/operator_assign.out2
-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/chain_assignment_works.out12
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out12
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out16
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/stringify.out22
-rw-r--r--modules/gdscript/tests/scripts/utils.notest.gd1
-rw-r--r--modules/gltf/config.py1
-rw-r--r--modules/gltf/doc_classes/GLTFAccessor.xml38
-rw-r--r--modules/gltf/doc_classes/GLTFDocument.xml18
-rw-r--r--modules/gltf/doc_classes/GLTFDocumentExtension.xml25
-rw-r--r--modules/gltf/doc_classes/GLTFNode.xml9
-rw-r--r--modules/gltf/doc_classes/GLTFObjectModelProperty.xml114
-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.cpp2309
-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/meshoptimizer/register_types.cpp4
-rw-r--r--modules/minimp3/resource_importer_mp3.h2
-rw-r--r--modules/mono/editor/bindings_generator.cpp110
-rw-r--r--modules/navigation/nav_agent.h2
-rw-r--r--modules/navigation/nav_map.cpp61
-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.cpp30
-rw-r--r--platform/android/display_server_android.h7
-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.kt17
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotIO.java22
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotLib.java5
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/FilePicker.kt160
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt72
-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_lib_jni.cpp16
-rw-r--r--platform/android/java_godot_lib_jni.h1
-rw-r--r--platform/android/java_godot_wrapper.cpp42
-rw-r--r--platform/android/java_godot_wrapper.h5
-rw-r--r--platform/ios/display_server_ios.mm1
-rw-r--r--platform/linuxbsd/os_linuxbsd.cpp8
-rw-r--r--platform/linuxbsd/wayland/display_server_wayland.cpp3
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp1
-rw-r--r--platform/macos/display_server_macos.mm1
-rw-r--r--platform/web/display_server_web.cpp1
-rw-r--r--platform/web/emscripten_helpers.py20
-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.cpp67
-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/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/2d/animated_sprite_2d.cpp6
-rw-r--r--scene/2d/animated_sprite_2d.h9
-rw-r--r--scene/2d/back_buffer_copy.cpp4
-rw-r--r--scene/2d/back_buffer_copy.h4
-rw-r--r--scene/2d/camera_2d.cpp1
-rw-r--r--scene/2d/gpu_particles_2d.cpp2
-rw-r--r--scene/2d/light_2d.cpp7
-rw-r--r--scene/2d/light_2d.h5
-rw-r--r--scene/2d/light_occluder_2d.cpp15
-rw-r--r--scene/2d/light_occluder_2d.h9
-rw-r--r--scene/2d/line_2d.cpp2
-rw-r--r--scene/2d/line_2d.h2
-rw-r--r--scene/2d/marker_2d.cpp4
-rw-r--r--scene/2d/marker_2d.h4
-rw-r--r--scene/2d/mesh_instance_2d.cpp4
-rw-r--r--scene/2d/mesh_instance_2d.h4
-rw-r--r--scene/2d/multimesh_instance_2d.cpp4
-rw-r--r--scene/2d/multimesh_instance_2d.h4
-rw-r--r--scene/2d/navigation_agent_2d.cpp8
-rw-r--r--scene/2d/navigation_link_2d.cpp4
-rw-r--r--scene/2d/navigation_link_2d.h4
-rw-r--r--scene/2d/navigation_obstacle_2d.cpp8
-rw-r--r--scene/2d/navigation_region_2d.cpp4
-rw-r--r--scene/2d/navigation_region_2d.h4
-rw-r--r--scene/2d/path_2d.cpp2
-rw-r--r--scene/2d/path_2d.h2
-rw-r--r--scene/2d/physics/collision_polygon_2d.cpp2
-rw-r--r--scene/2d/physics/collision_polygon_2d.h2
-rw-r--r--scene/2d/physics/collision_shape_2d.h4
-rw-r--r--scene/2d/polygon_2d.cpp4
-rw-r--r--scene/2d/polygon_2d.h5
-rw-r--r--scene/2d/sprite_2d.cpp4
-rw-r--r--scene/2d/sprite_2d.h5
-rw-r--r--scene/2d/tile_map.cpp16
-rw-r--r--scene/2d/tile_map.h8
-rw-r--r--scene/2d/touch_screen_button.cpp5
-rw-r--r--scene/2d/touch_screen_button.h4
-rw-r--r--scene/2d/visible_on_screen_notifier_2d.cpp4
-rw-r--r--scene/2d/visible_on_screen_notifier_2d.h4
-rw-r--r--scene/3d/camera_3d.cpp1
-rw-r--r--scene/3d/gpu_particles_3d.cpp2
-rw-r--r--scene/3d/navigation_agent_3d.cpp8
-rw-r--r--scene/3d/navigation_obstacle_3d.cpp8
-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/audio/audio_stream_player_internal.cpp8
-rw-r--r--scene/debugger/scene_debugger.cpp1192
-rw-r--r--scene/debugger/scene_debugger.h161
-rw-r--r--scene/gui/code_edit.cpp2
-rw-r--r--scene/gui/code_edit.h6
-rw-r--r--scene/gui/control.cpp46
-rw-r--r--scene/gui/control.h18
-rw-r--r--scene/gui/file_dialog.cpp27
-rw-r--r--scene/gui/file_dialog.h4
-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/gui/video_stream_player.cpp8
-rw-r--r--scene/main/canvas_item.cpp8
-rw-r--r--scene/main/canvas_item.h23
-rw-r--r--scene/main/node.cpp13
-rw-r--r--scene/main/node.h3
-rw-r--r--scene/main/scene_tree.cpp27
-rw-r--r--scene/main/scene_tree.h3
-rw-r--r--scene/main/viewport.cpp83
-rw-r--r--scene/main/viewport.h8
-rw-r--r--scene/resources/2d/navigation_polygon.cpp4
-rw-r--r--scene/resources/2d/navigation_polygon.h5
-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/convex_polygon_shape_3d.cpp2
-rw-r--r--scene/resources/3d/importer_mesh.cpp103
-rw-r--r--scene/resources/3d/importer_mesh.h2
-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.cpp4
-rw-r--r--scu_builders.py17
-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.cpp1
-rw-r--r--servers/display_server.h8
-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/core/math/test_aabb.h2
-rw-r--r--tests/core/math/test_color.h2
-rw-r--r--tests/core/math/test_rect2.h2
-rw-r--r--tests/core/string/test_string.h24
-rw-r--r--tests/core/variant/test_variant_utility.h2
-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
431 files changed, 8561 insertions, 2670 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/.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/config/engine.cpp b/core/config/engine.cpp
index 12ada98d43..aac048e93f 100644
--- a/core/config/engine.cpp
+++ b/core/config/engine.cpp
@@ -116,6 +116,10 @@ void Engine::set_time_scale(double p_scale) {
}
double Engine::get_time_scale() const {
+ return freeze_time_scale ? 0 : _time_scale;
+}
+
+double Engine::get_unfrozen_time_scale() const {
return _time_scale;
}
@@ -404,6 +408,10 @@ bool Engine::notify_frame_server_synced() {
return server_syncs > SERVER_SYNC_FRAME_COUNT_WARNING;
}
+void Engine::set_freeze_time_scale(bool p_frozen) {
+ freeze_time_scale = p_frozen;
+}
+
Engine::Engine() {
singleton = this;
}
diff --git a/core/config/engine.h b/core/config/engine.h
index fd7fbd717e..b38412308a 100644
--- a/core/config/engine.h
+++ b/core/config/engine.h
@@ -99,6 +99,8 @@ private:
int server_syncs = 0;
bool frame_server_synced = false;
+ bool freeze_time_scale = false;
+
public:
static Engine *get_singleton();
@@ -130,6 +132,7 @@ public:
void set_time_scale(double p_scale);
double get_time_scale() const;
+ double get_unfrozen_time_scale() const;
void set_print_to_stdout(bool p_enabled);
bool is_printing_to_stdout() const;
@@ -197,6 +200,8 @@ public:
void increment_frames_drawn();
bool notify_frame_server_synced();
+ void set_freeze_time_scale(bool p_frozen);
+
Engine();
virtual ~Engine();
};
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index 64af836d4d..092177bc15 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -214,36 +214,36 @@ String ProjectSettings::localize_path(const String &p_path) const {
}
void ProjectSettings::set_initial_value(const String &p_name, const Variant &p_value) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
// Duplicate so that if value is array or dictionary, changing the setting will not change the stored initial value.
props[p_name].initial = p_value.duplicate();
}
void ProjectSettings::set_restart_if_changed(const String &p_name, bool p_restart) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props[p_name].restart_if_changed = p_restart;
}
void ProjectSettings::set_as_basic(const String &p_name, bool p_basic) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props[p_name].basic = p_basic;
}
void ProjectSettings::set_as_internal(const String &p_name, bool p_internal) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props[p_name].internal = p_internal;
}
void ProjectSettings::set_ignore_value_in_docs(const String &p_name, bool p_ignore) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
#ifdef DEBUG_METHODS_ENABLED
props[p_name].ignore_value_in_docs = p_ignore;
#endif
}
bool ProjectSettings::get_ignore_value_in_docs(const String &p_name) const {
- ERR_FAIL_COND_V_MSG(!props.has(p_name), false, "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_V_MSG(!props.has(p_name), false, vformat("Request for nonexistent project setting: '%s'.", p_name));
#ifdef DEBUG_METHODS_ENABLED
return props[p_name].ignore_value_in_docs;
#else
@@ -371,7 +371,7 @@ Variant ProjectSettings::get_setting_with_override(const StringName &p_name) con
}
if (!props.has(name)) {
- WARN_PRINT("Property not found: " + String(name));
+ WARN_PRINT(vformat("Property not found: '%s'.", String(name)));
return Variant();
}
return props[name].variant;
@@ -565,7 +565,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b
if (!p_main_pack.is_empty()) {
bool ok = _load_resource_pack(p_main_pack);
- ERR_FAIL_COND_V_MSG(!ok, ERR_CANT_OPEN, "Cannot open resource pack '" + p_main_pack + "'.");
+ ERR_FAIL_COND_V_MSG(!ok, ERR_CANT_OPEN, vformat("Cannot open resource pack '%s'.", p_main_pack));
Error err = _load_settings_text_or_binary("res://project.godot", "res://project.binary");
if (err == OK && !p_ignore_override) {
@@ -644,7 +644,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b
// or, if requested (`p_upwards`) in parent directories.
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- ERR_FAIL_COND_V_MSG(d.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(d.is_null(), ERR_CANT_CREATE, vformat("Cannot create DirAccess for path '%s'.", p_path));
d->change_dir(p_path);
String current_dir = d->get_current_dir();
@@ -748,7 +748,7 @@ Error ProjectSettings::_load_settings_binary(const String &p_path) {
f->get_buffer(d.ptrw(), vlen);
Variant value;
err = decode_variant(value, d.ptr(), d.size(), nullptr, true);
- ERR_CONTINUE_MSG(err != OK, "Error decoding property: " + key + ".");
+ ERR_CONTINUE_MSG(err != OK, vformat("Error decoding property: '%s'.", key));
set(key, value);
}
@@ -790,7 +790,7 @@ Error ProjectSettings::_load_settings_text(const String &p_path) {
last_save_time = FileAccess::get_modified_time(get_resource_path().path_join("project.godot"));
return OK;
}
- ERR_FAIL_COND_V_MSG(err != OK, err, "Error parsing " + p_path + " at line " + itos(lines) + ": " + error_text + " File might be corrupted.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Error parsing '%s' at line %d: %s File might be corrupted.", p_path, lines, error_text));
if (!assign.is_empty()) {
if (section.is_empty() && assign == "config_version") {
@@ -816,7 +816,7 @@ Error ProjectSettings::_load_settings_text_or_binary(const String &p_text_path,
return OK;
} else if (err != ERR_FILE_NOT_FOUND) {
// If the file exists but can't be loaded, we want to know it.
- ERR_PRINT("Couldn't load file '" + p_bin_path + "', error code " + itos(err) + ".");
+ ERR_PRINT(vformat("Couldn't load file '%s', error code %d.", p_bin_path, err));
}
// Fallback to text-based project.godot file if binary was not found.
@@ -824,7 +824,7 @@ Error ProjectSettings::_load_settings_text_or_binary(const String &p_text_path,
if (err == OK) {
return OK;
} else if (err != ERR_FILE_NOT_FOUND) {
- ERR_PRINT("Couldn't load file '" + p_text_path + "', error code " + itos(err) + ".");
+ ERR_PRINT(vformat("Couldn't load file '%s', error code %d.", p_text_path, err));
}
return err;
@@ -838,17 +838,17 @@ Error ProjectSettings::load_custom(const String &p_path) {
}
int ProjectSettings::get_order(const String &p_name) const {
- ERR_FAIL_COND_V_MSG(!props.has(p_name), -1, "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_V_MSG(!props.has(p_name), -1, vformat("Request for nonexistent project setting: '%s'.", p_name));
return props[p_name].order;
}
void ProjectSettings::set_order(const String &p_name, int p_order) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props[p_name].order = p_order;
}
void ProjectSettings::set_builtin_order(const String &p_name) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
if (props[p_name].order >= NO_BUILTIN_ORDER_BASE) {
props[p_name].order = last_builtin_order++;
}
@@ -856,12 +856,12 @@ void ProjectSettings::set_builtin_order(const String &p_name) {
bool ProjectSettings::is_builtin_setting(const String &p_name) const {
// Return true because a false negative is worse than a false positive.
- ERR_FAIL_COND_V_MSG(!props.has(p_name), true, "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_V_MSG(!props.has(p_name), true, vformat("Request for nonexistent project setting: '%s'.", p_name));
return props[p_name].order < NO_BUILTIN_ORDER_BASE;
}
void ProjectSettings::clear(const String &p_name) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props.erase(p_name);
}
@@ -876,7 +876,7 @@ Error ProjectSettings::save() {
Error ProjectSettings::_save_settings_binary(const String &p_file, const RBMap<String, List<String>> &p_props, const CustomMap &p_custom, const String &p_custom_features) {
Error err;
Ref<FileAccess> file = FileAccess::open(p_file, FileAccess::WRITE, &err);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Couldn't save project.binary at " + p_file + ".");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Couldn't save project.binary at '%s'.", p_file));
uint8_t hdr[4] = { 'E', 'C', 'F', 'G' };
file->store_buffer(hdr, 4);
@@ -946,7 +946,7 @@ Error ProjectSettings::_save_settings_text(const String &p_file, const RBMap<Str
Error err;
Ref<FileAccess> file = FileAccess::open(p_file, FileAccess::WRITE, &err);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Couldn't save project.godot - " + p_file + ".");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Couldn't save project.godot - %s.", p_file));
file->store_line("; Engine configuration file.");
file->store_line("; It's best edited using the editor UI and not directly,");
@@ -1119,7 +1119,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust
} else if (p_path.ends_with(".binary")) {
return _save_settings_binary(p_path, save_props, p_custom, save_features);
} else {
- ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, "Unknown config file format: " + p_path);
+ ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, vformat("Unknown config file format: '%s'.", p_path));
}
}
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index 9349aafd1a..6ef466bee0 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -75,7 +75,7 @@ Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hi
Error err = OK;
Ref<Resource> ret = ::ResourceLoader::load(p_path, p_type_hint, ResourceFormatLoader::CacheMode(p_cache_mode), &err);
- ERR_FAIL_COND_V_MSG(err != OK, ret, "Error loading resource: '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, ret, vformat("Error loading resource: '%s'.", p_path));
return ret;
}
@@ -1315,7 +1315,7 @@ void Thread::_start_func(void *ud) {
}
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_MSG("Could not call function '" + func_name + "' to start thread " + t->get_id() + ": " + Variant::get_callable_error_text(t->target_callable, nullptr, 0, ce) + ".");
+ ERR_FAIL_MSG(vformat("Could not call function '%s' to start thread %d: %s.", func_name, t->get_id(), Variant::get_callable_error_text(t->target_callable, nullptr, 0, ce)));
}
}
@@ -1540,7 +1540,7 @@ TypedArray<Dictionary> ClassDB::class_get_method_list(const StringName &p_class,
return ret;
}
-Variant ClassDB::class_call_static_method(const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) {
+Variant ClassDB::class_call_static(const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) {
if (p_argcount < 2) {
r_call_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
return Variant::NIL;
@@ -1681,7 +1681,7 @@ void ClassDB::_bind_methods() {
::ClassDB::bind_method(D_METHOD("class_get_method_list", "class", "no_inheritance"), &ClassDB::class_get_method_list, DEFVAL(false));
- ::ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "class_call_static_method", &ClassDB::class_call_static_method, MethodInfo("class_call_static_method", PropertyInfo(Variant::STRING_NAME, "class"), PropertyInfo(Variant::STRING_NAME, "method")));
+ ::ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "class_call_static", &ClassDB::class_call_static, MethodInfo("class_call_static", PropertyInfo(Variant::STRING_NAME, "class"), PropertyInfo(Variant::STRING_NAME, "method")));
::ClassDB::bind_method(D_METHOD("class_get_integer_constant_list", "class", "no_inheritance"), &ClassDB::class_get_integer_constant_list, DEFVAL(false));
@@ -1814,8 +1814,8 @@ Object *Engine::get_singleton_object(const StringName &p_name) const {
}
void Engine::register_singleton(const StringName &p_name, Object *p_object) {
- ERR_FAIL_COND_MSG(has_singleton(p_name), "Singleton already registered: " + String(p_name));
- ERR_FAIL_COND_MSG(!String(p_name).is_valid_ascii_identifier(), "Singleton name is not a valid identifier: " + p_name);
+ ERR_FAIL_COND_MSG(has_singleton(p_name), vformat("Singleton already registered: '%s'.", String(p_name)));
+ ERR_FAIL_COND_MSG(!String(p_name).is_valid_ascii_identifier(), vformat("Singleton name is not a valid identifier: '%s'.", p_name));
::Engine::Singleton s;
s.class_name = p_name;
s.name = p_name;
@@ -1825,8 +1825,8 @@ void Engine::register_singleton(const StringName &p_name, Object *p_object) {
}
void Engine::unregister_singleton(const StringName &p_name) {
- ERR_FAIL_COND_MSG(!has_singleton(p_name), "Attempt to remove unregistered singleton: " + String(p_name));
- ERR_FAIL_COND_MSG(!::Engine::get_singleton()->is_singleton_user_created(p_name), "Attempt to remove non-user created singleton: " + String(p_name));
+ ERR_FAIL_COND_MSG(!has_singleton(p_name), vformat("Attempt to remove unregistered singleton: '%s'.", String(p_name)));
+ ERR_FAIL_COND_MSG(!::Engine::get_singleton()->is_singleton_user_created(p_name), vformat("Attempt to remove non-user created singleton: '%s'.", String(p_name)));
::Engine::get_singleton()->remove_singleton(p_name);
}
@@ -1970,14 +1970,14 @@ bool EngineDebugger::is_active() {
void EngineDebugger::register_profiler(const StringName &p_name, Ref<EngineProfiler> p_profiler) {
ERR_FAIL_COND(p_profiler.is_null());
ERR_FAIL_COND_MSG(p_profiler->is_bound(), "Profiler already registered.");
- ERR_FAIL_COND_MSG(profilers.has(p_name) || has_profiler(p_name), "Profiler name already in use: " + p_name);
+ ERR_FAIL_COND_MSG(profilers.has(p_name) || has_profiler(p_name), vformat("Profiler name already in use: '%s'.", p_name));
Error err = p_profiler->bind(p_name);
- ERR_FAIL_COND_MSG(err != OK, "Profiler failed to register with error: " + itos(err));
+ ERR_FAIL_COND_MSG(err != OK, vformat("Profiler failed to register with error: %d.", err));
profilers.insert(p_name, p_profiler);
}
void EngineDebugger::unregister_profiler(const StringName &p_name) {
- ERR_FAIL_COND_MSG(!profilers.has(p_name), "Profiler not registered: " + p_name);
+ ERR_FAIL_COND_MSG(!profilers.has(p_name), vformat("Profiler not registered: '%s'.", p_name));
profilers[p_name]->unbind();
profilers.erase(p_name);
}
@@ -2001,7 +2001,7 @@ void EngineDebugger::profiler_enable(const StringName &p_name, bool p_enabled, c
}
void EngineDebugger::register_message_capture(const StringName &p_name, const Callable &p_callable) {
- ERR_FAIL_COND_MSG(captures.has(p_name) || has_capture(p_name), "Capture already registered: " + p_name);
+ ERR_FAIL_COND_MSG(captures.has(p_name) || has_capture(p_name), vformat("Capture already registered: '%s'.", p_name));
captures.insert(p_name, p_callable);
Callable &c = captures[p_name];
::EngineDebugger::Capture capture(&c, &EngineDebugger::call_capture);
@@ -2009,7 +2009,7 @@ void EngineDebugger::register_message_capture(const StringName &p_name, const Ca
}
void EngineDebugger::unregister_message_capture(const StringName &p_name) {
- ERR_FAIL_COND_MSG(!captures.has(p_name), "Capture not registered: " + p_name);
+ ERR_FAIL_COND_MSG(!captures.has(p_name), vformat("Capture not registered: '%s'.", p_name));
::EngineDebugger::unregister_message_capture(p_name);
captures.erase(p_name);
}
@@ -2043,8 +2043,8 @@ Error EngineDebugger::call_capture(void *p_user, const String &p_cmd, const Arra
Variant retval;
Callable::CallError err;
capture.callp(args, 2, retval, err);
- ERR_FAIL_COND_V_MSG(err.error != Callable::CallError::CALL_OK, FAILED, "Error calling 'capture' to callable: " + Variant::get_callable_error_text(capture, args, 2, err));
- ERR_FAIL_COND_V_MSG(retval.get_type() != Variant::BOOL, FAILED, "Error calling 'capture' to callable: " + String(capture) + ". Return type is not bool.");
+ ERR_FAIL_COND_V_MSG(err.error != Callable::CallError::CALL_OK, FAILED, vformat("Error calling 'capture' to callable: %s.", Variant::get_callable_error_text(capture, args, 2, err)));
+ ERR_FAIL_COND_V_MSG(retval.get_type() != Variant::BOOL, FAILED, vformat("Error calling 'capture' to callable: '%s'. Return type is not bool.", String(capture)));
r_captured = retval;
return OK;
}
diff --git a/core/core_bind.h b/core/core_bind.h
index b690376551..430ecdc906 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -484,7 +484,7 @@ public:
int class_get_method_argument_count(const StringName &p_class, const StringName &p_method, bool p_no_inheritance = false) const;
TypedArray<Dictionary> class_get_method_list(const StringName &p_class, bool p_no_inheritance = false) const;
- Variant class_call_static_method(const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error);
+ Variant class_call_static(const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error);
PackedStringArray class_get_integer_constant_list(const StringName &p_class, bool p_no_inheritance = false) const;
bool class_has_integer_constant(const StringName &p_class, const StringName &p_name) const;
diff --git a/core/crypto/crypto.cpp b/core/crypto/crypto.cpp
index 62bacadf91..30003e4ea8 100644
--- a/core/crypto/crypto.cpp
+++ b/core/crypto/crypto.cpp
@@ -240,7 +240,7 @@ Error ResourceFormatSaverCrypto::save(const Ref<Resource> &p_resource, const Str
} else {
ERR_FAIL_V(ERR_INVALID_PARAMETER);
}
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save Crypto resource to file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot save Crypto resource to file '%s'.", p_path));
return OK;
}
diff --git a/core/crypto/crypto_core.cpp b/core/crypto/crypto_core.cpp
index 69a83284cc..13852d5177 100644
--- a/core/crypto/crypto_core.cpp
+++ b/core/crypto/crypto_core.cpp
@@ -70,7 +70,7 @@ int CryptoCore::RandomGenerator::_entropy_poll(void *p_data, unsigned char *r_bu
Error CryptoCore::RandomGenerator::init() {
int ret = mbedtls_ctr_drbg_seed((mbedtls_ctr_drbg_context *)ctx, mbedtls_entropy_func, (mbedtls_entropy_context *)entropy, nullptr, 0);
if (ret) {
- ERR_FAIL_COND_V_MSG(ret, FAILED, " failed\n ! mbedtls_ctr_drbg_seed returned an error" + itos(ret));
+ ERR_FAIL_COND_V_MSG(ret, FAILED, vformat(" failed\n ! mbedtls_ctr_drbg_seed returned an error %d.", ret));
}
return OK;
}
@@ -78,7 +78,7 @@ Error CryptoCore::RandomGenerator::init() {
Error CryptoCore::RandomGenerator::get_random_bytes(uint8_t *r_buffer, size_t p_bytes) {
ERR_FAIL_NULL_V(ctx, ERR_UNCONFIGURED);
int ret = mbedtls_ctr_drbg_random((mbedtls_ctr_drbg_context *)ctx, r_buffer, p_bytes);
- ERR_FAIL_COND_V_MSG(ret, FAILED, " failed\n ! mbedtls_ctr_drbg_seed returned an error" + itos(ret));
+ ERR_FAIL_COND_V_MSG(ret, FAILED, vformat(" failed\n ! mbedtls_ctr_drbg_seed returned an error %d.", ret));
return OK;
}
diff --git a/core/debugger/engine_debugger.cpp b/core/debugger/engine_debugger.cpp
index 97a020e4c3..6d2868cef2 100644
--- a/core/debugger/engine_debugger.cpp
+++ b/core/debugger/engine_debugger.cpp
@@ -46,12 +46,12 @@ HashMap<String, EngineDebugger::CreatePeerFunc> EngineDebugger::protocols;
void (*EngineDebugger::allow_focus_steal_fn)();
void EngineDebugger::register_profiler(const StringName &p_name, const Profiler &p_func) {
- ERR_FAIL_COND_MSG(profilers.has(p_name), "Profiler already registered: " + p_name);
+ ERR_FAIL_COND_MSG(profilers.has(p_name), vformat("Profiler already registered: '%s'.", p_name));
profilers.insert(p_name, p_func);
}
void EngineDebugger::unregister_profiler(const StringName &p_name) {
- ERR_FAIL_COND_MSG(!profilers.has(p_name), "Profiler not registered: " + p_name);
+ ERR_FAIL_COND_MSG(!profilers.has(p_name), vformat("Profiler not registered: '%s'.", p_name));
Profiler &p = profilers[p_name];
if (p.active && p.toggle) {
p.toggle(p.data, false, Array());
@@ -61,22 +61,22 @@ void EngineDebugger::unregister_profiler(const StringName &p_name) {
}
void EngineDebugger::register_message_capture(const StringName &p_name, Capture p_func) {
- ERR_FAIL_COND_MSG(captures.has(p_name), "Capture already registered: " + p_name);
+ ERR_FAIL_COND_MSG(captures.has(p_name), vformat("Capture already registered: '%s'.", p_name));
captures.insert(p_name, p_func);
}
void EngineDebugger::unregister_message_capture(const StringName &p_name) {
- ERR_FAIL_COND_MSG(!captures.has(p_name), "Capture not registered: " + p_name);
+ ERR_FAIL_COND_MSG(!captures.has(p_name), vformat("Capture not registered: '%s'.", p_name));
captures.erase(p_name);
}
void EngineDebugger::register_uri_handler(const String &p_protocol, CreatePeerFunc p_func) {
- ERR_FAIL_COND_MSG(protocols.has(p_protocol), "Protocol handler already registered: " + p_protocol);
+ ERR_FAIL_COND_MSG(protocols.has(p_protocol), vformat("Protocol handler already registered: '%s'.", p_protocol));
protocols.insert(p_protocol, p_func);
}
void EngineDebugger::profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts) {
- ERR_FAIL_COND_MSG(!profilers.has(p_name), "Can't change profiler state, no profiler: " + p_name);
+ ERR_FAIL_COND_MSG(!profilers.has(p_name), vformat("Can't change profiler state, no profiler: '%s'.", p_name));
Profiler &p = profilers[p_name];
if (p.toggle) {
p.toggle(p.data, p_enabled, p_opts);
@@ -85,7 +85,7 @@ void EngineDebugger::profiler_enable(const StringName &p_name, bool p_enabled, c
}
void EngineDebugger::profiler_add_frame_data(const StringName &p_name, const Array &p_data) {
- ERR_FAIL_COND_MSG(!profilers.has(p_name), "Can't add frame data, no profiler: " + p_name);
+ ERR_FAIL_COND_MSG(!profilers.has(p_name), vformat("Can't add frame data, no profiler: '%s'.", p_name));
Profiler &p = profilers[p_name];
if (p.add) {
p.add(p.data, p_data);
@@ -106,7 +106,7 @@ bool EngineDebugger::has_capture(const StringName &p_name) {
Error EngineDebugger::capture_parse(const StringName &p_name, const String &p_msg, const Array &p_args, bool &r_captured) {
r_captured = false;
- ERR_FAIL_COND_V_MSG(!captures.has(p_name), ERR_UNCONFIGURED, "Capture not registered: " + p_name);
+ ERR_FAIL_COND_V_MSG(!captures.has(p_name), ERR_UNCONFIGURED, vformat("Capture not registered: '%s'.", p_name));
const Capture &cap = captures[p_name];
return cap.capture(cap.data, p_msg, p_args, r_captured);
}
@@ -164,7 +164,7 @@ void EngineDebugger::initialize(const String &p_uri, bool p_skip_breakpoints, co
for (int i = 0; i < p_breakpoints.size(); i++) {
const String &bp = p_breakpoints[i];
int sp = bp.rfind(":");
- ERR_CONTINUE_MSG(sp == -1, "Invalid breakpoint: '" + bp + "', expected file:line format.");
+ ERR_CONTINUE_MSG(sp == -1, vformat("Invalid breakpoint: '%s', expected file:line format.", bp));
singleton_script_debugger->insert_breakpoint(bp.substr(sp + 1, bp.length()).to_int(), bp.substr(0, sp));
}
diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp
index fc1b7b74f9..a02354b377 100644
--- a/core/debugger/remote_debugger.cpp
+++ b/core/debugger/remote_debugger.cpp
@@ -79,7 +79,7 @@ public:
for (int i = 0; i < custom_monitor_names.size(); i++) {
Variant monitor_value = performance->call("get_custom_monitor", custom_monitor_names[i]);
if (!monitor_value.is_num()) {
- ERR_PRINT("Value of custom monitor '" + String(custom_monitor_names[i]) + "' is not a number");
+ ERR_PRINT(vformat("Value of custom monitor '%s' is not a number.", String(custom_monitor_names[i])));
arr[i + max] = Variant();
} else {
arr[i + max] = monitor_value;
@@ -569,7 +569,7 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
bool captured = false;
ERR_CONTINUE(_try_capture(command, data, captured) != OK);
if (!captured) {
- WARN_PRINT("Unknown message received from debugger: " + command);
+ WARN_PRINT(vformat("Unknown message received from debugger: %s.", command));
}
}
} else {
diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp
index 9dca47a0b4..793db7330f 100644
--- a/core/debugger/remote_debugger_peer.cpp
+++ b/core/debugger/remote_debugger_peer.cpp
@@ -178,7 +178,7 @@ Error RemoteDebuggerPeerTCP::connect_to_host(const String &p_host, uint16_t p_po
}
if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
- ERR_PRINT("Remote Debugger: Unable to connect. Status: " + String::num(tcp_client->get_status()) + ".");
+ ERR_PRINT(vformat("Remote Debugger: Unable to connect. Status: %s.", String::num(tcp_client->get_status())));
return FAILED;
}
connected = true;
diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp
index c5f7502c12..7263cafdf3 100644
--- a/core/extension/extension_api_dump.cpp
+++ b/core/extension/extension_api_dump.cpp
@@ -1363,7 +1363,7 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_
return true; // May just not have this array and its still good. Probably added recently.
}
bool failed = false;
- ERR_FAIL_COND_V_MSG(!p_new_api.has(p_base_array), false, "New API lacks base array: " + p_base_array);
+ ERR_FAIL_COND_V_MSG(!p_new_api.has(p_base_array), false, vformat("New API lacks base array: %s", p_base_array));
Array new_api = p_new_api[p_base_array];
HashMap<String, Dictionary> new_api_assoc;
@@ -1371,6 +1371,9 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_
Dictionary elem = var;
ERR_FAIL_COND_V_MSG(!elem.has(p_name_field), false, vformat("Validate extension JSON: Element of base_array '%s' is missing field '%s'. This is a bug.", base_array, p_name_field));
String name = elem[p_name_field];
+ if (name.is_valid_float()) {
+ name = name.trim_suffix(".0"); // Make "integers" stringified as integers.
+ }
if (p_compare_operators && elem.has("right_type")) {
name += " " + String(elem["right_type"]);
}
@@ -1386,6 +1389,9 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_
continue;
}
String name = old_elem[p_name_field];
+ if (name.is_valid_float()) {
+ name = name.trim_suffix(".0"); // Make "integers" stringified as integers.
+ }
if (p_compare_operators && old_elem.has("right_type")) {
name += " " + String(old_elem["right_type"]);
}
@@ -1515,7 +1521,7 @@ static bool compare_sub_dict_array(HashSet<String> &r_removed_classes_registered
return true; // May just not have this array and its still good. Probably added recently or optional.
}
bool failed = false;
- ERR_FAIL_COND_V_MSG(!p_new_api.has(p_outer), false, "New API lacks base array: " + p_outer);
+ ERR_FAIL_COND_V_MSG(!p_new_api.has(p_outer), false, vformat("New API lacks base array: %s", p_outer));
Array new_api = p_new_api[p_outer];
HashMap<String, Dictionary> new_api_assoc;
diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp
index 7cba5cb161..05ba114a96 100644
--- a/core/extension/gdextension.cpp
+++ b/core/extension/gdextension.cpp
@@ -355,8 +355,8 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
StringName parent_class_name = *reinterpret_cast<const StringName *>(p_parent_class_name);
- ERR_FAIL_COND_MSG(!String(class_name).is_valid_unicode_identifier(), "Attempt to register extension class '" + class_name + "', which is not a valid class identifier.");
- ERR_FAIL_COND_MSG(ClassDB::class_exists(class_name), "Attempt to register extension class '" + class_name + "', which appears to be already registered.");
+ ERR_FAIL_COND_MSG(!String(class_name).is_valid_unicode_identifier(), vformat("Attempt to register extension class '%s', which is not a valid class identifier.", class_name));
+ ERR_FAIL_COND_MSG(ClassDB::class_exists(class_name), vformat("Attempt to register extension class '%s', which appears to be already registered.", class_name));
Extension *parent_extension = nullptr;
@@ -370,7 +370,7 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
//inheriting from engine class
}
} else {
- ERR_FAIL_MSG("Attempt to register an extension class '" + String(class_name) + "' using non-existing parent class '" + String(parent_class_name) + "'.");
+ ERR_FAIL_MSG(vformat("Attempt to register an extension class '%s' using non-existing parent class '%s'.", String(class_name), String(parent_class_name)));
}
#ifdef TOOLS_ENABLED
@@ -463,7 +463,7 @@ void GDExtension::_register_extension_class_method(GDExtensionClassLibraryPtr p_
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
StringName method_name = *reinterpret_cast<const StringName *>(p_method_info->name);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension method '" + String(method_name) + "' for unexisting class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension method '%s' for unexisting class '%s'.", String(method_name), class_name));
#ifdef TOOLS_ENABLED
Extension *extension = &self->extension_classes[class_name];
@@ -513,7 +513,7 @@ void GDExtension::_register_extension_class_integer_constant(GDExtensionClassLib
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
StringName enum_name = *reinterpret_cast<const StringName *>(p_enum_name);
StringName constant_name = *reinterpret_cast<const StringName *>(p_constant_name);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension constant '" + constant_name + "' for unexisting class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension constant '%s' for unexisting class '%s'.", constant_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@@ -537,7 +537,7 @@ void GDExtension::_register_extension_class_property_indexed(GDExtensionClassLib
StringName setter = *reinterpret_cast<const StringName *>(p_setter);
StringName getter = *reinterpret_cast<const StringName *>(p_getter);
String property_name = *reinterpret_cast<const StringName *>(p_info->name);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property '" + property_name + "' for unexisting class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension class property '%s' for unexisting class '%s'.", property_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@@ -558,7 +558,7 @@ void GDExtension::_register_extension_class_property_group(GDExtensionClassLibra
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
String group_name = *reinterpret_cast<const String *>(p_group_name);
String prefix = *reinterpret_cast<const String *>(p_prefix);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property group '" + group_name + "' for unexisting class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension class property group '%s' for unexisting class '%s'.", group_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@@ -577,7 +577,7 @@ void GDExtension::_register_extension_class_property_subgroup(GDExtensionClassLi
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
String subgroup_name = *reinterpret_cast<const String *>(p_subgroup_name);
String prefix = *reinterpret_cast<const String *>(p_prefix);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property subgroup '" + subgroup_name + "' for unexisting class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension class property subgroup '%s' for unexisting class '%s'.", subgroup_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@@ -595,7 +595,7 @@ void GDExtension::_register_extension_class_signal(GDExtensionClassLibraryPtr p_
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
StringName signal_name = *reinterpret_cast<const StringName *>(p_signal_name);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class signal '" + signal_name + "' for unexisting class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension class signal '%s' for unexisting class '%s'.", signal_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@@ -618,7 +618,7 @@ void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_libra
GDExtension *self = reinterpret_cast<GDExtension *>(p_library);
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to unregister unexisting extension class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to unregister unexisting extension class '%s'.", class_name));
Extension *ext = &self->extension_classes[class_name];
#ifdef TOOLS_ENABLED
@@ -626,7 +626,7 @@ void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_libra
self->_clear_extension(ext);
}
#endif
- ERR_FAIL_COND_MSG(ext->gdextension.children.size(), "Attempt to unregister class '" + class_name + "' while other extension classes inherit from it.");
+ ERR_FAIL_COND_MSG(ext->gdextension.children.size(), vformat("Attempt to unregister class '%s' while other extension classes inherit from it.", class_name));
#ifdef TOOLS_ENABLED
ClassDB::unregister_extension_class(class_name, !ext->is_reloading);
@@ -664,13 +664,13 @@ void GDExtension::_get_library_path(GDExtensionClassLibraryPtr p_library, GDExte
HashMap<StringName, GDExtensionInterfaceFunctionPtr> GDExtension::gdextension_interface_functions;
void GDExtension::register_interface_function(const StringName &p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer) {
- ERR_FAIL_COND_MSG(gdextension_interface_functions.has(p_function_name), "Attempt to register interface function '" + p_function_name + "', which appears to be already registered.");
+ ERR_FAIL_COND_MSG(gdextension_interface_functions.has(p_function_name), vformat("Attempt to register interface function '%s', which appears to be already registered.", p_function_name));
gdextension_interface_functions.insert(p_function_name, p_function_pointer);
}
GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(const StringName &p_function_name) {
GDExtensionInterfaceFunctionPtr *function = gdextension_interface_functions.getptr(p_function_name);
- ERR_FAIL_NULL_V_MSG(function, nullptr, "Attempt to get non-existent interface function: " + String(p_function_name) + ".");
+ ERR_FAIL_NULL_V_MSG(function, nullptr, vformat("Attempt to get non-existent interface function: '%s'.", String(p_function_name)));
return *function;
}
@@ -680,8 +680,8 @@ Error GDExtension::open_library(const String &p_path, const Ref<GDExtensionLoade
Error err = loader->open_library(p_path);
- ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + p_path);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + p_path);
+ ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, vformat("GDExtension dynamic library not found: '%s'.", p_path));
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Can't open GDExtension dynamic library: '%s'.", p_path));
err = loader->initialize(&gdextension_get_proc_address, this, &initialization);
diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp
index 91f0b4a2de..203f7960dc 100644
--- a/core/extension/gdextension_interface.cpp
+++ b/core/extension/gdextension_interface.cpp
@@ -1526,7 +1526,7 @@ static GDExtensionMethodBindPtr gdextension_classdb_get_method_bind(GDExtensionC
#endif
if (!mb && exists) {
- ERR_PRINT("Method '" + classname + "." + methodname + "' has changed and no compatibility fallback has been provided. Please open an issue.");
+ ERR_PRINT(vformat("Method '%s.%s' has changed and no compatibility fallback has been provided. Please open an issue.", classname, methodname));
return nullptr;
}
ERR_FAIL_NULL_V(mb, nullptr);
diff --git a/core/extension/gdextension_library_loader.cpp b/core/extension/gdextension_library_loader.cpp
index d5f2eb668f..17200916ba 100644
--- a/core/extension/gdextension_library_loader.cpp
+++ b/core/extension/gdextension_library_loader.cpp
@@ -219,7 +219,7 @@ Error GDExtensionLibraryLoader::initialize(GDExtensionInterfaceGetProcAddress p_
Error err = OS::get_singleton()->get_dynamic_library_symbol_handle(library, entry_symbol, entry_funcptr, false);
if (err != OK) {
- ERR_PRINT("GDExtension entry point '" + entry_symbol + "' not found in library " + library_path);
+ ERR_PRINT(vformat("GDExtension entry point '%s' not found in library %s.", entry_symbol, library_path));
return err;
}
@@ -230,7 +230,7 @@ Error GDExtensionLibraryLoader::initialize(GDExtensionInterfaceGetProcAddress p_
if (ret) {
return OK;
} else {
- ERR_PRINT("GDExtension initialization function '" + entry_symbol + "' returned an error.");
+ ERR_PRINT(vformat("GDExtension initialization function '%s' returned an error.", entry_symbol));
return FAILED;
}
}
@@ -272,12 +272,12 @@ Error GDExtensionLibraryLoader::parse_gdextension_file(const String &p_path) {
Error err = config->load(p_path);
if (err != OK) {
- ERR_PRINT("Error loading GDExtension configuration file: " + p_path);
+ ERR_PRINT(vformat("Error loading GDExtension configuration file: '%s'.", p_path));
return err;
}
if (!config->has_section_key("configuration", "entry_symbol")) {
- ERR_PRINT("GDExtension configuration file must contain a \"configuration/entry_symbol\" key: " + p_path);
+ ERR_PRINT(vformat("GDExtension configuration file must contain a \"configuration/entry_symbol\" key: '%s'.", p_path));
return ERR_INVALID_DATA;
}
@@ -296,7 +296,7 @@ Error GDExtensionLibraryLoader::parse_gdextension_file(const String &p_path) {
}
}
} else {
- ERR_PRINT("GDExtension configuration file must contain a \"configuration/compatibility_minimum\" key: " + p_path);
+ ERR_PRINT(vformat("GDExtension configuration file must contain a \"configuration/compatibility_minimum\" key: '%s'.", p_path));
return ERR_INVALID_DATA;
}
diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp
index fff938858f..0dc13cf375 100644
--- a/core/extension/gdextension_manager.cpp
+++ b/core/extension/gdextension_manager.cpp
@@ -258,7 +258,7 @@ void GDExtensionManager::load_extensions() {
String s = f->get_line().strip_edges();
if (!s.is_empty()) {
LoadStatus err = load_extension(s);
- ERR_CONTINUE_MSG(err == LOAD_STATUS_FAILED, "Error loading extension: " + s);
+ ERR_CONTINUE_MSG(err == LOAD_STATUS_FAILED, vformat("Error loading extension: '%s'.", s));
}
}
diff --git a/core/input/input.cpp b/core/input/input.cpp
index 6261a435fa..7e2227c729 100644
--- a/core/input/input.cpp
+++ b/core/input/input.cpp
@@ -87,11 +87,50 @@ Input *Input::get_singleton() {
void Input::set_mouse_mode(MouseMode p_mode) {
ERR_FAIL_INDEX((int)p_mode, 5);
+
+ if (p_mode == mouse_mode) {
+ return;
+ }
+
+ // Allow to be set even if overridden, to see if the platform allows the mode.
set_mouse_mode_func(p_mode);
+ mouse_mode = get_mouse_mode_func();
+
+ if (mouse_mode_override_enabled) {
+ set_mouse_mode_func(mouse_mode_override);
+ }
}
Input::MouseMode Input::get_mouse_mode() const {
- return get_mouse_mode_func();
+ return mouse_mode;
+}
+
+void Input::set_mouse_mode_override_enabled(bool p_enabled) {
+ if (p_enabled == mouse_mode_override_enabled) {
+ return;
+ }
+
+ mouse_mode_override_enabled = p_enabled;
+
+ if (p_enabled) {
+ set_mouse_mode_func(mouse_mode_override);
+ mouse_mode_override = get_mouse_mode_func();
+ } else {
+ set_mouse_mode_func(mouse_mode);
+ }
+}
+
+void Input::set_mouse_mode_override(MouseMode p_mode) {
+ ERR_FAIL_INDEX((int)p_mode, 5);
+
+ if (p_mode == mouse_mode_override) {
+ return;
+ }
+
+ if (mouse_mode_override_enabled) {
+ set_mouse_mode_func(p_mode);
+ mouse_mode_override = get_mouse_mode_func();
+ }
}
void Input::_bind_methods() {
@@ -252,6 +291,10 @@ Input::VelocityTrack::VelocityTrack() {
bool Input::is_anything_pressed() const {
_THREAD_SAFE_METHOD_
+ if (disable_input) {
+ return false;
+ }
+
if (!keys_pressed.is_empty() || !joy_buttons_pressed.is_empty() || !mouse_button_mask.is_empty()) {
return true;
}
@@ -267,21 +310,41 @@ bool Input::is_anything_pressed() const {
bool Input::is_key_pressed(Key p_keycode) const {
_THREAD_SAFE_METHOD_
+
+ if (disable_input) {
+ return false;
+ }
+
return keys_pressed.has(p_keycode);
}
bool Input::is_physical_key_pressed(Key p_keycode) const {
_THREAD_SAFE_METHOD_
+
+ if (disable_input) {
+ return false;
+ }
+
return physical_keys_pressed.has(p_keycode);
}
bool Input::is_key_label_pressed(Key p_keycode) const {
_THREAD_SAFE_METHOD_
+
+ if (disable_input) {
+ return false;
+ }
+
return key_label_pressed.has(p_keycode);
}
bool Input::is_mouse_button_pressed(MouseButton p_button) const {
_THREAD_SAFE_METHOD_
+
+ if (disable_input) {
+ return false;
+ }
+
return mouse_button_mask.has_flag(mouse_button_to_mask(p_button));
}
@@ -295,11 +358,21 @@ static JoyButton _combine_device(JoyButton p_value, int p_device) {
bool Input::is_joy_button_pressed(int p_device, JoyButton p_button) const {
_THREAD_SAFE_METHOD_
+
+ if (disable_input) {
+ return false;
+ }
+
return joy_buttons_pressed.has(_combine_device(p_button, p_device));
}
bool Input::is_action_pressed(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
+
+ if (disable_input) {
+ return false;
+ }
+
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return false;
@@ -310,6 +383,11 @@ bool Input::is_action_pressed(const StringName &p_action, bool p_exact) const {
bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
+
+ if (disable_input) {
+ return false;
+ }
+
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return false;
@@ -331,6 +409,11 @@ bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) con
bool Input::is_action_just_released(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
+
+ if (disable_input) {
+ return false;
+ }
+
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return false;
@@ -352,6 +435,11 @@ bool Input::is_action_just_released(const StringName &p_action, bool p_exact) co
float Input::get_action_strength(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action));
+
+ if (disable_input) {
+ return 0.0f;
+ }
+
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return 0.0f;
@@ -366,6 +454,11 @@ float Input::get_action_strength(const StringName &p_action, bool p_exact) const
float Input::get_action_raw_strength(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action));
+
+ if (disable_input) {
+ return 0.0f;
+ }
+
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return 0.0f;
@@ -410,6 +503,11 @@ Vector2 Input::get_vector(const StringName &p_negative_x, const StringName &p_po
float Input::get_joy_axis(int p_device, JoyAxis p_axis) const {
_THREAD_SAFE_METHOD_
+
+ if (disable_input) {
+ return 0;
+ }
+
JoyAxis c = _combine_device(p_axis, p_device);
if (_joy_axis.has(c)) {
return _joy_axis[c];
@@ -936,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.
@@ -1664,6 +1762,14 @@ int Input::get_unused_joy_id() {
return -1;
}
+void Input::set_disable_input(bool p_disable) {
+ disable_input = p_disable;
+}
+
+bool Input::is_input_disabled() const {
+ return disable_input;
+}
+
Input::Input() {
singleton = this;
diff --git a/core/input/input.h b/core/input/input.h
index 95dd623cc0..a189ae7d9a 100644
--- a/core/input/input.h
+++ b/core/input/input.h
@@ -103,6 +103,11 @@ private:
Vector2 mouse_pos;
int64_t mouse_window = 0;
bool legacy_just_pressed_behavior = false;
+ bool disable_input = false;
+
+ MouseMode mouse_mode = MOUSE_MODE_VISIBLE;
+ bool mouse_mode_override_enabled = false;
+ MouseMode mouse_mode_override = MOUSE_MODE_VISIBLE;
struct ActionState {
uint64_t pressed_physics_frame = UINT64_MAX;
@@ -279,6 +284,8 @@ protected:
public:
void set_mouse_mode(MouseMode p_mode);
MouseMode get_mouse_mode() const;
+ void set_mouse_mode_override_enabled(bool p_enabled);
+ void set_mouse_mode_override(MouseMode p_mode);
#ifdef TOOLS_ENABLED
void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
@@ -380,6 +387,9 @@ public:
void set_event_dispatch_function(EventDispatchFunc p_function);
+ void set_disable_input(bool p_disable);
+ bool is_input_disabled() const;
+
Input();
~Input();
};
diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp
index 5b9377fe59..6378f18545 100644
--- a/core/input/input_map.cpp
+++ b/core/input/input_map.cpp
@@ -114,7 +114,7 @@ void InputMap::get_argument_options(const StringName &p_function, int p_idx, Lis
#endif
void InputMap::add_action(const StringName &p_action, float p_deadzone) {
- ERR_FAIL_COND_MSG(input_map.has(p_action), "InputMap already has action \"" + String(p_action) + "\".");
+ ERR_FAIL_COND_MSG(input_map.has(p_action), vformat("InputMap already has action \"%s\".", String(p_action)));
input_map[p_action] = Action();
static int last_id = 1;
input_map[p_action].id = last_id;
diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp
index 2f3fe4deeb..5d2f65ca99 100644
--- a/core/io/dir_access.cpp
+++ b/core/io/dir_access.cpp
@@ -177,7 +177,7 @@ Error DirAccess::make_dir_recursive(const String &p_dir) {
curpath = curpath.path_join(subdirs[i]);
Error err = make_dir(curpath);
if (err != OK && err != ERR_ALREADY_EXISTS) {
- ERR_FAIL_V_MSG(err, "Could not create directory: " + curpath);
+ ERR_FAIL_V_MSG(err, vformat("Could not create directory: '%s'.", curpath));
}
}
@@ -239,7 +239,7 @@ Ref<DirAccess> DirAccess::create_for_path(const String &p_path) {
Ref<DirAccess> DirAccess::open(const String &p_path, Error *r_error) {
Ref<DirAccess> da = create_for_path(p_path);
- ERR_FAIL_COND_V_MSG(da.is_null(), nullptr, "Cannot create DirAccess for path '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(da.is_null(), nullptr, vformat("Cannot create DirAccess for path '%s'.", p_path));
Error err = da->change_dir(p_path);
if (r_error) {
*r_error = err;
@@ -345,10 +345,10 @@ Error DirAccess::copy(const String &p_from, const String &p_to, int p_chmod_flag
Error err;
{
Ref<FileAccess> fsrc = FileAccess::open(p_from, FileAccess::READ, &err);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to open " + p_from);
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Failed to open '%s'.", p_from));
Ref<FileAccess> fdst = FileAccess::open(p_to, FileAccess::WRITE, &err);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to open " + p_to);
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Failed to open '%s'.", p_to));
const size_t copy_buffer_limit = 65536; // 64 KB
@@ -444,11 +444,11 @@ Error DirAccess::_copy_dir(Ref<DirAccess> &p_target_da, const String &p_to, int
String target_dir = p_to + rel_path;
if (!p_target_da->dir_exists(target_dir)) {
Error err = p_target_da->make_dir(target_dir);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + target_dir + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot create directory '%s'.", target_dir));
}
Error err = change_dir(rel_path);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot change current directory to '" + rel_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot change current directory to '%s'.", rel_path));
err = _copy_dir(p_target_da, p_to + rel_path + "/", p_chmod_flags, p_copy_links);
if (err) {
@@ -466,11 +466,11 @@ Error DirAccess::copy_dir(const String &p_from, String p_to, int p_chmod_flags,
ERR_FAIL_COND_V_MSG(!dir_exists(p_from), ERR_FILE_NOT_FOUND, "Source directory doesn't exist.");
Ref<DirAccess> target_da = DirAccess::create_for_path(p_to);
- ERR_FAIL_COND_V_MSG(target_da.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_to + "'.");
+ ERR_FAIL_COND_V_MSG(target_da.is_null(), ERR_CANT_CREATE, vformat("Cannot create DirAccess for path '%s'.", p_to));
if (!target_da->dir_exists(p_to)) {
Error err = target_da->make_dir_recursive(p_to);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + p_to + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot create directory '%s'.", p_to));
}
if (!p_to.ends_with("/")) {
diff --git a/core/io/dir_access.h b/core/io/dir_access.h
index 54e5ddf729..2392944f76 100644
--- a/core/io/dir_access.h
+++ b/core/io/dir_access.h
@@ -116,10 +116,10 @@ public:
Ref<DirAccess> da = create(ACCESS_FILESYSTEM);
if (da->file_exists(p_path)) {
if (da->remove(p_path) != OK) {
- ERR_FAIL_MSG("Cannot remove file or directory: " + p_path);
+ ERR_FAIL_MSG(vformat("Cannot remove file or directory: '%s'.", p_path));
}
} else {
- ERR_FAIL_MSG("Cannot remove non-existent file or directory: " + p_path);
+ ERR_FAIL_MSG(vformat("Cannot remove non-existent file or directory: '%s'.", p_path));
}
}
diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp
index d919243e6b..d8bf645a7d 100644
--- a/core/io/file_access.cpp
+++ b/core/io/file_access.cpp
@@ -459,7 +459,7 @@ Vector<uint8_t> FileAccess::get_buffer(int64_t p_length) const {
}
Error err = data.resize(p_length);
- ERR_FAIL_COND_V_MSG(err != OK, data, "Can't resize data to " + itos(p_length) + " elements.");
+ ERR_FAIL_COND_V_MSG(err != OK, data, vformat("Can't resize data to %d elements.", p_length));
uint8_t *w = data.ptrw();
int64_t len = get_buffer(w, p_length);
@@ -540,7 +540,7 @@ uint64_t FileAccess::get_modified_time(const String &p_file) {
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), 0, vformat("Cannot create FileAccess for path '%s'.", p_file));
uint64_t mt = fa->_get_modified_time(p_file);
return mt;
@@ -552,7 +552,7 @@ BitField<FileAccess::UnixPermissionFlags> FileAccess::get_unix_permissions(const
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), 0, vformat("Cannot create FileAccess for path '%s'.", p_file));
return fa->_get_unix_permissions(p_file);
}
@@ -563,7 +563,7 @@ Error FileAccess::set_unix_permissions(const String &p_file, BitField<FileAccess
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, vformat("Cannot create FileAccess for path '%s'.", p_file));
Error err = fa->_set_unix_permissions(p_file, p_permissions);
return err;
@@ -575,7 +575,7 @@ bool FileAccess::get_hidden_attribute(const String &p_file) {
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), false, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("Cannot create FileAccess for path '%s'.", p_file));
return fa->_get_hidden_attribute(p_file);
}
@@ -586,7 +586,7 @@ Error FileAccess::set_hidden_attribute(const String &p_file, bool p_hidden) {
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, vformat("Cannot create FileAccess for path '%s'.", p_file));
Error err = fa->_set_hidden_attribute(p_file, p_hidden);
return err;
@@ -598,7 +598,7 @@ bool FileAccess::get_read_only_attribute(const String &p_file) {
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), false, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("Cannot create FileAccess for path '%s'.", p_file));
return fa->_get_read_only_attribute(p_file);
}
@@ -609,7 +609,7 @@ Error FileAccess::set_read_only_attribute(const String &p_file, bool p_ro) {
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, vformat("Cannot create FileAccess for path '%s'.", p_file));
Error err = fa->_set_read_only_attribute(p_file, p_ro);
return err;
@@ -697,7 +697,7 @@ Vector<uint8_t> FileAccess::get_file_as_bytes(const String &p_path, Error *r_err
if (r_error) { // if error requested, do not throw error
return Vector<uint8_t>();
}
- ERR_FAIL_V_MSG(Vector<uint8_t>(), "Can't open file from path '" + String(p_path) + "'.");
+ ERR_FAIL_V_MSG(Vector<uint8_t>(), vformat("Can't open file from path '%s'.", String(p_path)));
}
Vector<uint8_t> data;
data.resize(f->get_length());
@@ -715,7 +715,7 @@ String FileAccess::get_file_as_string(const String &p_path, Error *r_error) {
if (r_error) {
return String();
}
- ERR_FAIL_V_MSG(String(), "Can't get file as string from path '" + String(p_path) + "'.");
+ ERR_FAIL_V_MSG(String(), vformat("Can't get file as string from path '%s'.", String(p_path)));
}
String ret;
diff --git a/core/io/file_access_compressed.cpp b/core/io/file_access_compressed.cpp
index 3602baf8c5..f7f2852e0a 100644
--- a/core/io/file_access_compressed.cpp
+++ b/core/io/file_access_compressed.cpp
@@ -58,7 +58,7 @@ Error FileAccessCompressed::open_after_magic(Ref<FileAccess> p_base) {
block_size = f->get_32();
if (block_size == 0) {
f.unref();
- ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Can't open compressed file '" + p_base->get_path() + "' with block size 0, it is corrupted.");
+ ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, vformat("Can't open compressed file '%s' with block size 0, it is corrupted.", p_base->get_path()));
}
read_total = f->get_32();
uint32_t bc = (read_total / block_size) + 1;
diff --git a/core/io/file_access_encrypted.cpp b/core/io/file_access_encrypted.cpp
index 13d1e0c8fc..24be9ef230 100644
--- a/core/io/file_access_encrypted.cpp
+++ b/core/io/file_access_encrypted.cpp
@@ -37,7 +37,7 @@
#include <stdio.h>
Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic) {
- ERR_FAIL_COND_V_MSG(file.is_valid(), ERR_ALREADY_IN_USE, "Can't open file while another file from path '" + file->get_path_absolute() + "' is open.");
+ ERR_FAIL_COND_V_MSG(file.is_valid(), ERR_ALREADY_IN_USE, vformat("Can't open file while another file from path '%s' is open.", file->get_path_absolute()));
ERR_FAIL_COND_V(p_key.size() != 32, ERR_INVALID_PARAMETER);
pos = 0;
diff --git a/core/io/file_access_memory.cpp b/core/io/file_access_memory.cpp
index 1541a5ed4a..8d74011632 100644
--- a/core/io/file_access_memory.cpp
+++ b/core/io/file_access_memory.cpp
@@ -85,7 +85,7 @@ Error FileAccessMemory::open_internal(const String &p_path, int p_mode_flags) {
//name = DirAccess::normalize_path(name);
HashMap<String, Vector<uint8_t>>::Iterator E = files->find(name);
- ERR_FAIL_COND_V_MSG(!E, ERR_FILE_NOT_FOUND, "Can't find file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(!E, ERR_FILE_NOT_FOUND, vformat("Can't find file '%s'.", p_path));
data = E->value.ptrw();
length = E->value.size();
diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp
index 1340382eaa..bfd1a53f3e 100644
--- a/core/io/file_access_pack.cpp
+++ b/core/io/file_access_pack.cpp
@@ -223,8 +223,8 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files,
uint32_t ver_minor = f->get_32();
f->get_32(); // patch number, not used for validation.
- ERR_FAIL_COND_V_MSG(version != PACK_FORMAT_VERSION, false, "Pack version unsupported: " + itos(version) + ".");
- ERR_FAIL_COND_V_MSG(ver_major > VERSION_MAJOR || (ver_major == VERSION_MAJOR && ver_minor > VERSION_MINOR), false, "Pack created with a newer version of the engine: " + itos(ver_major) + "." + itos(ver_minor) + ".");
+ ERR_FAIL_COND_V_MSG(version != PACK_FORMAT_VERSION, false, vformat("Pack version unsupported: %d.", version));
+ ERR_FAIL_COND_V_MSG(ver_major > VERSION_MAJOR || (ver_major == VERSION_MAJOR && ver_minor > VERSION_MINOR), false, vformat("Pack created with a newer version of the engine: %d.%d.", ver_major, ver_minor));
uint32_t pack_flags = f->get_32();
uint64_t file_base = f->get_64();
@@ -386,7 +386,7 @@ void FileAccessPack::close() {
FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFile &p_file) :
pf(p_file),
f(FileAccess::open(pf.pack, FileAccess::READ)) {
- ERR_FAIL_COND_MSG(f.is_null(), "Can't open pack-referenced file '" + String(pf.pack) + "'.");
+ ERR_FAIL_COND_MSG(f.is_null(), vformat("Can't open pack-referenced file '%s'.", String(pf.pack)));
f->seek(pf.offset);
off = pf.offset;
@@ -394,7 +394,7 @@ FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFil
if (pf.encrypted) {
Ref<FileAccessEncrypted> fae;
fae.instantiate();
- ERR_FAIL_COND_MSG(fae.is_null(), "Can't open encrypted pack-referenced file '" + String(pf.pack) + "'.");
+ ERR_FAIL_COND_MSG(fae.is_null(), vformat("Can't open encrypted pack-referenced file '%s'.", String(pf.pack)));
Vector<uint8_t> key;
key.resize(32);
@@ -403,7 +403,7 @@ FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFil
}
Error err = fae->open_and_parse(f, key, FileAccessEncrypted::MODE_READ, false);
- ERR_FAIL_COND_MSG(err, "Can't open encrypted pack-referenced file '" + String(pf.pack) + "'.");
+ ERR_FAIL_COND_MSG(err, vformat("Can't open encrypted pack-referenced file '%s'.", String(pf.pack)));
f = fae;
off = 0;
}
diff --git a/core/io/file_access_zip.cpp b/core/io/file_access_zip.cpp
index b33b7b35c3..41907d1a3f 100644
--- a/core/io/file_access_zip.cpp
+++ b/core/io/file_access_zip.cpp
@@ -116,7 +116,7 @@ void ZipArchive::close_handle(unzFile p_file) const {
}
unzFile ZipArchive::get_file_handle(const String &p_file) const {
- ERR_FAIL_COND_V_MSG(!file_exists(p_file), nullptr, "File '" + p_file + " doesn't exist.");
+ ERR_FAIL_COND_V_MSG(!file_exists(p_file), nullptr, vformat("File '%s' doesn't exist.", p_file));
File file = files[p_file];
zlib_filefunc_def io;
@@ -136,7 +136,7 @@ unzFile ZipArchive::get_file_handle(const String &p_file) const {
io.free_mem = godot_free;
unzFile pkg = unzOpen2(packages[file.package].filename.utf8().get_data(), &io);
- ERR_FAIL_NULL_V_MSG(pkg, nullptr, "Cannot open file '" + packages[file.package].filename + "'.");
+ ERR_FAIL_NULL_V_MSG(pkg, nullptr, vformat("Cannot open file '%s'.", packages[file.package].filename));
int unz_err = unzGoToFilePos(pkg, &file.file_pos);
if (unz_err != UNZ_OK || unzOpenCurrentFile(pkg) != UNZ_OK) {
unzClose(pkg);
diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp
index fc91341bed..9e426c4908 100644
--- a/core/io/http_client.cpp
+++ b/core/io/http_client.cpp
@@ -100,9 +100,9 @@ String HTTPClient::query_string_from_dict(const Dictionary &p_dict) {
Error HTTPClient::verify_headers(const Vector<String> &p_headers) {
for (int i = 0; i < p_headers.size(); i++) {
String sanitized = p_headers[i].strip_edges();
- ERR_FAIL_COND_V_MSG(sanitized.is_empty(), ERR_INVALID_PARAMETER, "Invalid HTTP header at index " + itos(i) + ": empty.");
+ ERR_FAIL_COND_V_MSG(sanitized.is_empty(), ERR_INVALID_PARAMETER, vformat("Invalid HTTP header at index %d: empty.", i));
ERR_FAIL_COND_V_MSG(sanitized.find(":") < 1, ERR_INVALID_PARAMETER,
- "Invalid HTTP header at index " + itos(i) + ": String must contain header-value pair, delimited by ':', but was: " + p_headers[i]);
+ vformat("Invalid HTTP header at index %d: String must contain header-value pair, delimited by ':', but was: '%s'.", i, p_headers[i]));
}
return OK;
diff --git a/core/io/image.cpp b/core/io/image.cpp
index d782af931f..9b5bb058ef 100644
--- a/core/io/image.cpp
+++ b/core/io/image.cpp
@@ -535,7 +535,7 @@ static bool _are_formats_compatible(Image::Format p_format0, Image::Format p_for
}
void Image::convert(Format p_new_format) {
- ERR_FAIL_INDEX_MSG(p_new_format, FORMAT_MAX, "The Image format specified (" + itos(p_new_format) + ") is out of range. See Image's Format enum.");
+ ERR_FAIL_INDEX_MSG(p_new_format, FORMAT_MAX, vformat("The Image format specified (%d) is out of range. See Image's Format enum.", p_new_format));
if (data.size() == 0) {
return;
}
@@ -1132,9 +1132,9 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) {
ERR_FAIL_COND_MSG(p_width <= 0, "Image width must be greater than 0.");
ERR_FAIL_COND_MSG(p_height <= 0, "Image height must be greater than 0.");
- ERR_FAIL_COND_MSG(p_width > MAX_WIDTH, "Image width cannot be greater than " + itos(MAX_WIDTH) + ".");
- ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT, "Image height cannot be greater than " + itos(MAX_HEIGHT) + ".");
- ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS, "Too many pixels for image, maximum is " + itos(MAX_PIXELS));
+ ERR_FAIL_COND_MSG(p_width > MAX_WIDTH, vformat("Image width cannot be greater than %d pixels.", MAX_WIDTH));
+ ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT, vformat("Image height cannot be greater than %d pixels.", MAX_HEIGHT));
+ ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS, vformat("Too many pixels for image, maximum is %d pixels.", MAX_PIXELS));
if (p_width == width && p_height == height) {
return;
@@ -1435,8 +1435,8 @@ void Image::crop_from_point(int p_x, int p_y, int p_width, int p_height) {
ERR_FAIL_COND_MSG(p_y < 0, "Start y position cannot be smaller than 0.");
ERR_FAIL_COND_MSG(p_width <= 0, "Width of image must be greater than 0.");
ERR_FAIL_COND_MSG(p_height <= 0, "Height of image must be greater than 0.");
- ERR_FAIL_COND_MSG(p_x + p_width > MAX_WIDTH, "End x position cannot be greater than " + itos(MAX_WIDTH) + ".");
- ERR_FAIL_COND_MSG(p_y + p_height > MAX_HEIGHT, "End y position cannot be greater than " + itos(MAX_HEIGHT) + ".");
+ ERR_FAIL_COND_MSG(p_x + p_width > MAX_WIDTH, vformat("End x position cannot be greater than %d.", MAX_WIDTH));
+ ERR_FAIL_COND_MSG(p_y + p_height > MAX_HEIGHT, vformat("End y position cannot be greater than %d.", MAX_HEIGHT));
/* to save memory, cropping should be done in-place, however, since this function
will most likely either not be used much, or in critical areas, for now it won't, because
@@ -1484,8 +1484,8 @@ void Image::crop(int p_width, int p_height) {
void Image::rotate_90(ClockDirection p_direction) {
ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot rotate in compressed or custom image formats.");
- ERR_FAIL_COND_MSG(width <= 0, "The Image width specified (" + itos(width) + " pixels) must be greater than 0 pixels.");
- ERR_FAIL_COND_MSG(height <= 0, "The Image height specified (" + itos(height) + " pixels) must be greater than 0 pixels.");
+ ERR_FAIL_COND_MSG(width <= 0, vformat("The Image width specified (%d pixels) must be greater than 0 pixels.", width));
+ ERR_FAIL_COND_MSG(height <= 0, vformat("The Image height specified (%d pixels) must be greater than 0 pixels.", height));
bool used_mipmaps = has_mipmaps();
if (used_mipmaps) {
@@ -1602,8 +1602,8 @@ void Image::rotate_90(ClockDirection p_direction) {
void Image::rotate_180() {
ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot rotate in compressed or custom image formats.");
- ERR_FAIL_COND_MSG(width <= 0, "The Image width specified (" + itos(width) + " pixels) must be greater than 0 pixels.");
- ERR_FAIL_COND_MSG(height <= 0, "The Image height specified (" + itos(height) + " pixels) must be greater than 0 pixels.");
+ ERR_FAIL_COND_MSG(width <= 0, vformat("The Image width specified (%d pixels) must be greater than 0 pixels.", width));
+ ERR_FAIL_COND_MSG(height <= 0, vformat("The Image height specified (%d pixels) must be greater than 0 pixels.", height));
bool used_mipmaps = has_mipmaps();
if (used_mipmaps) {
@@ -2249,15 +2249,15 @@ void Image::set_data(int p_width, int p_height, bool p_use_mipmaps, Format p_for
}
void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format) {
- ERR_FAIL_COND_MSG(p_width <= 0, "The Image width specified (" + itos(p_width) + " pixels) must be greater than 0 pixels.");
- ERR_FAIL_COND_MSG(p_height <= 0, "The Image height specified (" + itos(p_height) + " pixels) must be greater than 0 pixels.");
+ ERR_FAIL_COND_MSG(p_width <= 0, vformat("The Image width specified (%d pixels) must be greater than 0 pixels.", p_width));
+ ERR_FAIL_COND_MSG(p_height <= 0, vformat("The Image height specified (%d pixels) must be greater than 0 pixels.", p_height));
ERR_FAIL_COND_MSG(p_width > MAX_WIDTH,
- "The Image width specified (" + itos(p_width) + " pixels) cannot be greater than " + itos(MAX_WIDTH) + "pixels.");
+ vformat("The Image width specified (%d pixels) cannot be greater than %d pixels.", p_width, MAX_WIDTH));
ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT,
- "The Image height specified (" + itos(p_height) + " pixels) cannot be greater than " + itos(MAX_HEIGHT) + "pixels.");
+ vformat("The Image height specified (%d pixels) cannot be greater than %d pixels.", p_height, MAX_HEIGHT));
ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS,
- "Too many pixels for Image. Maximum is " + itos(MAX_WIDTH) + "x" + itos(MAX_HEIGHT) + " = " + itos(MAX_PIXELS) + "pixels.");
- ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "The Image format specified (" + itos(p_format) + ") is out of range. See Image's Format enum.");
+ vformat("Too many pixels for Image. Maximum is %dx%d = %d pixels.", MAX_WIDTH, MAX_HEIGHT, MAX_PIXELS));
+ ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, vformat("The Image format specified (%d) is out of range. See Image's Format enum.", p_format));
int mm = 0;
int64_t size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0);
@@ -2275,15 +2275,15 @@ void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Forma
}
void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector<uint8_t> &p_data) {
- ERR_FAIL_COND_MSG(p_width <= 0, "The Image width specified (" + itos(p_width) + " pixels) must be greater than 0 pixels.");
- ERR_FAIL_COND_MSG(p_height <= 0, "The Image height specified (" + itos(p_height) + " pixels) must be greater than 0 pixels.");
+ ERR_FAIL_COND_MSG(p_width <= 0, vformat("The Image width specified (%d pixels) must be greater than 0 pixels.", p_width));
+ ERR_FAIL_COND_MSG(p_height <= 0, vformat("The Image height specified (%d pixels) must be greater than 0 pixels.", p_height));
ERR_FAIL_COND_MSG(p_width > MAX_WIDTH,
- "The Image width specified (" + itos(p_width) + " pixels) cannot be greater than " + itos(MAX_WIDTH) + " pixels.");
+ vformat("The Image width specified (%d pixels) cannot be greater than %d pixels.", p_width, MAX_WIDTH));
ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT,
- "The Image height specified (" + itos(p_height) + " pixels) cannot be greater than " + itos(MAX_HEIGHT) + " pixels.");
+ vformat("The Image height specified (%d pixels) cannot be greater than %d pixels.", p_height, MAX_HEIGHT));
ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS,
- "Too many pixels for Image. Maximum is " + itos(MAX_WIDTH) + "x" + itos(MAX_HEIGHT) + " = " + itos(MAX_PIXELS) + "pixels .");
- ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "The Image format specified (" + itos(p_format) + ") is out of range. See Image's Format enum.");
+ vformat("Too many pixels for Image. Maximum is %dx%d = %d pixels.", MAX_WIDTH, MAX_HEIGHT, MAX_PIXELS));
+ ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, vformat("The Image format specified (%d) is out of range. See Image's Format enum.", p_format));
int mm;
int64_t size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0);
@@ -2577,7 +2577,7 @@ Image::AlphaMode Image::detect_alpha() const {
Error Image::load(const String &p_path) {
#ifdef DEBUG_ENABLED
if (p_path.begins_with("res://") && ResourceLoader::exists(p_path)) {
- WARN_PRINT("Loaded resource as image file, this will not work on export: '" + p_path + "'. Instead, import the image file as an Image resource and load it normally as a resource.");
+ WARN_PRINT(vformat("Loaded resource as image file, this will not work on export: '%s'. Instead, import the image file as an Image resource and load it normally as a resource.", p_path));
}
#endif
return ImageLoader::load_image(p_path, this);
@@ -2586,7 +2586,7 @@ Error Image::load(const String &p_path) {
Ref<Image> Image::load_from_file(const String &p_path) {
#ifdef DEBUG_ENABLED
if (p_path.begins_with("res://") && ResourceLoader::exists(p_path)) {
- WARN_PRINT("Loaded resource as image file, this will not work on export: '" + p_path + "'. Instead, import the image file as an Image resource and load it normally as a resource.");
+ WARN_PRINT(vformat("Loaded resource as image file, this will not work on export: '%s'. Instead, import the image file as an Image resource and load it normally as a resource.", p_path));
}
#endif
Ref<Image> image;
@@ -2649,7 +2649,7 @@ Error Image::save_webp(const String &p_path, const bool p_lossy, const float p_q
if (save_webp_func == nullptr) {
return ERR_UNAVAILABLE;
}
- ERR_FAIL_COND_V_MSG(p_lossy && !(0.0f <= p_quality && p_quality <= 1.0f), ERR_INVALID_PARAMETER, "The WebP lossy quality was set to " + rtos(p_quality) + ", which is not valid. WebP lossy quality must be between 0.0 and 1.0 (inclusive).");
+ ERR_FAIL_COND_V_MSG(p_lossy && !(0.0f <= p_quality && p_quality <= 1.0f), ERR_INVALID_PARAMETER, vformat("The WebP lossy quality was set to %f, which is not valid. WebP lossy quality must be between 0.0 and 1.0 (inclusive).", p_quality));
return save_webp_func(p_path, Ref<Image>((Image *)this), p_lossy, p_quality);
}
@@ -2658,7 +2658,7 @@ Vector<uint8_t> Image::save_webp_to_buffer(const bool p_lossy, const float p_qua
if (save_webp_buffer_func == nullptr) {
return Vector<uint8_t>();
}
- ERR_FAIL_COND_V_MSG(p_lossy && !(0.0f <= p_quality && p_quality <= 1.0f), Vector<uint8_t>(), "The WebP lossy quality was set to " + rtos(p_quality) + ", which is not valid. WebP lossy quality must be between 0.0 and 1.0 (inclusive).");
+ ERR_FAIL_COND_V_MSG(p_lossy && !(0.0f <= p_quality && p_quality <= 1.0f), Vector<uint8_t>(), vformat("The WebP lossy quality was set to %f, which is not valid. WebP lossy quality must be between 0.0 and 1.0 (inclusive).", p_quality));
return save_webp_buffer_func(Ref<Image>((Image *)this), p_lossy, p_quality);
}
diff --git a/core/io/image_loader.cpp b/core/io/image_loader.cpp
index 92c690dc2a..50c4704aa3 100644
--- a/core/io/image_loader.cpp
+++ b/core/io/image_loader.cpp
@@ -87,7 +87,7 @@ Error ImageLoader::load_image(const String &p_file, Ref<Image> p_image, Ref<File
if (f.is_null()) {
Error err;
f = FileAccess::open(p_file, FileAccess::READ, &err);
- ERR_FAIL_COND_V_MSG(f.is_null(), err, "Error opening file '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(f.is_null(), err, vformat("Error opening file '%s'.", p_file));
}
String extension = p_file.get_extension();
@@ -98,7 +98,7 @@ Error ImageLoader::load_image(const String &p_file, Ref<Image> p_image, Ref<File
}
Error err = loader.write[i]->load_image(p_image, f, p_flags, p_scale);
if (err != OK) {
- ERR_PRINT("Error loading image: " + p_file);
+ ERR_PRINT(vformat("Error loading image: '%s'.", p_file));
}
if (err != ERR_FILE_UNRECOGNIZED) {
diff --git a/core/io/ip.cpp b/core/io/ip.cpp
index aa71ad04d0..3c67a8f894 100644
--- a/core/io/ip.cpp
+++ b/core/io/ip.cpp
@@ -201,7 +201,7 @@ IPAddress IP::get_resolve_item_address(ResolverID p_id) const {
MutexLock lock(resolver->mutex);
if (resolver->queue[p_id].status.get() != IP::RESOLVER_STATUS_DONE) {
- ERR_PRINT("Resolve of '" + resolver->queue[p_id].hostname + "'' didn't complete yet.");
+ ERR_PRINT(vformat("Resolve of '%s' didn't complete yet.", resolver->queue[p_id].hostname));
return IPAddress();
}
@@ -220,7 +220,7 @@ Array IP::get_resolve_item_addresses(ResolverID p_id) const {
MutexLock lock(resolver->mutex);
if (resolver->queue[p_id].status.get() != IP::RESOLVER_STATUS_DONE) {
- ERR_PRINT("Resolve of '" + resolver->queue[p_id].hostname + "'' didn't complete yet.");
+ ERR_PRINT(vformat("Resolve of '%s' didn't complete yet.", resolver->queue[p_id].hostname));
return Array();
}
diff --git a/core/io/json.cpp b/core/io/json.cpp
index 22219fca29..e73677be9c 100644
--- a/core/io/json.cpp
+++ b/core/io/json.cpp
@@ -1402,7 +1402,7 @@ Error ResourceFormatSaverJSON::save(const Ref<Resource> &p_resource, const Strin
Error err;
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err);
- ERR_FAIL_COND_V_MSG(err, err, "Cannot save json '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err, err, vformat("Cannot save json '%s'.", p_path));
file->store_string(source);
if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) {
diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp
index 67469de5cc..c4d11b8a32 100644
--- a/core/io/marshalls.cpp
+++ b/core/io/marshalls.cpp
@@ -698,9 +698,9 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
if (str == "script" && value.get_type() != Variant::NIL) {
ERR_FAIL_COND_V_MSG(value.get_type() != Variant::STRING, ERR_INVALID_DATA, "Invalid value for \"script\" property, expected script path as String.");
String path = value;
- ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, "Invalid script path: '" + path + "'.");
+ ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, vformat("Invalid script path: '%s'.", path));
Ref<Script> script = ResourceLoader::load(path, "Script");
- ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, "Can't load script at path: '" + path + "'.");
+ ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, vformat("Can't load script at path: '%s'.", path));
obj->set_script(script);
} else {
obj->set(str, value);
@@ -820,9 +820,9 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
builtin_type = Variant::OBJECT;
if (p_allow_objects) {
- ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, "Invalid script path: '" + path + "'.");
+ ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, vformat("Invalid script path: '%s'.", path));
script = ResourceLoader::load(path, "Script");
- ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, "Can't load script at path: '" + path + "'.");
+ ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, vformat("Can't load script at path: '%s'.", path));
class_name = script->get_instance_base_type();
} else {
class_name = EncodedObjectAsID::get_class_static();
diff --git a/core/io/pck_packer.cpp b/core/io/pck_packer.cpp
index 93179d9a11..8ccf74261f 100644
--- a/core/io/pck_packer.cpp
+++ b/core/io/pck_packer.cpp
@@ -84,7 +84,7 @@ Error PCKPacker::pck_start(const String &p_pck_path, int p_alignment, const Stri
enc_dir = p_encrypt_directory;
file = FileAccess::open(p_pck_path, FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(file.is_null(), ERR_CANT_CREATE, "Can't open file to write: " + String(p_pck_path) + ".");
+ ERR_FAIL_COND_V_MSG(file.is_null(), ERR_CANT_CREATE, vformat("Can't open file to write: '%s'.", String(p_pck_path)));
alignment = p_alignment;
diff --git a/core/io/plist.cpp b/core/io/plist.cpp
index 8d91e6dec2..32e83c31f2 100644
--- a/core/io/plist.cpp
+++ b/core/io/plist.cpp
@@ -450,7 +450,7 @@ PList::PList() {
PList::PList(const String &p_string) {
String err_str;
bool ok = load_string(p_string, err_str);
- ERR_FAIL_COND_MSG(!ok, "PList: " + err_str);
+ ERR_FAIL_COND_MSG(!ok, vformat("PList: %s.", err_str));
}
uint64_t PList::read_bplist_var_size_int(Ref<FileAccess> p_file, uint8_t p_size) {
diff --git a/core/io/remote_filesystem_client.cpp b/core/io/remote_filesystem_client.cpp
index 1198810441..c3f9a0016c 100644
--- a/core/io/remote_filesystem_client.cpp
+++ b/core/io/remote_filesystem_client.cpp
@@ -96,7 +96,7 @@ Error RemoteFilesystemClient::_store_file(const String &p_path, const LocalVecto
}
Ref<FileAccess> f = FileAccess::open(full_path, FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Unable to open file for writing to remote filesystem cache: " + p_path);
+ ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, vformat("Unable to open file for writing to remote filesystem cache: '%s'.", p_path));
f->store_buffer(p_file.ptr(), p_file.size());
Error err = f->get_error();
if (err) {
@@ -115,10 +115,10 @@ Error RemoteFilesystemClient::_store_cache_file(const Vector<FileCache> &p_cache
String full_path = cache_path.path_join(FILES_CACHE_FILE);
String base_file_dir = full_path.get_base_dir();
Error err = DirAccess::make_dir_recursive_absolute(base_file_dir);
- ERR_FAIL_COND_V_MSG(err != OK && err != ERR_ALREADY_EXISTS, err, "Unable to create base directory to store cache file: " + base_file_dir);
+ ERR_FAIL_COND_V_MSG(err != OK && err != ERR_ALREADY_EXISTS, err, vformat("Unable to create base directory to store cache file: '%s'.", base_file_dir));
Ref<FileAccess> f = FileAccess::open(full_path, FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Unable to open the remote cache file for writing: " + full_path);
+ ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, vformat("Unable to open the remote cache file for writing: '%s'.", full_path));
f->store_line(itos(FILESYSTEM_CACHE_VERSION));
for (int i = 0; i < p_cache.size(); i++) {
String l = p_cache[i].path + "::" + itos(p_cache[i].server_modified_time) + "::" + itos(p_cache[i].modified_time);
@@ -151,10 +151,10 @@ Error RemoteFilesystemClient::_synchronize_with_server(const String &p_host, int
tcp_client.instantiate();
IPAddress ip = p_host.is_valid_ip_address() ? IPAddress(p_host) : IP::get_singleton()->resolve_hostname(p_host);
- ERR_FAIL_COND_V_MSG(!ip.is_valid(), ERR_INVALID_PARAMETER, "Unable to resolve remote filesystem server hostname: " + p_host);
+ ERR_FAIL_COND_V_MSG(!ip.is_valid(), ERR_INVALID_PARAMETER, vformat("Unable to resolve remote filesystem server hostname: '%s'.", p_host));
print_verbose(vformat("Remote Filesystem: Connecting to host %s, port %d.", ip, p_port));
Error err = tcp_client->connect_to_host(ip, p_port);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to open connection to remote file server (" + String(p_host) + ", port " + itos(p_port) + ") failed.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Unable to open connection to remote file server (%s, port %d) failed.", String(p_host), p_port));
while (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTING) {
tcp_client->poll();
@@ -162,7 +162,7 @@ Error RemoteFilesystemClient::_synchronize_with_server(const String &p_host, int
}
if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
- ERR_FAIL_V_MSG(ERR_CANT_CONNECT, "Connection to remote file server (" + String(p_host) + ", port " + itos(p_port) + ") failed.");
+ ERR_FAIL_V_MSG(ERR_CANT_CONNECT, vformat("Connection to remote file server (%s, port %d) failed.", String(p_host), p_port));
}
// Connection OK, now send the current file state.
@@ -280,7 +280,7 @@ Error RemoteFilesystemClient::_synchronize_with_server(const String &p_host, int
err = tcp_client->get_data(file_buffer.ptr(), file_size);
if (err != OK) {
- ERR_PRINT("Error retrieving file from remote filesystem: " + file);
+ ERR_PRINT(vformat("Error retrieving file from remote filesystem: '%s'.", file));
server_disconnected = true;
}
diff --git a/core/io/resource.cpp b/core/io/resource.cpp
index 0ff4fbe490..c65484b6c6 100644
--- a/core/io/resource.cpp
+++ b/core/io/resource.cpp
@@ -76,7 +76,7 @@ void Resource::set_path(const String &p_path, bool p_take_over) {
existing->path_cache = String();
ResourceCache::resources.erase(p_path);
} else {
- ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion).");
+ ERR_FAIL_MSG(vformat("Another resource is loaded from path '%s' (possible cyclic resource inclusion).", p_path));
}
}
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index ecbb9c0104..8bfa91a220 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -411,7 +411,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) {
//always use internal cache for loading internal resources
if (!internal_index_cache.has(path)) {
- WARN_PRINT(String("Couldn't load resource (no cache): " + path).utf8().get_data());
+ WARN_PRINT(vformat("Couldn't load resource (no cache): %s.", path));
r_v = Variant();
} else {
r_v = internal_index_cache[path];
@@ -435,7 +435,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) {
Ref<Resource> res = ResourceLoader::load(path, exttype, cache_mode_for_external);
if (res.is_null()) {
- WARN_PRINT(String("Couldn't load resource: " + path).utf8().get_data());
+ WARN_PRINT(vformat("Couldn't load resource: %s.", path));
}
r_v = res;
@@ -458,7 +458,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) {
ResourceLoader::notify_dependency_error(local_path, external_resources[erindex].path, external_resources[erindex].type);
} else {
error = ERR_FILE_MISSING_DEPENDENCIES;
- ERR_FAIL_V_MSG(error, "Can't load dependency: " + external_resources[erindex].path + ".");
+ ERR_FAIL_V_MSG(error, vformat("Can't load dependency: '%s'.", external_resources[erindex].path));
}
}
} else {
@@ -704,7 +704,7 @@ Error ResourceLoaderBinary::load() {
ResourceLoader::notify_dependency_error(local_path, path, external_resources[i].type);
} else {
error = ERR_FILE_MISSING_DEPENDENCIES;
- ERR_FAIL_V_MSG(error, "Can't load dependency: " + path + ".");
+ ERR_FAIL_V_MSG(error, vformat("Can't load dependency: '%s'.", path));
}
}
}
@@ -780,7 +780,7 @@ Error ResourceLoaderBinary::load() {
obj = missing_resource;
} else {
error = ERR_FILE_CORRUPT;
- ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource of unrecognized type in file: " + t + ".");
+ ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, vformat("'%s': Resource of unrecognized type in file: '%s'.", local_path, t));
}
}
@@ -789,7 +789,7 @@ Error ResourceLoaderBinary::load() {
String obj_class = obj->get_class();
error = ERR_FILE_CORRUPT;
memdelete(obj); //bye
- ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource type in resource field not a resource, type is: " + obj_class + ".");
+ ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, vformat("'%s': Resource type in resource field not a resource, type is: %s.", local_path, obj_class));
}
res = Ref<Resource>(r);
@@ -999,7 +999,7 @@ void ResourceLoaderBinary::open(Ref<FileAccess> p_f, bool p_no_resources, bool p
error = fac->open_after_magic(f);
if (error != OK) {
f.unref();
- ERR_FAIL_MSG("Failed to open binary resource file: " + local_path + ".");
+ ERR_FAIL_MSG(vformat("Failed to open binary resource file: '%s'.", local_path));
}
f = fac;
@@ -1007,7 +1007,7 @@ void ResourceLoaderBinary::open(Ref<FileAccess> p_f, bool p_no_resources, bool p
// Not normal.
error = ERR_FILE_UNRECOGNIZED;
f.unref();
- ERR_FAIL_MSG("Unrecognized binary resource file: " + local_path + ".");
+ ERR_FAIL_MSG(vformat("Unrecognized binary resource file: '%s'.", local_path));
}
bool big_endian = f->get_32();
@@ -1093,10 +1093,10 @@ void ResourceLoaderBinary::open(Ref<FileAccess> p_f, bool p_no_resources, bool p
#ifdef TOOLS_ENABLED
// Silence a warning that can happen during the initial filesystem scan due to cache being regenerated.
if (ResourceLoader::get_resource_uid(res_path) != er.uid) {
- WARN_PRINT(String(res_path + ": In external resource #" + itos(i) + ", invalid UID: " + ResourceUID::get_singleton()->id_to_text(er.uid) + " - using text path instead: " + er.path).utf8().get_data());
+ WARN_PRINT(vformat("'%s': In external resource #%d, invalid UID: '%s' - using text path instead: '%s'.", res_path, i, ResourceUID::get_singleton()->id_to_text(er.uid), er.path));
}
#else
- WARN_PRINT(String(res_path + ": In external resource #" + itos(i) + ", invalid UID: " + ResourceUID::get_singleton()->id_to_text(er.uid) + " - using text path instead: " + er.path).utf8().get_data());
+ WARN_PRINT(vformat("'%s': In external resource #%d, invalid UID: '%s' - using text path instead: '%s'.", res_path, i, ResourceUID::get_singleton()->id_to_text(er.uid), er.path));
#endif
}
}
@@ -1120,7 +1120,7 @@ void ResourceLoaderBinary::open(Ref<FileAccess> p_f, bool p_no_resources, bool p
if (f->eof_reached()) {
error = ERR_FILE_CORRUPT;
f.unref();
- ERR_FAIL_MSG("Premature end of file (EOF): " + local_path + ".");
+ ERR_FAIL_MSG(vformat("Premature end of file (EOF): '%s'.", local_path));
}
}
@@ -1224,7 +1224,7 @@ Ref<Resource> ResourceFormatLoaderBinary::load(const String &p_path, const Strin
Error err;
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
- ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), vformat("Cannot open file '%s'.", p_path));
ResourceLoaderBinary loader;
switch (p_cache_mode) {
@@ -1301,7 +1301,7 @@ bool ResourceFormatLoaderBinary::handles_type(const String &p_type) const {
void ResourceFormatLoaderBinary::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_MSG(f.is_null(), "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_MSG(f.is_null(), vformat("Cannot open file '%s'.", p_path));
ResourceLoaderBinary loader;
loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
@@ -1311,7 +1311,7 @@ void ResourceFormatLoaderBinary::get_dependencies(const String &p_path, List<Str
Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, const HashMap<String, String> &p_map) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Cannot open file '%s'.", p_path));
Ref<FileAccess> fw;
@@ -1324,23 +1324,23 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons
Ref<FileAccessCompressed> fac;
fac.instantiate();
Error err = fac->open_after_magic(f);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot open file '%s'.", p_path));
f = fac;
Ref<FileAccessCompressed> facw;
facw.instantiate();
facw->configure("RSCC");
err = facw->open_internal(p_path + ".depren", FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(err, ERR_FILE_CORRUPT, "Cannot create file '" + p_path + ".depren'.");
+ ERR_FAIL_COND_V_MSG(err, ERR_FILE_CORRUPT, vformat("Cannot create file '%s.depren'.", p_path));
fw = facw;
} else if (header[0] != 'R' || header[1] != 'S' || header[2] != 'R' || header[3] != 'C') {
// Not normal.
- ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, "Unrecognized binary resource file '" + local_path + "'.");
+ ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, vformat("Unrecognized binary resource file '%s'.", local_path));
} else {
fw = FileAccess::open(p_path + ".depren", FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(fw.is_null(), ERR_CANT_CREATE, "Cannot create file '" + p_path + ".depren'.");
+ ERR_FAIL_COND_V_MSG(fw.is_null(), ERR_CANT_CREATE, vformat("Cannot create file '%s.depren'.", p_path));
uint8_t magic[4] = { 'R', 'S', 'R', 'C' };
fw->store_buffer(magic, 4);
@@ -1372,12 +1372,12 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons
// Use the old approach.
- WARN_PRINT("This file is old, so it can't refactor dependencies, opening and resaving '" + p_path + "'.");
+ WARN_PRINT(vformat("This file is old, so it can't refactor dependencies, opening and resaving '%s'.", p_path));
Error err;
f = FileAccess::open(p_path, FileAccess::READ, &err);
- ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_OPEN, "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_OPEN, vformat("Cannot open file '%s'.", p_path));
ResourceLoaderBinary loader;
loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
@@ -1523,7 +1523,7 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons
void ResourceFormatLoaderBinary::get_classes_used(const String &p_path, HashSet<StringName> *r_classes) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_MSG(f.is_null(), "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_MSG(f.is_null(), vformat("Cannot open file '%s'.", p_path));
ResourceLoaderBinary loader;
loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
@@ -2027,7 +2027,7 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant
if (!p_main && (!bundle_resources) && !res->is_built_in()) {
if (res->get_path() == path) {
- ERR_PRINT("Circular reference to resource being saved found: '" + local_path + "' will be null next time it's loaded.");
+ ERR_PRINT(vformat("Circular reference to resource being saved found: '%s' will be null next time it's loaded.", local_path));
return;
}
int idx = external_resources.size();
@@ -2153,7 +2153,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
f = FileAccess::open(p_path, FileAccess::WRITE, &err);
}
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot create file '%s'.", p_path));
relative_paths = p_flags & ResourceSaver::FLAG_RELATIVE_PATHS;
skip_editor = p_flags & ResourceSaver::FLAG_OMIT_EDITOR_PROPERTIES;
@@ -2384,7 +2384,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
Error ResourceFormatSaverBinaryInstance::set_uid(const String &p_path, ResourceUID::ID p_uid) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Cannot open file '%s'.", p_path));
Ref<FileAccess> fw;
@@ -2397,14 +2397,14 @@ Error ResourceFormatSaverBinaryInstance::set_uid(const String &p_path, ResourceU
Ref<FileAccessCompressed> fac;
fac.instantiate();
Error err = fac->open_after_magic(f);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot open file '%s'.", p_path));
f = fac;
Ref<FileAccessCompressed> facw;
facw.instantiate();
facw->configure("RSCC");
err = facw->open_internal(p_path + ".uidren", FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(err, ERR_FILE_CORRUPT, "Cannot create file '" + p_path + ".uidren'.");
+ ERR_FAIL_COND_V_MSG(err, ERR_FILE_CORRUPT, vformat("Cannot create file '%s.uidren'.", p_path));
fw = facw;
@@ -2413,7 +2413,7 @@ Error ResourceFormatSaverBinaryInstance::set_uid(const String &p_path, ResourceU
return ERR_FILE_UNRECOGNIZED;
} else {
fw = FileAccess::open(p_path + ".uidren", FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(fw.is_null(), ERR_CANT_CREATE, "Cannot create file '" + p_path + ".uidren'.");
+ ERR_FAIL_COND_V_MSG(fw.is_null(), ERR_CANT_CREATE, vformat("Cannot create file '%s.uidren'.", p_path));
uint8_t magich[4] = { 'R', 'S', 'R', 'C' };
fw->store_buffer(magich, 4);
@@ -2444,7 +2444,7 @@ Error ResourceFormatSaverBinaryInstance::set_uid(const String &p_path, ResourceU
// Use the old approach.
- WARN_PRINT("This file is old, so it does not support UIDs, opening and resaving '" + p_path + "'.");
+ WARN_PRINT(vformat("This file is old, so it does not support UIDs, opening and resaving '%s'.", p_path));
return ERR_UNAVAILABLE;
}
diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp
index 1ae50d2d0d..e603f9dfde 100644
--- a/core/io/resource_importer.cpp
+++ b/core/io/resource_importer.cpp
@@ -75,7 +75,7 @@ Error ResourceFormatImporter::_get_path_and_type(const String &p_path, PathAndTy
if (err == ERR_FILE_EOF) {
return OK;
} else if (err != OK) {
- ERR_PRINT("ResourceFormatImporter::load - " + p_path + ".import:" + itos(lines) + " error: " + error_text);
+ ERR_PRINT(vformat("ResourceFormatImporter::load - %s.import:%d error: %s.", p_path, lines, error_text));
return err;
}
@@ -335,7 +335,7 @@ void ResourceFormatImporter::get_internal_resource_path_list(const String &p_pat
if (err == ERR_FILE_EOF) {
return;
} else if (err != OK) {
- ERR_PRINT("ResourceFormatImporter::get_internal_resource_path_list - " + p_path + ".import:" + itos(lines) + " error: " + error_text);
+ ERR_PRINT(vformat("ResourceFormatImporter::get_internal_resource_path_list - %s.import:%d error: %s.", p_path, lines, error_text));
return;
}
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/io/resource_loader.cpp b/core/io/resource_loader.cpp
index f026d5416c..c8c7d430cc 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -162,7 +162,7 @@ Ref<Resource> ResourceFormatLoader::load(const String &p_path, const String &p_o
}
}
- ERR_FAIL_V_MSG(Ref<Resource>(), "Failed to load resource '" + p_path + "'. ResourceFormatLoader::load was not implemented for this resource type.");
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Failed to load resource '%s'. ResourceFormatLoader::load was not implemented for this resource type.", p_path));
}
void ResourceFormatLoader::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
@@ -1163,7 +1163,7 @@ String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_rem
// An extra remap may still be necessary afterwards due to the text -> binary converter on export.
String locale = TranslationServer::get_singleton()->get_locale();
- ERR_FAIL_COND_V_MSG(locale.length() < 2, p_path, "Could not remap path '" + p_path + "' for translation as configured locale '" + locale + "' is invalid.");
+ ERR_FAIL_COND_V_MSG(locale.length() < 2, p_path, vformat("Could not remap path '%s' for translation as configured locale '%s' is invalid.", p_path, locale));
Vector<String> &res_remaps = *translation_remaps.getptr(new_path);
@@ -1222,7 +1222,7 @@ String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_rem
if (err == ERR_FILE_EOF) {
break;
} else if (err != OK) {
- ERR_PRINT("Parse error: " + p_path + ".remap:" + itos(lines) + " error: " + error_text + ".");
+ ERR_PRINT(vformat("Parse error: %s.remap:%d error: %s.", p_path, lines, error_text));
break;
}
diff --git a/core/io/resource_saver.cpp b/core/io/resource_saver.cpp
index 1dc1245355..d49037dbe0 100644
--- a/core/io/resource_saver.cpp
+++ b/core/io/resource_saver.cpp
@@ -98,7 +98,7 @@ void ResourceFormatSaver::_bind_methods() {
}
Error ResourceSaver::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) {
- ERR_FAIL_COND_V_MSG(p_resource.is_null(), ERR_INVALID_PARAMETER, "Can't save empty resource to path '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(p_resource.is_null(), ERR_INVALID_PARAMETER, vformat("Can't save empty resource to path '%s'.", p_path));
String path = p_path;
if (path.is_empty()) {
path = p_resource->get_path();
diff --git a/core/io/translation_loader_po.cpp b/core/io/translation_loader_po.cpp
index 812fbc774e..7a11d06df6 100644
--- a/core/io/translation_loader_po.cpp
+++ b/core/io/translation_loader_po.cpp
@@ -169,14 +169,14 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
// If we reached last line and it's not a content line, break, otherwise let processing that last loop
if (is_eof && l.is_empty()) {
if (status == STATUS_READING_ID || status == STATUS_READING_CONTEXT || (status == STATUS_READING_PLURAL && plural_index != plural_forms - 1)) {
- ERR_FAIL_V_MSG(Ref<Resource>(), "Unexpected EOF while reading PO file at: " + path + ":" + itos(line));
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Unexpected EOF while reading PO file at: %s:%d.", path, line));
} else {
break;
}
}
if (l.begins_with("msgctxt")) {
- ERR_FAIL_COND_V_MSG(status != STATUS_READING_STRING && status != STATUS_READING_PLURAL, Ref<Resource>(), "Unexpected 'msgctxt', was expecting 'msgid_plural' or 'msgstr' before 'msgctxt' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(status != STATUS_READING_STRING && status != STATUS_READING_PLURAL, Ref<Resource>(), vformat("Unexpected 'msgctxt', was expecting 'msgid_plural' or 'msgstr' before 'msgctxt' while parsing: %s:%d.", path, line));
// In PO file, "msgctxt" appears before "msgid". If we encounter a "msgctxt", we add what we have read
// and set "entered_context" to true to prevent adding twice.
@@ -184,7 +184,7 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
if (status == STATUS_READING_STRING) {
translation->add_message(msg_id, msg_str, msg_context);
} else if (status == STATUS_READING_PLURAL) {
- ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), vformat("Number of 'msgstr[]' doesn't match with number of plural forms: %s:%d.", path, line));
translation->add_plural_message(msg_id, msgs_plural, msg_context);
}
}
@@ -196,9 +196,9 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
if (l.begins_with("msgid_plural")) {
if (plural_forms == 0) {
- ERR_FAIL_V_MSG(Ref<Resource>(), "PO file uses 'msgid_plural' but 'Plural-Forms' is invalid or missing in header: " + path + ":" + itos(line));
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("PO file uses 'msgid_plural' but 'Plural-Forms' is invalid or missing in header: %s:%d.", path, line));
} else if (status != STATUS_READING_ID) {
- ERR_FAIL_V_MSG(Ref<Resource>(), "Unexpected 'msgid_plural', was expecting 'msgid' before 'msgid_plural' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Unexpected 'msgid_plural', was expecting 'msgid' before 'msgid_plural' while parsing: %s:%d.", path, line));
}
// We don't record the message in "msgid_plural" itself as tr_n(), TTRN(), RTRN() interfaces provide the plural string already.
// We just have to reset variables related to plurals for "msgstr[]" later on.
@@ -208,14 +208,14 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
msgs_plural.resize(plural_forms);
status = STATUS_READING_PLURAL;
} else if (l.begins_with("msgid")) {
- ERR_FAIL_COND_V_MSG(status == STATUS_READING_ID, Ref<Resource>(), "Unexpected 'msgid', was expecting 'msgstr' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(status == STATUS_READING_ID, Ref<Resource>(), vformat("Unexpected 'msgid', was expecting 'msgstr' while parsing: %s:%d.", path, line));
if (!msg_id.is_empty()) {
if (!skip_this && !entered_context) {
if (status == STATUS_READING_STRING) {
translation->add_message(msg_id, msg_str, msg_context);
} else if (status == STATUS_READING_PLURAL) {
- ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), vformat("Number of 'msgstr[]' doesn't match with number of plural forms: %s:%d.", path, line));
translation->add_plural_message(msg_id, msgs_plural, msg_context);
}
}
@@ -244,11 +244,11 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
}
if (l.begins_with("msgstr[")) {
- ERR_FAIL_COND_V_MSG(status != STATUS_READING_PLURAL, Ref<Resource>(), "Unexpected 'msgstr[]', was expecting 'msgid_plural' before 'msgstr[]' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(status != STATUS_READING_PLURAL, Ref<Resource>(), vformat("Unexpected 'msgstr[]', was expecting 'msgid_plural' before 'msgstr[]' while parsing: %s:%d.", path, line));
plural_index++; // Increment to add to the next slot in vector msgs_plural.
l = l.substr(9, l.length()).strip_edges();
} else if (l.begins_with("msgstr")) {
- ERR_FAIL_COND_V_MSG(status != STATUS_READING_ID, Ref<Resource>(), "Unexpected 'msgstr', was expecting 'msgid' before 'msgstr' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(status != STATUS_READING_ID, Ref<Resource>(), vformat("Unexpected 'msgstr', was expecting 'msgid' before 'msgstr' while parsing: %s:%d.", path, line));
l = l.substr(6, l.length()).strip_edges();
status = STATUS_READING_STRING;
}
@@ -261,7 +261,7 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
continue; // Nothing to read or comment.
}
- ERR_FAIL_COND_V_MSG(!l.begins_with("\"") || status == STATUS_NONE, Ref<Resource>(), "Invalid line '" + l + "' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(!l.begins_with("\"") || status == STATUS_NONE, Ref<Resource>(), vformat("Invalid line '%s' while parsing: %s:%d.", l, path, line));
l = l.substr(1, l.length());
// Find final quote, ignoring escaped ones (\").
@@ -283,7 +283,7 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
escape_next = false;
}
- ERR_FAIL_COND_V_MSG(end_pos == -1, Ref<Resource>(), "Expected '\"' at end of message while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(end_pos == -1, Ref<Resource>(), vformat("Expected '\"' at end of message while parsing: %s:%d.", path, line));
l = l.substr(0, end_pos);
l = l.c_unescape();
@@ -295,7 +295,7 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
} else if (status == STATUS_READING_CONTEXT) {
msg_context += l;
} else if (status == STATUS_READING_PLURAL && plural_index >= 0) {
- ERR_FAIL_COND_V_MSG(plural_index >= plural_forms, Ref<Resource>(), "Unexpected plural form while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(plural_index >= plural_forms, Ref<Resource>(), vformat("Unexpected plural form while parsing: %s:%d.", path, line));
msgs_plural.write[plural_index] = msgs_plural[plural_index] + l;
}
@@ -313,13 +313,13 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
}
} else if (status == STATUS_READING_PLURAL) {
if (!skip_this && !msg_id.is_empty()) {
- ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), vformat("Number of 'msgstr[]' doesn't match with number of plural forms: %s:%d.", path, line));
translation->add_plural_message(msg_id, msgs_plural, msg_context);
}
}
}
- ERR_FAIL_COND_V_MSG(config.is_empty(), Ref<Resource>(), "No config found in file: " + path + ".");
+ ERR_FAIL_COND_V_MSG(config.is_empty(), Ref<Resource>(), vformat("No config found in file: '%s'.", path));
Vector<String> configs = config.split("\n");
for (int i = 0; i < configs.size(); i++) {
@@ -349,7 +349,7 @@ Ref<Resource> TranslationLoaderPO::load(const String &p_path, const String &p_or
}
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(f.is_null(), Ref<Resource>(), "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(f.is_null(), Ref<Resource>(), vformat("Cannot open file '%s'.", p_path));
return load_translation(f, r_error);
}
diff --git a/core/io/xml_parser.cpp b/core/io/xml_parser.cpp
index 06888c7cda..6923ca1269 100644
--- a/core/io/xml_parser.cpp
+++ b/core/io/xml_parser.cpp
@@ -429,7 +429,7 @@ String XMLParser::get_named_attribute_value(const String &p_name) const {
}
}
- ERR_FAIL_COND_V_MSG(idx < 0, "", "Attribute not found: " + p_name + ".");
+ ERR_FAIL_COND_V_MSG(idx < 0, "", vformat("Attribute not found: '%s'.", p_name));
return attributes[idx].value;
}
@@ -493,7 +493,7 @@ Error XMLParser::open(const String &p_path) {
Error err;
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot open file '%s'.", p_path));
length = file->get_length();
ERR_FAIL_COND_V(length < 1, ERR_FILE_CORRUPT);
diff --git a/core/math/expression.cpp b/core/math/expression.cpp
index 0692ece1e6..35303fe9ac 100644
--- a/core/math/expression.cpp
+++ b/core/math/expression.cpp
@@ -1491,7 +1491,7 @@ Error Expression::parse(const String &p_expression, const Vector<String> &p_inpu
}
Variant Expression::execute(const Array &p_inputs, Object *p_base, bool p_show_error, bool p_const_calls_only) {
- ERR_FAIL_COND_V_MSG(error_set, Variant(), "There was previously a parse error: " + error_str + ".");
+ ERR_FAIL_COND_V_MSG(error_set, Variant(), vformat("There was previously a parse error: %s.", error_str));
execution_error = false;
Variant output;
diff --git a/core/math/rect2.cpp b/core/math/rect2.cpp
index c55226a57e..7f77b0786c 100644
--- a/core/math/rect2.cpp
+++ b/core/math/rect2.cpp
@@ -283,7 +283,7 @@ next4:
}
Rect2::operator String() const {
- return "[P: " + position.operator String() + ", S: " + size + "]";
+ return "[P: " + position.operator String() + ", S: " + size.operator String() + "]";
}
Rect2::operator Rect2i() const {
diff --git a/core/math/vector2.cpp b/core/math/vector2.cpp
index e86b97d6a8..0590ee8a37 100644
--- a/core/math/vector2.cpp
+++ b/core/math/vector2.cpp
@@ -203,7 +203,7 @@ bool Vector2::is_finite() const {
}
Vector2::operator String() const {
- return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ")";
+ return "(" + String::num_real(x, true) + ", " + String::num_real(y, true) + ")";
}
Vector2::operator Vector2i() const {
diff --git a/core/math/vector3.cpp b/core/math/vector3.cpp
index 1e90002665..e18ac3b011 100644
--- a/core/math/vector3.cpp
+++ b/core/math/vector3.cpp
@@ -165,7 +165,7 @@ bool Vector3::is_finite() const {
}
Vector3::operator String() const {
- return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ", " + String::num_real(z, false) + ")";
+ return "(" + String::num_real(x, true) + ", " + String::num_real(y, true) + ", " + String::num_real(z, true) + ")";
}
Vector3::operator Vector3i() const {
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index 9826d73a9d..bad224eff4 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -352,7 +352,7 @@ StringName ClassDB::get_compatibility_remapped_class(const StringName &p_class)
StringName ClassDB::_get_parent_class(const StringName &p_class) {
ClassInfo *ti = classes.getptr(p_class);
- ERR_FAIL_NULL_V_MSG(ti, StringName(), "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_NULL_V_MSG(ti, StringName(), vformat("Cannot get class '%s'.", String(p_class)));
return ti->inherits;
}
@@ -367,7 +367,7 @@ ClassDB::APIType ClassDB::get_api_type(const StringName &p_class) {
ClassInfo *ti = classes.getptr(p_class);
- ERR_FAIL_NULL_V_MSG(ti, API_NONE, "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_NULL_V_MSG(ti, API_NONE, vformat("Cannot get class '%s'.", String(p_class)));
return ti->api;
}
@@ -390,7 +390,7 @@ uint32_t ClassDB::get_api_hash(APIType p_api) {
for (const StringName &E : class_list) {
ClassInfo *t = classes.getptr(E);
- ERR_FAIL_NULL_V_MSG(t, 0, "Cannot get class '" + String(E) + "'.");
+ ERR_FAIL_NULL_V_MSG(t, 0, vformat("Cannot get class '%s'.", String(E)));
if (t->api != p_api || !t->exposed) {
continue;
}
@@ -547,14 +547,14 @@ Object *ClassDB::_instantiate_internal(const StringName &p_class, bool p_require
ti = classes.getptr(compat_classes[p_class]);
}
}
- ERR_FAIL_NULL_V_MSG(ti, nullptr, "Cannot get class '" + String(p_class) + "'.");
- ERR_FAIL_COND_V_MSG(ti->disabled, nullptr, "Class '" + String(p_class) + "' is disabled.");
- ERR_FAIL_NULL_V_MSG(ti->creation_func, nullptr, "Class '" + String(p_class) + "' or its base class cannot be instantiated.");
+ ERR_FAIL_NULL_V_MSG(ti, nullptr, vformat("Cannot get class '%s'.", String(p_class)));
+ ERR_FAIL_COND_V_MSG(ti->disabled, nullptr, vformat("Class '%s' is disabled.", String(p_class)));
+ ERR_FAIL_NULL_V_MSG(ti->creation_func, nullptr, vformat("Class '%s' or its base class cannot be instantiated.", String(p_class)));
}
#ifdef TOOLS_ENABLED
if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) {
- ERR_PRINT("Class '" + String(p_class) + "' can only be instantiated by editor.");
+ ERR_PRINT(vformat("Class '%s' can only be instantiated by editor.", String(p_class)));
return nullptr;
}
#endif
@@ -653,8 +653,8 @@ ObjectGDExtension *ClassDB::get_placeholder_extension(const StringName &p_class)
ti = classes.getptr(compat_classes[p_class]);
}
}
- ERR_FAIL_NULL_V_MSG(ti, nullptr, "Cannot get class '" + String(p_class) + "'.");
- ERR_FAIL_COND_V_MSG(ti->disabled, nullptr, "Class '" + String(p_class) + "' is disabled.");
+ ERR_FAIL_NULL_V_MSG(ti, nullptr, vformat("Cannot get class '%s'.", String(p_class)));
+ ERR_FAIL_COND_V_MSG(ti->disabled, nullptr, vformat("Class '%s' is disabled.", String(p_class)));
}
// Make a "fake" extension to act as a placeholder.
@@ -734,9 +734,9 @@ void ClassDB::set_object_extension_instance(Object *p_object, const StringName &
ti = classes.getptr(compat_classes[p_class]);
}
}
- ERR_FAIL_NULL_MSG(ti, "Cannot get class '" + String(p_class) + "'.");
- ERR_FAIL_COND_MSG(ti->disabled, "Class '" + String(p_class) + "' is disabled.");
- ERR_FAIL_NULL_MSG(ti->gdextension, "Class '" + String(p_class) + "' has no native extension.");
+ ERR_FAIL_NULL_MSG(ti, vformat("Cannot get class '%s'.", String(p_class)));
+ ERR_FAIL_COND_MSG(ti->disabled, vformat("Class '%s' is disabled.", String(p_class)));
+ ERR_FAIL_NULL_MSG(ti->gdextension, vformat("Class '%s' has no native extension.", String(p_class)));
}
p_object->_extension = ti->gdextension;
@@ -755,7 +755,7 @@ bool ClassDB::can_instantiate(const StringName &p_class) {
ClassInfo *ti = classes.getptr(p_class);
if (!ti) {
if (!ScriptServer::is_global_class(p_class)) {
- ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_V_MSG(false, vformat("Cannot get class '%s'.", String(p_class)));
}
String path = ScriptServer::get_global_class_path(p_class);
Ref<Script> scr = ResourceLoader::load(path);
@@ -775,7 +775,7 @@ bool ClassDB::is_abstract(const StringName &p_class) {
ClassInfo *ti = classes.getptr(p_class);
if (!ti) {
if (!ScriptServer::is_global_class(p_class)) {
- ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_V_MSG(false, vformat("Cannot get class '%s'.", String(p_class)));
}
String path = ScriptServer::get_global_class_path(p_class);
Ref<Script> scr = ResourceLoader::load(path);
@@ -801,7 +801,7 @@ bool ClassDB::is_virtual(const StringName &p_class) {
ClassInfo *ti = classes.getptr(p_class);
if (!ti) {
if (!ScriptServer::is_global_class(p_class)) {
- ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_V_MSG(false, vformat("Cannot get class '%s'.", String(p_class)));
}
String path = ScriptServer::get_global_class_path(p_class);
Ref<Script> scr = ResourceLoader::load(path);
@@ -820,7 +820,7 @@ void ClassDB::_add_class2(const StringName &p_class, const StringName &p_inherit
const StringName &name = p_class;
- ERR_FAIL_COND_MSG(classes.has(name), "Class '" + String(p_class) + "' already exists.");
+ ERR_FAIL_COND_MSG(classes.has(name), vformat("Class '%s' already exists.", String(p_class)));
classes[name] = ClassInfo();
ClassInfo &ti = classes[name];
@@ -1328,7 +1328,7 @@ void ClassDB::add_signal(const StringName &p_class, const MethodInfo &p_signal)
#ifdef DEBUG_METHODS_ENABLED
ClassInfo *check = type;
while (check) {
- ERR_FAIL_COND_MSG(check->signal_map.has(sname), "Class '" + String(p_class) + "' already has signal '" + String(sname) + "'.");
+ ERR_FAIL_COND_MSG(check->signal_map.has(sname), vformat("Class '%s' already has signal '%s'.", String(p_class), String(sname)));
check = check->inherits_ptr;
}
#endif
@@ -1442,10 +1442,10 @@ void ClassDB::add_property(const StringName &p_class, const PropertyInfo &p_pinf
mb_set = get_method(p_class, p_setter);
#ifdef DEBUG_METHODS_ENABLED
- ERR_FAIL_NULL_MSG(mb_set, "Invalid setter '" + p_class + "::" + p_setter + "' for property '" + p_pinfo.name + "'.");
+ ERR_FAIL_NULL_MSG(mb_set, vformat("Invalid setter '%s::%s' for property '%s'.", p_class, p_setter, p_pinfo.name));
int exp_args = 1 + (p_index >= 0 ? 1 : 0);
- ERR_FAIL_COND_MSG(mb_set->get_argument_count() != exp_args, "Invalid function for setter '" + p_class + "::" + p_setter + " for property '" + p_pinfo.name + "'.");
+ ERR_FAIL_COND_MSG(mb_set->get_argument_count() != exp_args, vformat("Invalid function for setter '%s::%s' for property '%s'.", p_class, p_setter, p_pinfo.name));
#endif
}
@@ -1454,15 +1454,15 @@ void ClassDB::add_property(const StringName &p_class, const PropertyInfo &p_pinf
mb_get = get_method(p_class, p_getter);
#ifdef DEBUG_METHODS_ENABLED
- ERR_FAIL_NULL_MSG(mb_get, "Invalid getter '" + p_class + "::" + p_getter + "' for property '" + p_pinfo.name + "'.");
+ ERR_FAIL_NULL_MSG(mb_get, vformat("Invalid getter '%s::%s' for property '%s'.", p_class, p_getter, p_pinfo.name));
int exp_args = 0 + (p_index >= 0 ? 1 : 0);
- ERR_FAIL_COND_MSG(mb_get->get_argument_count() != exp_args, "Invalid function for getter '" + p_class + "::" + p_getter + "' for property: '" + p_pinfo.name + "'.");
+ ERR_FAIL_COND_MSG(mb_get->get_argument_count() != exp_args, vformat("Invalid function for getter '%s::%s' for property '%s'.", p_class, p_getter, p_pinfo.name));
#endif
}
#ifdef DEBUG_METHODS_ENABLED
- ERR_FAIL_COND_MSG(type->property_setget.has(p_pinfo.name), "Object '" + p_class + "' already has property '" + p_pinfo.name + "'.");
+ ERR_FAIL_COND_MSG(type->property_setget.has(p_pinfo.name), vformat("Object '%s' already has property '%s'.", p_class, p_pinfo.name));
#endif
OBJTYPE_WLOCK
@@ -1847,7 +1847,7 @@ void ClassDB::_bind_method_custom(const StringName &p_class, MethodBind *p_metho
ClassInfo *type = classes.getptr(p_class);
if (!type) {
- ERR_FAIL_MSG("Couldn't bind custom method '" + p_method->get_name() + "' for instance '" + p_class + "'.");
+ ERR_FAIL_MSG(vformat("Couldn't bind custom method '%s' for instance '%s'.", p_method->get_name(), p_class));
}
if (p_compatibility) {
@@ -1857,7 +1857,7 @@ void ClassDB::_bind_method_custom(const StringName &p_class, MethodBind *p_metho
if (type->method_map.has(p_method->get_name())) {
// overloading not supported
- ERR_FAIL_MSG("Method already bound '" + p_class + "::" + p_method->get_name() + "'.");
+ ERR_FAIL_MSG(vformat("Method already bound '%s::%s'.", p_class, p_method->get_name()));
}
#ifdef DEBUG_METHODS_ENABLED
@@ -1888,7 +1888,7 @@ MethodBind *ClassDB::_bind_vararg_method(MethodBind *p_bind, const StringName &p
if (type->method_map.has(p_name)) {
memdelete(bind);
// Overloading not supported
- ERR_FAIL_V_MSG(nullptr, "Method already bound: " + instance_type + "::" + p_name + ".");
+ ERR_FAIL_V_MSG(nullptr, vformat("Method already bound: '%s::%s'.", instance_type, p_name));
}
type->method_map[p_name] = bind;
#ifdef DEBUG_METHODS_ENABLED
@@ -1916,26 +1916,26 @@ MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, bool p_
#ifdef DEBUG_ENABLED
- ERR_FAIL_COND_V_MSG(!p_compatibility && has_method(instance_type, mdname), nullptr, "Class " + String(instance_type) + " already has a method " + String(mdname) + ".");
+ ERR_FAIL_COND_V_MSG(!p_compatibility && has_method(instance_type, mdname), nullptr, vformat("Class '%s' already has a method '%s'.", String(instance_type), String(mdname)));
#endif
ClassInfo *type = classes.getptr(instance_type);
if (!type) {
memdelete(p_bind);
- ERR_FAIL_V_MSG(nullptr, "Couldn't bind method '" + mdname + "' for instance '" + instance_type + "'.");
+ ERR_FAIL_V_MSG(nullptr, vformat("Couldn't bind method '%s' for instance '%s'.", mdname, instance_type));
}
if (!p_compatibility && type->method_map.has(mdname)) {
memdelete(p_bind);
// overloading not supported
- ERR_FAIL_V_MSG(nullptr, "Method already bound '" + instance_type + "::" + mdname + "'.");
+ ERR_FAIL_V_MSG(nullptr, vformat("Method already bound '%s::%s'.", instance_type, mdname));
}
#ifdef DEBUG_METHODS_ENABLED
if (method_name.args.size() > p_bind->get_argument_count()) {
memdelete(p_bind);
- ERR_FAIL_V_MSG(nullptr, "Method definition provides more arguments than the method actually has '" + instance_type + "::" + mdname + "'.");
+ ERR_FAIL_V_MSG(nullptr, vformat("Method definition provides more arguments than the method actually has '%s::%s'.", instance_type, mdname));
}
p_bind->set_argument_names(method_name.args);
@@ -1964,7 +1964,7 @@ MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, bool p_
}
void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual, const Vector<String> &p_arg_names, bool p_object_core) {
- ERR_FAIL_COND_MSG(!classes.has(p_class), "Request for nonexistent class '" + p_class + "'.");
+ ERR_FAIL_COND_MSG(!classes.has(p_class), vformat("Request for nonexistent class '%s'.", p_class));
OBJTYPE_WLOCK;
@@ -1979,7 +1979,7 @@ void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_
if (!p_object_core) {
if (p_arg_names.size() != mi.arguments.size()) {
- WARN_PRINT("Mismatch argument name count for virtual method: " + String(p_class) + "::" + p_method.name);
+ WARN_PRINT(vformat("Mismatch argument name count for virtual method: '%s::%s'.", String(p_class), p_method.name));
} else {
List<PropertyInfo>::Iterator itr = mi.arguments.begin();
for (int i = 0; i < p_arg_names.size(); ++itr, ++i) {
@@ -1990,7 +1990,7 @@ void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_
if (classes[p_class].virtual_methods_map.has(p_method.name)) {
// overloading not supported
- ERR_FAIL_MSG("Virtual method already bound '" + String(p_class) + "::" + p_method.name + "'.");
+ ERR_FAIL_MSG(vformat("Virtual method already bound '%s::%s'.", String(p_class), p_method.name));
}
classes[p_class].virtual_methods.push_back(mi);
classes[p_class].virtual_methods_map[p_method.name] = mi;
@@ -1999,7 +1999,7 @@ void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_
}
void ClassDB::get_virtual_methods(const StringName &p_class, List<MethodInfo> *p_methods, bool p_no_inheritance) {
- ERR_FAIL_COND_MSG(!classes.has(p_class), "Request for nonexistent class '" + p_class + "'.");
+ ERR_FAIL_COND_MSG(!classes.has(p_class), vformat("Request for nonexistent class '%s'.", p_class));
#ifdef DEBUG_METHODS_ENABLED
@@ -2020,7 +2020,7 @@ void ClassDB::get_virtual_methods(const StringName &p_class, List<MethodInfo> *p
}
void ClassDB::add_extension_class_virtual_method(const StringName &p_class, const GDExtensionClassVirtualMethodInfo *p_method_info) {
- ERR_FAIL_COND_MSG(!classes.has(p_class), "Request for nonexistent class '" + p_class + "'.");
+ ERR_FAIL_COND_MSG(!classes.has(p_class), vformat("Request for nonexistent class '%s'.", p_class));
#ifdef DEBUG_METHODS_ENABLED
PackedStringArray arg_names;
@@ -2044,7 +2044,7 @@ void ClassDB::add_extension_class_virtual_method(const StringName &p_class, cons
void ClassDB::set_class_enabled(const StringName &p_class, bool p_enable) {
OBJTYPE_WLOCK;
- ERR_FAIL_COND_MSG(!classes.has(p_class), "Request for nonexistent class '" + p_class + "'.");
+ ERR_FAIL_COND_MSG(!classes.has(p_class), vformat("Request for nonexistent class '%s'.", p_class));
classes[p_class].disabled = !p_enable;
}
@@ -2058,7 +2058,7 @@ bool ClassDB::is_class_enabled(const StringName &p_class) {
}
}
- ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_NULL_V_MSG(ti, false, vformat("Cannot get class '%s'.", String(p_class)));
return !ti->disabled;
}
@@ -2066,7 +2066,7 @@ bool ClassDB::is_class_exposed(const StringName &p_class) {
OBJTYPE_RLOCK;
ClassInfo *ti = classes.getptr(p_class);
- ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_NULL_V_MSG(ti, false, vformat("Cannot get class '%s'.", String(p_class)));
return ti->exposed;
}
@@ -2074,7 +2074,7 @@ bool ClassDB::is_class_reloadable(const StringName &p_class) {
OBJTYPE_RLOCK;
ClassInfo *ti = classes.getptr(p_class);
- ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_NULL_V_MSG(ti, false, vformat("Cannot get class '%s'.", String(p_class)));
return ti->reloadable;
}
@@ -2082,7 +2082,7 @@ bool ClassDB::is_class_runtime(const StringName &p_class) {
OBJTYPE_RLOCK;
ClassInfo *ti = classes.getptr(p_class);
- ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_NULL_V_MSG(ti, false, vformat("Cannot get class '%s'.", String(p_class)));
return ti->is_runtime;
}
@@ -2192,14 +2192,14 @@ Variant ClassDB::class_get_default_property_value(const StringName &p_class, con
void ClassDB::register_extension_class(ObjectGDExtension *p_extension) {
GLOBAL_LOCK_FUNCTION;
- ERR_FAIL_COND_MSG(classes.has(p_extension->class_name), "Class already registered: " + String(p_extension->class_name));
- ERR_FAIL_COND_MSG(!classes.has(p_extension->parent_class_name), "Parent class name for extension class not found: " + String(p_extension->parent_class_name));
+ ERR_FAIL_COND_MSG(classes.has(p_extension->class_name), vformat("Class already registered: '%s'.", String(p_extension->class_name)));
+ ERR_FAIL_COND_MSG(!classes.has(p_extension->parent_class_name), vformat("Parent class name for extension class not found: '%s'.", String(p_extension->parent_class_name)));
ClassInfo *parent = classes.getptr(p_extension->parent_class_name);
#ifdef TOOLS_ENABLED
// @todo This is a limitation of the current implementation, but it should be possible to remove.
- ERR_FAIL_COND_MSG(p_extension->is_runtime && parent->gdextension && !parent->is_runtime, "Extension runtime class " + String(p_extension->class_name) + " cannot descend from " + parent->name + " which isn't also a runtime class");
+ ERR_FAIL_COND_MSG(p_extension->is_runtime && parent->gdextension && !parent->is_runtime, vformat("Extension runtime class '%s' cannot descend from '%s' which isn't also a runtime class.", String(p_extension->class_name), parent->name));
#endif
ClassInfo c;
@@ -2215,7 +2215,7 @@ void ClassDB::register_extension_class(ObjectGDExtension *p_extension) {
concrete_ancestor->gdextension != nullptr) {
concrete_ancestor = concrete_ancestor->inherits_ptr;
}
- ERR_FAIL_NULL_MSG(concrete_ancestor->creation_func, "Extension class " + String(p_extension->class_name) + " cannot extend native abstract class " + String(concrete_ancestor->name));
+ ERR_FAIL_NULL_MSG(concrete_ancestor->creation_func, vformat("Extension class '%s' cannot extend native abstract class '%s'.", String(p_extension->class_name), String(concrete_ancestor->name)));
c.creation_func = concrete_ancestor->creation_func;
}
c.inherits = parent->name;
@@ -2239,7 +2239,7 @@ void ClassDB::register_extension_class(ObjectGDExtension *p_extension) {
void ClassDB::unregister_extension_class(const StringName &p_class, bool p_free_method_binds) {
ClassInfo *c = classes.getptr(p_class);
- ERR_FAIL_NULL_MSG(c, "Class '" + String(p_class) + "' does not exist.");
+ ERR_FAIL_NULL_MSG(c, vformat("Class '%s' does not exist.", String(p_class)));
if (p_free_method_binds) {
for (KeyValue<StringName, MethodBind *> &F : c->method_map) {
memdelete(F.value);
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/object/object.cpp b/core/object/object.cpp
index b3a4ec6e2e..ef1ca8132c 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -750,7 +750,7 @@ Variant Object::callv(const StringName &p_method, const Array &p_args) {
Callable::CallError ce;
const Variant ret = callp(p_method, argptrs, p_args.size(), ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_V_MSG(Variant(), "Error calling method from 'callv': " + Variant::get_call_error_text(this, p_method, argptrs, p_args.size(), ce) + ".");
+ ERR_FAIL_V_MSG(Variant(), vformat("Error calling method from 'callv': %s.", Variant::get_call_error_text(this, p_method, argptrs, p_args.size(), ce)));
}
return ret;
}
@@ -999,7 +999,7 @@ void Object::set_meta(const StringName &p_name, const Variant &p_value) {
if (E) {
E->value = p_value;
} else {
- ERR_FAIL_COND_MSG(!p_name.operator String().is_valid_ascii_identifier(), "Invalid metadata identifier: '" + p_name + "'.");
+ ERR_FAIL_COND_MSG(!p_name.operator String().is_valid_ascii_identifier(), vformat("Invalid metadata identifier: '%s'.", p_name));
Variant *V = &metadata.insert(p_name, p_value)->value;
const String &sname = p_name;
@@ -1015,7 +1015,7 @@ Variant Object::get_meta(const StringName &p_name, const Variant &p_default) con
if (p_default != Variant()) {
return p_default;
} else {
- ERR_FAIL_V_MSG(Variant(), "The object does not have any 'meta' values with the key '" + p_name + "'.");
+ ERR_FAIL_V_MSG(Variant(), vformat("The object does not have any 'meta' values with the key '%s'.", p_name));
}
}
return metadata[p_name];
@@ -1071,8 +1071,8 @@ void Object::get_meta_list(List<StringName> *p_list) const {
void Object::add_user_signal(const MethodInfo &p_signal) {
ERR_FAIL_COND_MSG(p_signal.name.is_empty(), "Signal name cannot be empty.");
- ERR_FAIL_COND_MSG(ClassDB::has_signal(get_class_name(), p_signal.name), "User signal's name conflicts with a built-in signal of '" + get_class_name() + "'.");
- ERR_FAIL_COND_MSG(signal_map.has(p_signal.name), "Trying to add already existing signal '" + p_signal.name + "'.");
+ ERR_FAIL_COND_MSG(ClassDB::has_signal(get_class_name(), p_signal.name), vformat("User signal's name conflicts with a built-in signal of '%s'.", get_class_name()));
+ ERR_FAIL_COND_MSG(signal_map.has(p_signal.name), vformat("Trying to add already existing signal '%s'.", p_signal.name));
SignalData s;
s.user = p_signal;
signal_map[p_signal.name] = s;
@@ -1137,7 +1137,7 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int
#ifdef DEBUG_ENABLED
bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_name);
//check in script
- ERR_FAIL_COND_V_MSG(!signal_is_valid && !script.is_null() && !Ref<Script>(script)->has_script_signal(p_name), ERR_UNAVAILABLE, "Can't emit non-existing signal " + String("\"") + p_name + "\".");
+ ERR_FAIL_COND_V_MSG(!signal_is_valid && !script.is_null() && !Ref<Script>(script)->has_script_signal(p_name), ERR_UNAVAILABLE, vformat("Can't emit non-existing signal \"%s\".", p_name));
#endif
//not connected? just return
return ERR_UNAVAILABLE;
@@ -1210,7 +1210,7 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int
if (ce.error == Callable::CallError::CALL_ERROR_INVALID_METHOD && target && !ClassDB::class_exists(target->get_class_name())) {
//most likely object is not initialized yet, do not throw error.
} else {
- ERR_PRINT("Error calling from signal '" + String(p_name) + "' to callable: " + Variant::get_callable_error_text(callable, args, argc, ce) + ".");
+ ERR_PRINT(vformat("Error calling from signal '%s' to callable: %s.", String(p_name), Variant::get_callable_error_text(callable, args, argc, ce)));
err = ERR_METHOD_NOT_FOUND;
}
}
@@ -1371,15 +1371,15 @@ void Object::get_signals_connected_to_this(List<Connection> *p_connections) cons
}
Error Object::connect(const StringName &p_signal, const Callable &p_callable, uint32_t p_flags) {
- ERR_FAIL_COND_V_MSG(p_callable.is_null(), ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "': the provided callable is null.");
+ ERR_FAIL_COND_V_MSG(p_callable.is_null(), ERR_INVALID_PARAMETER, vformat("Cannot connect to '%s': the provided callable is null.", p_signal));
if (p_callable.is_standard()) {
// FIXME: This branch should probably removed in favor of the `is_valid()` branch, but there exist some classes
// that call `connect()` before they are fully registered with ClassDB. Until all such classes can be found
// and registered soon enough this branch is needed to allow `connect()` to succeed.
- ERR_FAIL_NULL_V_MSG(p_callable.get_object(), ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "' to callable '" + p_callable + "': the callable object is null.");
+ ERR_FAIL_NULL_V_MSG(p_callable.get_object(), ERR_INVALID_PARAMETER, vformat("Cannot connect to '%s' to callable '%s': the callable object is null.", p_signal, p_callable));
} else {
- ERR_FAIL_COND_V_MSG(!p_callable.is_valid(), ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "': the provided callable is not valid: " + p_callable);
+ ERR_FAIL_COND_V_MSG(!p_callable.is_valid(), ERR_INVALID_PARAMETER, vformat("Cannot connect to '%s': the provided callable is not valid: '%s'.", p_signal, p_callable));
}
SignalData *s = signal_map.getptr(p_signal);
@@ -1400,7 +1400,7 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, ui
#endif
}
- ERR_FAIL_COND_V_MSG(!signal_is_valid, ERR_INVALID_PARAMETER, "In Object of type '" + String(get_class()) + "': Attempt to connect nonexistent signal '" + p_signal + "' to callable '" + p_callable + "'.");
+ ERR_FAIL_COND_V_MSG(!signal_is_valid, ERR_INVALID_PARAMETER, vformat("In Object of type '%s': Attempt to connect nonexistent signal '%s' to callable '%s'.", String(get_class()), p_signal, p_callable));
signal_map[p_signal] = SignalData();
s = &signal_map[p_signal];
@@ -1412,7 +1412,7 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, ui
s->slot_map[*p_callable.get_base_comparator()].reference_count++;
return OK;
} else {
- ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Signal '" + p_signal + "' is already connected to given callable '" + p_callable + "' in that object.");
+ ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, vformat("Signal '%s' is already connected to given callable '%s' in that object.", p_signal, p_callable));
}
}
@@ -1439,7 +1439,7 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, ui
}
bool Object::is_connected(const StringName &p_signal, const Callable &p_callable) const {
- ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, "Cannot determine if connected to '" + p_signal + "': the provided callable is null."); // Should use `is_null`, see note in `connect` about the use of `is_valid`.
+ ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, vformat("Cannot determine if connected to '%s': the provided callable is null.", p_signal)); // Should use `is_null`, see note in `connect` about the use of `is_valid`.
const SignalData *s = signal_map.getptr(p_signal);
if (!s) {
bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_signal);
@@ -1451,7 +1451,7 @@ bool Object::is_connected(const StringName &p_signal, const Callable &p_callable
return false;
}
- ERR_FAIL_V_MSG(false, "Nonexistent signal: " + p_signal + ".");
+ ERR_FAIL_V_MSG(false, vformat("Nonexistent signal: '%s'.", p_signal));
}
return s->slot_map.has(*p_callable.get_base_comparator());
@@ -1469,7 +1469,7 @@ bool Object::has_connections(const StringName &p_signal) const {
return false;
}
- ERR_FAIL_V_MSG(false, "Nonexistent signal: " + p_signal + ".");
+ ERR_FAIL_V_MSG(false, vformat("Nonexistent signal: '%s'.", p_signal));
}
return !s->slot_map.is_empty();
@@ -1480,17 +1480,17 @@ void Object::disconnect(const StringName &p_signal, const Callable &p_callable)
}
bool Object::_disconnect(const StringName &p_signal, const Callable &p_callable, bool p_force) {
- ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, "Cannot disconnect from '" + p_signal + "': the provided callable is null."); // Should use `is_null`, see note in `connect` about the use of `is_valid`.
+ ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, vformat("Cannot disconnect from '%s': the provided callable is null.", p_signal)); // Should use `is_null`, see note in `connect` about the use of `is_valid`.
SignalData *s = signal_map.getptr(p_signal);
if (!s) {
bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_signal) ||
(!script.is_null() && Ref<Script>(script)->has_script_signal(p_signal));
- ERR_FAIL_COND_V_MSG(signal_is_valid, false, "Attempt to disconnect a nonexistent connection from '" + to_string() + "'. Signal: '" + p_signal + "', callable: '" + p_callable + "'.");
+ ERR_FAIL_COND_V_MSG(signal_is_valid, false, vformat("Attempt to disconnect a nonexistent connection from '%s'. Signal: '%s', callable: '%s'.", to_string(), p_signal, p_callable));
}
- ERR_FAIL_NULL_V_MSG(s, false, vformat("Disconnecting nonexistent signal '%s' in %s.", p_signal, to_string()));
+ ERR_FAIL_NULL_V_MSG(s, false, vformat("Disconnecting nonexistent signal '%s' in '%s'.", p_signal, to_string()));
- ERR_FAIL_COND_V_MSG(!s->slot_map.has(*p_callable.get_base_comparator()), false, "Attempt to disconnect a nonexistent connection from '" + to_string() + "'. Signal: '" + p_signal + "', callable: '" + p_callable + "'.");
+ ERR_FAIL_COND_V_MSG(!s->slot_map.has(*p_callable.get_base_comparator()), false, vformat("Attempt to disconnect a nonexistent connection from '%s'. Signal: '%s', callable: '%s'.", to_string(), p_signal, p_callable));
SignalData::Slot *slot = &s->slot_map[*p_callable.get_base_comparator()];
@@ -2128,7 +2128,7 @@ Object::~Object() {
if (_emitting) {
//@todo this may need to actually reach the debugger prioritarily somehow because it may crash before
- ERR_PRINT("Object " + to_string() + " was freed or unreferenced while a signal is being emitted from it. Try connecting to the signal using 'CONNECT_DEFERRED' flag, or use queue_free() to free the object (if this object is a Node) to avoid this error and potential crashes.");
+ ERR_PRINT(vformat("Object '%s' was freed or unreferenced while a signal is being emitted from it. Try connecting to the signal using 'CONNECT_DEFERRED' flag, or use queue_free() to free the object (if this object is a Node) to avoid this error and potential crashes.", to_string()));
}
// Drop all connections to the signals of this object.
diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp
index c5856a8a81..542cb2b24d 100644
--- a/core/object/script_language.cpp
+++ b/core/object/script_language.cpp
@@ -239,9 +239,9 @@ Error ScriptServer::register_language(ScriptLanguage *p_language) {
ERR_FAIL_COND_V_MSG(_language_count >= MAX_LANGUAGES, ERR_UNAVAILABLE, "Script languages limit has been reach, cannot register more.");
for (int i = 0; i < _language_count; i++) {
const ScriptLanguage *other_language = _languages[i];
- ERR_FAIL_COND_V_MSG(other_language->get_extension() == p_language->get_extension(), ERR_ALREADY_EXISTS, "A script language with extension '" + p_language->get_extension() + "' is already registered.");
- ERR_FAIL_COND_V_MSG(other_language->get_name() == p_language->get_name(), ERR_ALREADY_EXISTS, "A script language with name '" + p_language->get_name() + "' is already registered.");
- ERR_FAIL_COND_V_MSG(other_language->get_type() == p_language->get_type(), ERR_ALREADY_EXISTS, "A script language with type '" + p_language->get_type() + "' is already registered.");
+ ERR_FAIL_COND_V_MSG(other_language->get_extension() == p_language->get_extension(), ERR_ALREADY_EXISTS, vformat("A script language with extension '%s' is already registered.", p_language->get_extension()));
+ ERR_FAIL_COND_V_MSG(other_language->get_name() == p_language->get_name(), ERR_ALREADY_EXISTS, vformat("A script language with name '%s' is already registered.", p_language->get_name()));
+ ERR_FAIL_COND_V_MSG(other_language->get_type() == p_language->get_type(), ERR_ALREADY_EXISTS, vformat("A script language with type '%s' is already registered.", p_language->get_type()));
}
_languages[_language_count++] = p_language;
return OK;
diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp
index 03537dbeb1..7126852a5a 100644
--- a/core/object/undo_redo.cpp
+++ b/core/object/undo_redo.cpp
@@ -364,7 +364,7 @@ void UndoRedo::_process_operation_list(List<Operation>::Element *E, bool p_execu
Variant ret;
op.callable.callp(nullptr, 0, ret, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT("Error calling UndoRedo method operation '" + String(op.name) + "': " + Variant::get_call_error_text(obj, op.name, nullptr, 0, ce));
+ ERR_PRINT(vformat("Error calling UndoRedo method operation '%s': %s.", String(op.name), Variant::get_call_error_text(obj, op.name, nullptr, 0, ce)));
}
#ifdef TOOLS_ENABLED
Resource *res = Object::cast_to<Resource>(obj);
diff --git a/core/os/os.cpp b/core/os/os.cpp
index 642de11a9f..aed14d48c0 100644
--- a/core/os/os.cpp
+++ b/core/os/os.cpp
@@ -352,7 +352,7 @@ void OS::ensure_user_data_dir() {
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
Error err = da->make_dir_recursive(dd);
- ERR_FAIL_COND_MSG(err != OK, "Error attempting to create data dir: " + dd + ".");
+ ERR_FAIL_COND_MSG(err != OK, vformat("Error attempting to create data dir: %s.", dd));
}
String OS::get_model_name() const {
diff --git a/core/string/node_path.cpp b/core/string/node_path.cpp
index fdc72bc8dc..3faf3bb0c5 100644
--- a/core/string/node_path.cpp
+++ b/core/string/node_path.cpp
@@ -420,7 +420,7 @@ NodePath::NodePath(const String &p_path) {
continue; // Allow end-of-path :
}
- ERR_FAIL_MSG("Invalid NodePath '" + p_path + "'.");
+ ERR_FAIL_MSG(vformat("Invalid NodePath '%s'.", p_path));
}
subpath.push_back(str);
diff --git a/core/string/translation_po.cpp b/core/string/translation_po.cpp
index 8e275505b0..da79e472e7 100644
--- a/core/string/translation_po.cpp
+++ b/core/string/translation_po.cpp
@@ -246,7 +246,7 @@ void TranslationPO::add_message(const StringName &p_src_text, const StringName &
HashMap<StringName, Vector<StringName>> &map_id_str = translation_map[p_context];
if (map_id_str.has(p_src_text)) {
- WARN_PRINT("Double translations for \"" + String(p_src_text) + "\" under the same context \"" + String(p_context) + "\" for locale \"" + get_locale() + "\".\nThere should only be one unique translation for a given string under the same context.");
+ WARN_PRINT(vformat("Double translations for \"%s\" under the same context \"%s\" for locale \"%s\".\nThere should only be one unique translation for a given string under the same context.", String(p_src_text), String(p_context), get_locale()));
map_id_str[p_src_text].set(0, p_xlated_text);
} else {
map_id_str[p_src_text].push_back(p_xlated_text);
@@ -254,12 +254,12 @@ void TranslationPO::add_message(const StringName &p_src_text, const StringName &
}
void TranslationPO::add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context) {
- ERR_FAIL_COND_MSG(p_plural_xlated_texts.size() != plural_forms, "Trying to add plural texts that don't match the required number of plural forms for locale \"" + get_locale() + "\"");
+ ERR_FAIL_COND_MSG(p_plural_xlated_texts.size() != plural_forms, vformat("Trying to add plural texts that don't match the required number of plural forms for locale \"%s\".", get_locale()));
HashMap<StringName, Vector<StringName>> &map_id_str = translation_map[p_context];
if (map_id_str.has(p_src_text)) {
- WARN_PRINT("Double translations for \"" + p_src_text + "\" under the same context \"" + p_context + "\" for locale " + get_locale() + ".\nThere should only be one unique translation for a given string under the same context.");
+ WARN_PRINT(vformat("Double translations for \"%s\" under the same context \"%s\" for locale %s.\nThere should only be one unique translation for a given string under the same context.", p_src_text, p_context, get_locale()));
map_id_str[p_src_text].clear();
}
@@ -280,7 +280,7 @@ StringName TranslationPO::get_message(const StringName &p_src_text, const String
if (!translation_map.has(p_context) || !translation_map[p_context].has(p_src_text)) {
return StringName();
}
- ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].is_empty(), StringName(), "Source text \"" + String(p_src_text) + "\" is registered but doesn't have a translation. Please report this bug.");
+ ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].is_empty(), StringName(), vformat("Source text \"%s\" is registered but doesn't have a translation. Please report this bug.", String(p_src_text)));
return translation_map[p_context][p_src_text][0];
}
@@ -296,7 +296,7 @@ StringName TranslationPO::get_plural_message(const StringName &p_src_text, const
if (!translation_map.has(p_context) || !translation_map[p_context].has(p_src_text)) {
return StringName();
}
- ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].is_empty(), StringName(), "Source text \"" + String(p_src_text) + "\" is registered but doesn't have a translation. Please report this bug.");
+ ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].is_empty(), StringName(), vformat("Source text \"%s\" is registered but doesn't have a translation. Please report this bug.", String(p_src_text)));
int plural_index = _get_plural_index(p_n);
ERR_FAIL_COND_V_MSG(plural_index < 0 || translation_map[p_context][p_src_text].size() < plural_index + 1, StringName(), "Plural index returned or number of plural translations is not valid. Please report this bug.");
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index 28319fc643..a78f0ff5ff 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -1819,7 +1819,7 @@ String String::num(double p_num, int p_decimals) {
#endif
buf[324] = 0;
- //destroy trailing zeroes
+ // Destroy trailing zeroes, except one after period.
{
bool period = false;
int z = 0;
@@ -1836,7 +1836,7 @@ String String::num(double p_num, int p_decimals) {
if (buf[z] == '0') {
buf[z] = 0;
} else if (buf[z] == '.') {
- buf[z] = 0;
+ buf[z + 1] = '0';
break;
} else {
break;
@@ -1929,14 +1929,28 @@ String String::num_real(double p_num, bool p_trailing) {
return num_int64((int64_t)p_num);
}
}
-#ifdef REAL_T_IS_DOUBLE
int decimals = 14;
-#else
+ // We want to align the digits to the above sane default, so we only need
+ // to subtract log10 for numbers with a positive power of ten magnitude.
+ const double abs_num = Math::abs(p_num);
+ if (abs_num > 10) {
+ decimals -= (int)floor(log10(abs_num));
+ }
+ return num(p_num, decimals);
+}
+
+String String::num_real(float p_num, bool p_trailing) {
+ if (p_num == (float)(int64_t)p_num) {
+ if (p_trailing) {
+ return num_int64((int64_t)p_num) + ".0";
+ } else {
+ return num_int64((int64_t)p_num);
+ }
+ }
int decimals = 6;
-#endif
// We want to align the digits to the above sane default, so we only need
// to subtract log10 for numbers with a positive power of ten magnitude.
- double abs_num = Math::abs(p_num);
+ const float abs_num = Math::abs(p_num);
if (abs_num > 10) {
decimals -= (int)floor(log10(abs_num));
}
@@ -4616,7 +4630,7 @@ String String::humanize_size(uint64_t p_size) {
}
if (magnitude == 0) {
- return String::num(p_size) + " " + RTR("B");
+ return String::num_uint64(p_size) + " " + RTR("B");
} else {
String suffix;
switch (magnitude) {
diff --git a/core/string/ustring.h b/core/string/ustring.h
index 11c0f74062..11d187beb4 100644
--- a/core/string/ustring.h
+++ b/core/string/ustring.h
@@ -332,6 +332,7 @@ public:
static String num(double p_num, int p_decimals = -1);
static String num_scientific(double p_num);
static String num_real(double p_num, bool p_trailing = true);
+ static String num_real(float p_num, bool p_trailing = true);
static String num_int64(int64_t p_num, int base = 10, bool capitalize_hex = false);
static String num_uint64(uint64_t p_num, int base = 10, bool capitalize_hex = false);
static String chr(char32_t p_char);
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/array.cpp b/core/variant/array.cpp
index 3e62d3dffa..f7a86b8fa3 100644
--- a/core/variant/array.cpp
+++ b/core/variant/array.cpp
@@ -385,7 +385,7 @@ int Array::find_custom(const Callable &p_callable, int p_from) const {
Callable::CallError ce;
p_callable.callp(argptrs, 1, res, ce);
if (unlikely(ce.error != Callable::CallError::CALL_OK)) {
- ERR_FAIL_V_MSG(ret, "Error calling method from 'find_custom': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ ERR_FAIL_V_MSG(ret, vformat("Error calling method from 'find_custom': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
ERR_FAIL_COND_V_MSG(res.get_type() != Variant::Type::BOOL, ret, "Error on method from 'find_custom': Return type of callable must be boolean.");
@@ -445,7 +445,7 @@ int Array::rfind_custom(const Callable &p_callable, int p_from) const {
Callable::CallError ce;
p_callable.callp(argptrs, 1, res, ce);
if (unlikely(ce.error != Callable::CallError::CALL_OK)) {
- ERR_FAIL_V_MSG(-1, "Error calling method from 'rfind_custom': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ ERR_FAIL_V_MSG(-1, vformat("Error calling method from 'rfind_custom': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
ERR_FAIL_COND_V_MSG(res.get_type() != Variant::Type::BOOL, -1, "Error on method from 'rfind_custom': Return type of callable must be boolean.");
@@ -574,7 +574,7 @@ Array Array::filter(const Callable &p_callable) const {
Callable::CallError ce;
p_callable.callp(argptrs, 1, result, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_V_MSG(Array(), "Error calling method from 'filter': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ ERR_FAIL_V_MSG(Array(), vformat("Error calling method from 'filter': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
if (result.operator bool()) {
@@ -600,7 +600,7 @@ Array Array::map(const Callable &p_callable) const {
Callable::CallError ce;
p_callable.callp(argptrs, 1, result, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_V_MSG(Array(), "Error calling method from 'map': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ ERR_FAIL_V_MSG(Array(), vformat("Error calling method from 'map': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
new_arr[i] = result;
@@ -626,7 +626,7 @@ Variant Array::reduce(const Callable &p_callable, const Variant &p_accum) const
Callable::CallError ce;
p_callable.callp(argptrs, 2, result, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_V_MSG(Variant(), "Error calling method from 'reduce': " + Variant::get_callable_error_text(p_callable, argptrs, 2, ce));
+ ERR_FAIL_V_MSG(Variant(), vformat("Error calling method from 'reduce': %s.", Variant::get_callable_error_text(p_callable, argptrs, 2, ce)));
}
ret = result;
}
@@ -643,7 +643,7 @@ bool Array::any(const Callable &p_callable) const {
Callable::CallError ce;
p_callable.callp(argptrs, 1, result, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_V_MSG(false, "Error calling method from 'any': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ ERR_FAIL_V_MSG(false, vformat("Error calling method from 'any': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
if (result.operator bool()) {
@@ -665,7 +665,7 @@ bool Array::all(const Callable &p_callable) const {
Callable::CallError ce;
p_callable.callp(argptrs, 1, result, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_V_MSG(false, "Error calling method from 'all': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ ERR_FAIL_V_MSG(false, vformat("Error calling method from 'all': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
if (!(result.operator bool())) {
diff --git a/core/variant/container_type_validate.h b/core/variant/container_type_validate.h
index 0a23c69cb4..8971fadf73 100644
--- a/core/variant/container_type_validate.h
+++ b/core/variant/container_type_validate.h
@@ -94,7 +94,7 @@ struct ContainerTypeValidate {
return true;
}
- ERR_FAIL_V_MSG(false, "Attempted to " + String(p_operation) + " a variable of type '" + Variant::get_type_name(inout_variant.get_type()) + "' into a " + where + " of type '" + Variant::get_type_name(type) + "'.");
+ ERR_FAIL_V_MSG(false, vformat("Attempted to %s a variable of type '%s' into a %s of type '%s'.", String(p_operation), Variant::get_type_name(inout_variant.get_type()), where, Variant::get_type_name(type)));
}
if (type != Variant::OBJECT) {
@@ -113,7 +113,7 @@ struct ContainerTypeValidate {
return true; // This is fine, it's null.
}
Object *object = ObjectDB::get_instance(object_id);
- ERR_FAIL_NULL_V_MSG(object, false, "Attempted to " + String(p_operation) + " an invalid (previously freed?) object instance into a '" + String(where) + ".");
+ ERR_FAIL_NULL_V_MSG(object, false, vformat("Attempted to %s an invalid (previously freed?) object instance into a '%s'.", String(p_operation), String(where)));
#else
Object *object = p_variant;
if (object == nullptr) {
@@ -126,7 +126,7 @@ struct ContainerTypeValidate {
StringName obj_class = object->get_class_name();
if (obj_class != class_name) {
- ERR_FAIL_COND_V_MSG(!ClassDB::is_parent_class(object->get_class_name(), class_name), false, "Attempted to " + String(p_operation) + " an object of type '" + object->get_class() + "' into a " + where + ", which does not inherit from '" + String(class_name) + "'.");
+ ERR_FAIL_COND_V_MSG(!ClassDB::is_parent_class(object->get_class_name(), class_name), false, vformat("Attempted to %s an object of type '%s' into a %s, which does not inherit from '%s'.", String(p_operation), object->get_class(), where, String(class_name)));
}
if (script.is_null()) {
@@ -136,8 +136,8 @@ struct ContainerTypeValidate {
Ref<Script> other_script = object->get_script();
// Check base script..
- ERR_FAIL_COND_V_MSG(other_script.is_null(), false, "Attempted to " + String(p_operation) + " an object into a " + String(where) + ", that does not inherit from '" + String(script->get_class_name()) + "'.");
- ERR_FAIL_COND_V_MSG(!other_script->inherits_script(script), false, "Attempted to " + String(p_operation) + " an object into a " + String(where) + ", that does not inherit from '" + String(script->get_class_name()) + "'.");
+ ERR_FAIL_COND_V_MSG(other_script.is_null(), false, vformat("Attempted to %s an object into a %s, that does not inherit from '%s'.", String(p_operation), String(where), String(script->get_class_name())));
+ ERR_FAIL_COND_V_MSG(!other_script->inherits_script(script), false, vformat("Attempted to %s an object into a %s, that does not inherit from '%s'.", String(p_operation), String(where), String(script->get_class_name())));
return true;
}
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index e2865a06be..65bfc29a55 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -1736,7 +1736,7 @@ String Variant::stringify(int recursion_count) const {
case INT:
return itos(_data._int);
case FLOAT:
- return rtos(_data._float);
+ return String::num_real(_data._float, true);
case STRING:
return *reinterpret_cast<const String *>(_data._mem);
case VECTOR2:
@@ -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/core/variant/variant_construct.cpp b/core/variant/variant_construct.cpp
index 6c37d5e4b7..9706457549 100644
--- a/core/variant/variant_construct.cpp
+++ b/core/variant/variant_construct.cpp
@@ -43,7 +43,7 @@ static LocalVector<VariantConstructData> construct_data[Variant::VARIANT_MAX];
template <typename T>
static void add_constructor(const Vector<String> &arg_names) {
- ERR_FAIL_COND_MSG(arg_names.size() != T::get_argument_count(), "Argument names size mismatch for " + Variant::get_type_name(T::get_base_type()) + ".");
+ ERR_FAIL_COND_MSG(arg_names.size() != T::get_argument_count(), vformat("Argument names size mismatch for '%s'.", Variant::get_type_name(T::get_base_type())));
VariantConstructData cd;
cd.construct = T::construct;
diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp
index 384fe6c4a6..50932afbb6 100644
--- a/core/variant/variant_utility.cpp
+++ b/core/variant/variant_utility.cpp
@@ -1671,7 +1671,7 @@ static void register_utility_function(const String &p_name, const Vector<String>
bfi.argnames = argnames;
bfi.argcount = T::get_argument_count();
if (!bfi.is_vararg) {
- ERR_FAIL_COND_MSG(argnames.size() != bfi.argcount, "wrong number of arguments binding utility function: " + name);
+ ERR_FAIL_COND_MSG(argnames.size() != bfi.argcount, vformat("Wrong number of arguments binding utility function: '%s'.", name));
}
bfi.get_arg_type = T::get_argument_type;
bfi.return_type = T::get_return_type();
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/ClassDB.xml b/doc/classes/ClassDB.xml
index d1b666097c..d01a308e32 100644
--- a/doc/classes/ClassDB.xml
+++ b/doc/classes/ClassDB.xml
@@ -16,7 +16,7 @@
Returns [code]true[/code] if objects can be instantiated from the specified [param class], otherwise returns [code]false[/code].
</description>
</method>
- <method name="class_call_static_method" qualifiers="vararg">
+ <method name="class_call_static" qualifiers="vararg">
<return type="Variant" />
<param index="0" name="class" type="StringName" />
<param index="1" name="method" type="StringName" />
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index f6ff6da0c3..dafa86d42e 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -139,11 +139,12 @@
<description>
Displays OS native dialog for selecting files or directories in the file system.
Each filter string in the [param filters] array should be formatted like this: [code]*.txt,*.doc;Text Files[/code]. The description text of the filter is optional and can be omitted. See also [member FileDialog.filters].
- Callbacks have the following arguments: [code]status: bool, selected_paths: PackedStringArray, selected_filter_index: int[/code].
- [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG_FILE] feature. Supported platforms include Linux (X11/Wayland), Windows, and macOS.
+ Callbacks have the following arguments: [code]status: bool, selected_paths: PackedStringArray, selected_filter_index: int[/code]. [b]On Android,[/b] callback argument [code]selected_filter_index[/code] is always zero.
+ [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG_FILE] feature. Supported platforms include Linux (X11/Wayland), Windows, macOS, and Android.
[b]Note:[/b] [param current_directory] might be ignored.
- [b]Note:[/b] On Linux, [param show_hidden] is ignored.
- [b]Note:[/b] On macOS, native file dialogs have no title.
+ [b]Note:[/b] On Android, the filter strings in the [param filters] array should be specified using MIME types, for example:[code]image/png, image/jpeg"[/code]. Additionally, the [param mode] [constant FILE_DIALOG_MODE_OPEN_ANY] is not supported on Android.
+ [b]Note:[/b] On Android and Linux, [param show_hidden] is ignored.
+ [b]Note:[/b] On Android and macOS, native file dialogs have no title.
[b]Note:[/b] On macOS, sandboxed apps will save security-scoped bookmarks to retain access to the opened folders across multiple sessions. Use [method OS.get_granted_permissions] to get a list of saved bookmarks.
</description>
</method>
@@ -166,7 +167,7 @@
- [code]"values"[/code] - [PackedStringArray] of values. If empty, boolean option (check box) is used.
- [code]"default"[/code] - default selected option index ([int]) or default boolean value ([bool]).
Callbacks have the following arguments: [code]status: bool, selected_paths: PackedStringArray, selected_filter_index: int, selected_option: Dictionary[/code].
- [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG_FILE] feature. Supported platforms include Linux (X11/Wayland), Windows, and macOS.
+ [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG_FILE_EXTRA] feature. Supported platforms include Linux (X11/Wayland), Windows, and macOS.
[b]Note:[/b] [param current_directory] might be ignored.
[b]Note:[/b] On Linux (X11), [param show_hidden] is ignored.
[b]Note:[/b] On macOS, native file dialogs have no title.
@@ -184,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">
@@ -1889,7 +1890,10 @@
Display server supports spawning text input dialogs using the operating system's native look-and-feel. See [method dialog_input_text]. [b]Windows, macOS[/b]
</constant>
<constant name="FEATURE_NATIVE_DIALOG_FILE" value="25" enum="Feature">
- Display server supports spawning dialogs for selecting files or directories using the operating system's native look-and-feel. See [method file_dialog_show] and [method file_dialog_with_options_show]. [b]Windows, macOS, Linux (X11/Wayland)[/b]
+ Display server supports spawning dialogs for selecting files or directories using the operating system's native look-and-feel. See [method file_dialog_show]. [b]Windows, macOS, Linux (X11/Wayland), Android[/b]
+ </constant>
+ <constant name="FEATURE_NATIVE_DIALOG_FILE_EXTRA" value="26" enum="Feature">
+ The display server supports all features of [constant FEATURE_NATIVE_DIALOG_FILE], with the added functionality of Options and native dialog file access to [code]res://[/code] and [code]user://[/code] paths. See [method file_dialog_show] and [method file_dialog_with_options_show]. [b]Windows, macOS, Linux (X11/Wayland)[/b]
</constant>
<constant name="MOUSE_MODE_VISIBLE" value="0" enum="MouseMode">
Makes the mouse cursor visible if it is hidden.
diff --git a/doc/classes/EditorFeatureProfile.xml b/doc/classes/EditorFeatureProfile.xml
index 3aa1e63aac..c125c923ef 100644
--- a/doc/classes/EditorFeatureProfile.xml
+++ b/doc/classes/EditorFeatureProfile.xml
@@ -121,7 +121,10 @@
<constant name="FEATURE_HISTORY_DOCK" value="7" enum="Feature">
The History dock. If this feature is disabled, the History dock won't be visible.
</constant>
- <constant name="FEATURE_MAX" value="8" enum="Feature">
+ <constant name="FEATURE_GAME" value="8" enum="Feature">
+ The Game tab, which allows embedding the game window and selecting nodes by clicking inside of it. If this feature is disabled, the Game tab won't display.
+ </constant>
+ <constant name="FEATURE_MAX" value="9" enum="Feature">
Represents the size of the [enum Feature] enum.
</constant>
</constants>
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/FileDialog.xml b/doc/classes/FileDialog.xml
index 18b8eb1d39..64369bec30 100644
--- a/doc/classes/FileDialog.xml
+++ b/doc/classes/FileDialog.xml
@@ -146,6 +146,7 @@
</member>
<member name="filters" type="PackedStringArray" setter="set_filters" getter="get_filters" default="PackedStringArray()">
The available file type filters. Each filter string in the array should be formatted like this: [code]*.txt,*.doc;Text Files[/code]. The description text of the filter is optional and can be omitted.
+ [b]Note:[/b] For android native dialog, MIME types are used like this: [code]image/*, application/pdf[/code].
</member>
<member name="mode_overrides_title" type="bool" setter="set_mode_overrides_title" getter="is_mode_overriding_title" default="true">
If [code]true[/code], changing the [member file_mode] property will set the window title accordingly (e.g. setting [member file_mode] to [constant FILE_MODE_OPEN_FILE] will change the window title to "Open a File").
@@ -159,12 +160,13 @@
</member>
<member name="show_hidden_files" type="bool" setter="set_show_hidden_files" getter="is_showing_hidden_files" default="false">
If [code]true[/code], the dialog will show hidden files.
- [b]Note:[/b] This property is ignored by native file dialogs on Linux.
+ [b]Note:[/b] This property is ignored by native file dialogs on Android and Linux.
</member>
<member name="size" type="Vector2i" setter="set_size" getter="get_size" overrides="Window" default="Vector2i(640, 360)" />
<member name="title" type="String" setter="set_title" getter="get_title" overrides="Window" default="&quot;Save a File&quot;" />
<member name="use_native_dialog" type="bool" setter="set_use_native_dialog" getter="get_use_native_dialog" default="false">
If [code]true[/code], and if supported by the current [DisplayServer], OS native dialog will be used instead of custom one.
+ [b]Note:[/b] On Android, it is only supported when using [constant ACCESS_FILESYSTEM]. For access mode [constant ACCESS_RESOURCES] and [constant ACCESS_USERDATA], the system will fall back to custom FileDialog.
[b]Note:[/b] On Linux and macOS, sandboxed apps always use native dialogs to access the host file system.
[b]Note:[/b] On macOS, sandboxed apps will save security-scoped bookmarks to retain access to the opened folders across multiple sessions. Use [method OS.get_granted_permissions] to get a list of saved bookmarks.
[b]Note:[/b] Native dialogs are isolated from the base process, file dialog properties can't be modified once the dialog is shown.
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 3358e6838c..4d8ea28bc6 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -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.
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index 16a554eded..90a0798265 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -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" />
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/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/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/shader_gles3.cpp b/drivers/gles3/shader_gles3.cpp
index b73debf04a..83f74865f6 100644
--- a/drivers/gles3/shader_gles3.cpp
+++ b/drivers/gles3/shader_gles3.cpp
@@ -168,6 +168,10 @@ void ShaderGLES3::_build_variant_code(StringBuilder &builder, uint32_t p_variant
builder.append("#version 300 es\n");
}
+ if (GLES3::Config::get_singleton()->polyfill_half2float) {
+ builder.append("#define USE_HALF2FLOAT\n");
+ }
+
for (int i = 0; i < specialization_count; i++) {
if (p_specialization & (uint64_t(1) << uint64_t(i))) {
builder.append("#define " + String(specializations[i].name) + "\n");
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/shaders/stdlib_inc.glsl b/drivers/gles3/shaders/stdlib_inc.glsl
index f88c218506..004db9e5d0 100644
--- a/drivers/gles3/shaders/stdlib_inc.glsl
+++ b/drivers/gles3/shaders/stdlib_inc.glsl
@@ -1,25 +1,28 @@
-
// Compatibility renames. These are exposed with the "godot_" prefix
// to work around two distinct Adreno bugs:
// 1. Some Adreno devices expose ES310 functions in ES300 shaders.
// Internally, we must use the "godot_" prefix, but user shaders
// will be mapped automatically.
// 2. Adreno 3XX devices have poor implementations of the other packing
-// functions, so we just use our own everywhere to keep it simple.
+// functions, so we just use our own there to keep it simple.
+#ifdef USE_HALF2FLOAT
// Floating point pack/unpack functions are part of the GLSL ES 300 specification used by web and mobile.
+// It appears to be safe to expose these on mobile, but when running through ANGLE this appears to break.
uint float2half(uint f) {
- uint b = f + uint(0x00001000);
- uint e = (b & uint(0x7F800000)) >> 23;
- uint m = b & uint(0x007FFFFF);
- return (b & uint(0x80000000)) >> uint(16) | uint(e > uint(112)) * ((((e - uint(112)) << uint(10)) & uint(0x7C00)) | m >> uint(13)) | (uint(e < uint(113)) & uint(e > uint(101))) * ((((uint(0x007FF000) + m) >> (uint(125) - e)) + uint(1)) >> uint(1)) | uint(e > uint(143)) * uint(0x7FFF);
+ uint e = f & uint(0x7f800000);
+ if (e <= uint(0x38000000)) {
+ return uint(0);
+ } else {
+ return ((f >> uint(16)) & uint(0x8000)) |
+ (((e - uint(0x38000000)) >> uint(13)) & uint(0x7c00)) |
+ ((f >> uint(13)) & uint(0x03ff));
+ }
}
uint half2float(uint h) {
- uint e = (h & uint(0x7C00)) >> uint(10);
- uint m = (h & uint(0x03FF)) << uint(13);
- uint v = m >> uint(23);
- return (h & uint(0x8000)) << uint(16) | uint(e != uint(0)) * ((e + uint(112)) << uint(23) | m) | (uint(e == uint(0)) & uint(m != uint(0))) * ((v - uint(37)) << uint(23) | ((m << (uint(150) - v)) & uint(0x007FE000)));
+ uint h_e = h & uint(0x7c00);
+ return ((h & uint(0x8000)) << uint(16)) | uint((h_e >> uint(10)) != uint(0)) * (((h_e + uint(0x1c000)) << uint(13)) | ((h & uint(0x03ff)) << uint(13)));
}
uint godot_packHalf2x16(vec2 v) {
@@ -50,6 +53,17 @@ vec2 godot_unpackSnorm2x16(uint p) {
return clamp((v - 32767.0) * vec2(0.00003051851), vec2(-1.0), vec2(1.0));
}
+#define packHalf2x16 godot_packHalf2x16
+#define unpackHalf2x16 godot_unpackHalf2x16
+#define packUnorm2x16 godot_packUnorm2x16
+#define unpackUnorm2x16 godot_unpackUnorm2x16
+#define packSnorm2x16 godot_packSnorm2x16
+#define unpackSnorm2x16 godot_unpackSnorm2x16
+
+#endif // USE_HALF2FLOAT
+
+// Always expose these as they are ES310 functions and not available in ES300 or GLSL 330.
+
uint godot_packUnorm4x8(vec4 v) {
uvec4 uv = uvec4(round(clamp(v, vec4(0.0), vec4(1.0)) * 255.0));
return uv.x | (uv.y << uint(8)) | (uv.z << uint(16)) | (uv.w << uint(24));
@@ -73,9 +87,3 @@ vec4 godot_unpackSnorm4x8(uint p) {
#define unpackUnorm4x8 godot_unpackUnorm4x8
#define packSnorm4x8 godot_packSnorm4x8
#define unpackSnorm4x8 godot_unpackSnorm4x8
-#define packHalf2x16 godot_packHalf2x16
-#define unpackHalf2x16 godot_unpackHalf2x16
-#define packUnorm2x16 godot_packUnorm2x16
-#define unpackUnorm2x16 godot_unpackUnorm2x16
-#define packSnorm2x16 godot_packSnorm2x16
-#define unpackSnorm2x16 godot_unpackSnorm2x16
diff --git a/drivers/gles3/storage/config.cpp b/drivers/gles3/storage/config.cpp
index 209b7dd7d2..07dd5f58c1 100644
--- a/drivers/gles3/storage/config.cpp
+++ b/drivers/gles3/storage/config.cpp
@@ -231,6 +231,13 @@ Config::Config() {
} else if (rendering_device_name == "PowerVR Rogue GE8320") {
disable_transform_feedback_shader_cache = true;
}
+
+ if (OS::get_singleton()->get_current_rendering_driver_name() == "opengl3_angle") {
+ polyfill_half2float = false;
+ }
+#ifdef WEB_ENABLED
+ polyfill_half2float = false;
+#endif
}
Config::~Config() {
diff --git a/drivers/gles3/storage/config.h b/drivers/gles3/storage/config.h
index 06d88429be..51f6714320 100644
--- a/drivers/gles3/storage/config.h
+++ b/drivers/gles3/storage/config.h
@@ -95,13 +95,16 @@ public:
bool multiview_supported = false;
bool external_texture_supported = false;
- // Adreno 3XX compatibility
- bool disable_particles_workaround = false; // set to 'true' to disable 'GPUParticles'
+ // Adreno 3XX compatibility.
+ bool disable_particles_workaround = false; // Set to 'true' to disable 'GPUParticles'.
bool flip_xy_workaround = false;
- // PowerVR GE 8320 workaround
+ // PowerVR GE 8320 workaround.
bool disable_transform_feedback_shader_cache = false;
+ // ANGLE shader workaround.
+ bool polyfill_half2float = true;
+
#ifdef ANDROID_ENABLED
PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC eglFramebufferTextureMultiviewOVR = nullptr;
PFNGLTEXSTORAGE3DMULTISAMPLEPROC eglTexStorage3DMultisample = nullptr;
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/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp
index ee8aae661b..0f948b4ed5 100644
--- a/editor/debugger/editor_debugger_node.cpp
+++ b/editor/debugger/editor_debugger_node.cpp
@@ -105,6 +105,7 @@ ScriptEditorDebugger *EditorDebuggerNode::_add_debugger() {
node->connect("breakpoint_selected", callable_mp(this, &EditorDebuggerNode::_error_selected).bind(id));
node->connect("clear_execution", callable_mp(this, &EditorDebuggerNode::_clear_execution));
node->connect("breaked", callable_mp(this, &EditorDebuggerNode::_breaked).bind(id));
+ node->connect("remote_tree_select_requested", callable_mp(this, &EditorDebuggerNode::_remote_tree_select_requested).bind(id));
node->connect("remote_tree_updated", callable_mp(this, &EditorDebuggerNode::_remote_tree_updated).bind(id));
node->connect("remote_object_updated", callable_mp(this, &EditorDebuggerNode::_remote_object_updated).bind(id));
node->connect("remote_object_property_updated", callable_mp(this, &EditorDebuggerNode::_remote_object_property_updated).bind(id));
@@ -637,6 +638,13 @@ void EditorDebuggerNode::request_remote_tree() {
get_current_debugger()->request_remote_tree();
}
+void EditorDebuggerNode::_remote_tree_select_requested(ObjectID p_id, int p_debugger) {
+ if (p_debugger != tabs->get_current_tab()) {
+ return;
+ }
+ remote_scene_tree->select_node(p_id);
+}
+
void EditorDebuggerNode::_remote_tree_updated(int p_debugger) {
if (p_debugger != tabs->get_current_tab()) {
return;
diff --git a/editor/debugger/editor_debugger_node.h b/editor/debugger/editor_debugger_node.h
index 12e097f652..12c0d30c42 100644
--- a/editor/debugger/editor_debugger_node.h
+++ b/editor/debugger/editor_debugger_node.h
@@ -51,11 +51,8 @@ class EditorDebuggerNode : public MarginContainer {
public:
enum CameraOverride {
OVERRIDE_NONE,
- OVERRIDE_2D,
- OVERRIDE_3D_1, // 3D Viewport 1
- OVERRIDE_3D_2, // 3D Viewport 2
- OVERRIDE_3D_3, // 3D Viewport 3
- OVERRIDE_3D_4 // 3D Viewport 4
+ OVERRIDE_INGAME,
+ OVERRIDE_EDITORS,
};
private:
@@ -132,6 +129,7 @@ protected:
void _debugger_stopped(int p_id);
void _debugger_wants_stop(int p_id);
void _debugger_changed(int p_tab);
+ void _remote_tree_select_requested(ObjectID p_id, int p_debugger);
void _remote_tree_updated(int p_debugger);
void _remote_tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button);
void _remote_object_updated(ObjectID p_id, int p_debugger);
diff --git a/editor/debugger/editor_debugger_tree.cpp b/editor/debugger/editor_debugger_tree.cpp
index a900842651..4d67800e6e 100644
--- a/editor/debugger/editor_debugger_tree.cpp
+++ b/editor/debugger/editor_debugger_tree.cpp
@@ -30,6 +30,7 @@
#include "editor_debugger_tree.h"
+#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/gui/editor_file_dialog.h"
@@ -148,7 +149,8 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int
updating_scene_tree = true;
const String last_path = get_selected_path();
const String filter = SceneTreeDock::get_singleton()->get_filter();
- bool filter_changed = filter != last_filter;
+ bool should_scroll = scrolling_to_item || filter != last_filter;
+ scrolling_to_item = false;
TreeItem *scroll_item = nullptr;
// Nodes are in a flatten list, depth first. Use a stack of parents, avoid recursion.
@@ -185,8 +187,18 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int
// Select previously selected node.
if (debugger_id == p_debugger) { // Can use remote id.
if (node.id == inspected_object_id) {
+ if (selection_uncollapse_all) {
+ selection_uncollapse_all = false;
+
+ // Temporarily set to `false`, to allow caching the unfolds.
+ updating_scene_tree = false;
+ item->uncollapse_tree();
+ updating_scene_tree = true;
+ }
+
item->select(0);
- if (filter_changed) {
+
+ if (should_scroll) {
scroll_item = item;
}
}
@@ -194,7 +206,7 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int
if (last_path == _get_path(item)) {
updating_scene_tree = false; // Force emission of new selection.
item->select(0);
- if (filter_changed) {
+ if (should_scroll) {
scroll_item = item;
}
updating_scene_tree = true;
@@ -258,14 +270,30 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int
}
}
}
- debugger_id = p_debugger; // Needed by hook, could be avoided if every debugger had its own tree
+
+ debugger_id = p_debugger; // Needed by hook, could be avoided if every debugger had its own tree.
if (scroll_item) {
- callable_mp((Tree *)this, &Tree::scroll_to_item).call_deferred(scroll_item, false);
+ scroll_to_item(scroll_item, false);
}
last_filter = filter;
updating_scene_tree = false;
}
+void EditorDebuggerTree::select_node(ObjectID p_id) {
+ // Manually select, as the tree control may be out-of-date for some reason (e.g. not shown yet).
+ selection_uncollapse_all = true;
+ inspected_object_id = uint64_t(p_id);
+ scrolling_to_item = true;
+ emit_signal(SNAME("object_selected"), inspected_object_id, debugger_id);
+
+ if (!updating_scene_tree) {
+ // Request a tree refresh.
+ EditorDebuggerNode::get_singleton()->request_remote_tree();
+ }
+ // Set the value immediately, so no update flooding happens and causes a crash.
+ updating_scene_tree = true;
+}
+
Variant EditorDebuggerTree::get_drag_data(const Point2 &p_point) {
if (get_button_id_at_position(p_point) != -1) {
return Variant();
diff --git a/editor/debugger/editor_debugger_tree.h b/editor/debugger/editor_debugger_tree.h
index 705df17baf..d048688cad 100644
--- a/editor/debugger/editor_debugger_tree.h
+++ b/editor/debugger/editor_debugger_tree.h
@@ -49,6 +49,8 @@ private:
ObjectID inspected_object_id;
int debugger_id = 0;
bool updating_scene_tree = false;
+ bool scrolling_to_item = false;
+ bool selection_uncollapse_all = false;
HashSet<ObjectID> unfold_cache;
PopupMenu *item_menu = nullptr;
EditorFileDialog *file_dialog = nullptr;
@@ -78,6 +80,7 @@ public:
ObjectID get_selected_object();
int get_current_debugger(); // Would love to have one tree for every debugger.
void update_scene_tree(const SceneDebuggerTree *p_tree, int p_debugger);
+ void select_node(ObjectID p_id);
EditorDebuggerTree();
};
diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp
index c42740de50..b78aad1721 100644
--- a/editor/debugger/script_editor_debugger.cpp
+++ b/editor/debugger/script_editor_debugger.cpp
@@ -806,6 +806,10 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, uint64_t p_thread
} else if (p_msg == "request_quit") {
emit_signal(SNAME("stop_requested"));
_stop_and_notify();
+ } else if (p_msg == "remote_node_clicked") {
+ if (!p_data.is_empty()) {
+ emit_signal(SNAME("remote_tree_select_requested"), p_data[0]);
+ }
} else if (p_msg == "performance:profile_names") {
Vector<StringName> monitors;
monitors.resize(p_data.size());
@@ -905,37 +909,42 @@ void ScriptEditorDebugger::_notification(int p_what) {
if (is_session_active()) {
peer->poll();
- if (camera_override == CameraOverride::OVERRIDE_2D) {
- Dictionary state = CanvasItemEditor::get_singleton()->get_state();
- float zoom = state["zoom"];
- Point2 offset = state["ofs"];
- Transform2D transform;
-
- transform.scale_basis(Size2(zoom, zoom));
- transform.columns[2] = -offset * zoom;
-
- Array msg;
- msg.push_back(transform);
- _put_msg("scene:override_camera_2D:transform", msg);
-
- } else if (camera_override >= CameraOverride::OVERRIDE_3D_1) {
- int viewport_idx = camera_override - CameraOverride::OVERRIDE_3D_1;
- Node3DEditorViewport *viewport = Node3DEditor::get_singleton()->get_editor_viewport(viewport_idx);
- Camera3D *const cam = viewport->get_camera_3d();
-
- Array msg;
- msg.push_back(cam->get_camera_transform());
- if (cam->get_projection() == Camera3D::PROJECTION_ORTHOGONAL) {
- msg.push_back(false);
- msg.push_back(cam->get_size());
- } else {
- msg.push_back(true);
- msg.push_back(cam->get_fov());
+ if (camera_override == CameraOverride::OVERRIDE_EDITORS) {
+ // CanvasItem Editor
+ {
+ Dictionary state = CanvasItemEditor::get_singleton()->get_state();
+ float zoom = state["zoom"];
+ Point2 offset = state["ofs"];
+ Transform2D transform;
+
+ transform.scale_basis(Size2(zoom, zoom));
+ transform.columns[2] = -offset * zoom;
+
+ Array msg;
+ msg.push_back(transform);
+ _put_msg("scene:transform_camera_2d", msg);
+ }
+
+ // Node3D Editor
+ {
+ Node3DEditorViewport *viewport = Node3DEditor::get_singleton()->get_last_used_viewport();
+ const Camera3D *cam = viewport->get_camera_3d();
+
+ Array msg;
+ msg.push_back(cam->get_camera_transform());
+ if (cam->get_projection() == Camera3D::PROJECTION_ORTHOGONAL) {
+ msg.push_back(false);
+ msg.push_back(cam->get_size());
+ } else {
+ msg.push_back(true);
+ msg.push_back(cam->get_fov());
+ }
+ msg.push_back(cam->get_near());
+ msg.push_back(cam->get_far());
+ _put_msg("scene:transform_camera_3d", msg);
}
- msg.push_back(cam->get_near());
- msg.push_back(cam->get_far());
- _put_msg("scene:override_camera_3D:transform", msg);
}
+
if (is_breaked() && can_request_idle_draw) {
_put_msg("servers:draw", Array());
can_request_idle_draw = false;
@@ -1469,23 +1478,10 @@ CameraOverride ScriptEditorDebugger::get_camera_override() const {
}
void ScriptEditorDebugger::set_camera_override(CameraOverride p_override) {
- if (p_override == CameraOverride::OVERRIDE_2D && camera_override != CameraOverride::OVERRIDE_2D) {
- Array msg;
- msg.push_back(true);
- _put_msg("scene:override_camera_2D:set", msg);
- } else if (p_override != CameraOverride::OVERRIDE_2D && camera_override == CameraOverride::OVERRIDE_2D) {
- Array msg;
- msg.push_back(false);
- _put_msg("scene:override_camera_2D:set", msg);
- } else if (p_override >= CameraOverride::OVERRIDE_3D_1 && camera_override < CameraOverride::OVERRIDE_3D_1) {
- Array msg;
- msg.push_back(true);
- _put_msg("scene:override_camera_3D:set", msg);
- } else if (p_override < CameraOverride::OVERRIDE_3D_1 && camera_override >= CameraOverride::OVERRIDE_3D_1) {
- Array msg;
- msg.push_back(false);
- _put_msg("scene:override_camera_3D:set", msg);
- }
+ Array msg;
+ msg.push_back(p_override != CameraOverride::OVERRIDE_NONE);
+ msg.push_back(p_override == CameraOverride::OVERRIDE_EDITORS);
+ _put_msg("scene:override_cameras", msg);
camera_override = p_override;
}
@@ -1776,6 +1772,7 @@ void ScriptEditorDebugger::_bind_methods() {
ADD_SIGNAL(MethodInfo("remote_object_updated", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("remote_object_property_updated", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property")));
ADD_SIGNAL(MethodInfo("remote_tree_updated"));
+ ADD_SIGNAL(MethodInfo("remote_tree_select_requested", PropertyInfo(Variant::NODE_PATH, "path")));
ADD_SIGNAL(MethodInfo("output", PropertyInfo(Variant::STRING, "msg"), PropertyInfo(Variant::INT, "level")));
ADD_SIGNAL(MethodInfo("stack_dump", PropertyInfo(Variant::ARRAY, "stack_dump")));
ADD_SIGNAL(MethodInfo("stack_frame_vars", PropertyInfo(Variant::INT, "num_vars")));
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 44fc9e3702..7ffbc39d75 100644
--- a/editor/editor_feature_profile.cpp
+++ b/editor/editor_feature_profile.cpp
@@ -43,6 +43,7 @@
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"),
@@ -54,6 +55,7 @@ const char *EditorFeatureProfile::feature_names[FEATURE_MAX] = {
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."),
@@ -65,6 +67,7 @@ const char *EditorFeatureProfile::feature_descriptions[FEATURE_MAX] = {
const char *EditorFeatureProfile::feature_identifiers[FEATURE_MAX] = {
"3d",
"script",
+ "game",
"asset_lib",
"scene_tree",
"node_dock",
@@ -307,6 +310,7 @@ void EditorFeatureProfile::_bind_methods() {
BIND_ENUM_CONSTANT(FEATURE_FILESYSTEM_DOCK);
BIND_ENUM_CONSTANT(FEATURE_IMPORT_DOCK);
BIND_ENUM_CONSTANT(FEATURE_HISTORY_DOCK);
+ BIND_ENUM_CONSTANT(FEATURE_GAME);
BIND_ENUM_CONSTANT(FEATURE_MAX);
}
diff --git a/editor/editor_feature_profile.h b/editor/editor_feature_profile.h
index 7458a04e19..e84936dd34 100644
--- a/editor/editor_feature_profile.h
+++ b/editor/editor_feature_profile.h
@@ -55,6 +55,7 @@ public:
FEATURE_FILESYSTEM_DOCK,
FEATURE_IMPORT_DOCK,
FEATURE_HISTORY_DOCK,
+ FEATURE_GAME,
FEATURE_MAX
};
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_main_screen.h b/editor/editor_main_screen.h
index 153a182bc2..ca78ceaa88 100644
--- a/editor/editor_main_screen.h
+++ b/editor/editor_main_screen.h
@@ -47,6 +47,7 @@ public:
EDITOR_2D = 0,
EDITOR_3D,
EDITOR_SCRIPT,
+ EDITOR_GAME,
EDITOR_ASSETLIB,
};
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 161e518fc6..36b43b7e9b 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -145,6 +145,7 @@
#include "editor/plugins/editor_plugin.h"
#include "editor/plugins/editor_preview_plugins.h"
#include "editor/plugins/editor_resource_conversion_plugin.h"
+#include "editor/plugins/game_view_plugin.h"
#include "editor/plugins/gdextension_export_plugin.h"
#include "editor/plugins/material_editor_plugin.h"
#include "editor/plugins/mesh_library_editor_plugin.h"
@@ -357,6 +358,8 @@ void EditorNode::shortcut_input(const Ref<InputEvent> &p_event) {
editor_main_screen->select(EditorMainScreen::EDITOR_3D);
} else if (ED_IS_SHORTCUT("editor/editor_script", p_event)) {
editor_main_screen->select(EditorMainScreen::EDITOR_SCRIPT);
+ } else if (ED_IS_SHORTCUT("editor/editor_game", p_event)) {
+ editor_main_screen->select(EditorMainScreen::EDITOR_GAME);
} else if (ED_IS_SHORTCUT("editor/editor_help", p_event)) {
emit_signal(SNAME("request_help_search"), "");
} else if (ED_IS_SHORTCUT("editor/editor_assetlib", p_event) && AssetLibraryEditorPlugin::is_available()) {
@@ -6577,6 +6580,7 @@ void EditorNode::_feature_profile_changed() {
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_3D, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D));
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_SCRIPT, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_SCRIPT));
+ editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_GAME, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_GAME));
if (AssetLibraryEditorPlugin::is_available()) {
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_ASSETLIB, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_ASSET_LIB));
}
@@ -6587,6 +6591,7 @@ void EditorNode::_feature_profile_changed() {
editor_dock_manager->set_dock_enabled(history_dock, true);
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_3D, true);
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_SCRIPT, true);
+ editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_GAME, true);
if (AssetLibraryEditorPlugin::is_available()) {
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_ASSETLIB, true);
}
@@ -7714,6 +7719,7 @@ EditorNode::EditorNode() {
add_editor_plugin(memnew(CanvasItemEditorPlugin));
add_editor_plugin(memnew(Node3DEditorPlugin));
add_editor_plugin(memnew(ScriptEditorPlugin));
+ add_editor_plugin(memnew(GameViewPlugin));
EditorAudioBuses *audio_bus_editor = EditorAudioBuses::register_editor();
@@ -7896,12 +7902,14 @@ EditorNode::EditorNode() {
ED_SHORTCUT_AND_COMMAND("editor/editor_2d", TTR("Open 2D Editor"), KeyModifierMask::CTRL | Key::F1);
ED_SHORTCUT_AND_COMMAND("editor/editor_3d", TTR("Open 3D Editor"), KeyModifierMask::CTRL | Key::F2);
ED_SHORTCUT_AND_COMMAND("editor/editor_script", TTR("Open Script Editor"), KeyModifierMask::CTRL | Key::F3);
- ED_SHORTCUT_AND_COMMAND("editor/editor_assetlib", TTR("Open Asset Library"), KeyModifierMask::CTRL | Key::F4);
+ ED_SHORTCUT_AND_COMMAND("editor/editor_game", TTR("Open Game View"), KeyModifierMask::CTRL | Key::F4);
+ ED_SHORTCUT_AND_COMMAND("editor/editor_assetlib", TTR("Open Asset Library"), KeyModifierMask::CTRL | Key::F5);
ED_SHORTCUT_OVERRIDE("editor/editor_2d", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_1);
ED_SHORTCUT_OVERRIDE("editor/editor_3d", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_2);
ED_SHORTCUT_OVERRIDE("editor/editor_script", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_3);
- ED_SHORTCUT_OVERRIDE("editor/editor_assetlib", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_4);
+ ED_SHORTCUT_OVERRIDE("editor/editor_game", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_4);
+ ED_SHORTCUT_OVERRIDE("editor/editor_assetlib", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_5);
ED_SHORTCUT_AND_COMMAND("editor/editor_next", TTR("Open the next Editor"));
ED_SHORTCUT_AND_COMMAND("editor/editor_prev", TTR("Open the previous Editor"));
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/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index c147fcca81..9b08d21bdc 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -2472,9 +2472,6 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected
make_dir_dialog->popup_centered();
} break;
- case FILE_INFO: {
- } break;
-
case FILE_REIMPORT: {
ImportDock::get_singleton()->reimport_resources(p_selected);
} break;
diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h
index f48676d24d..72d5ac3a98 100644
--- a/editor/filesystem_dock.h
+++ b/editor/filesystem_dock.h
@@ -116,7 +116,6 @@ private:
FILE_REMOVE,
FILE_DUPLICATE,
FILE_REIMPORT,
- FILE_INFO,
FILE_NEW,
FILE_SHOW_IN_EXPLORER,
FILE_OPEN_EXTERNAL,
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/2DNodes.svg b/editor/icons/2DNodes.svg
new file mode 100644
index 0000000000..90b92a4bc7
--- /dev/null
+++ b/editor/icons/2DNodes.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16"><path fill="none" stroke="#8da5f3" stroke-width="2" d="M 8,13 C 5.2385763,13 3,10.761424 3,8 3,5.2385763 5.2385763,3 8,3"/><path fill="none" stroke="#8eef97" stroke-width="2" d="m 8,13 c 2.761424,0 5,-2.238576 5,-5 C 13,5.2385763 10.761424,3 8,3"/></svg> \ No newline at end of file
diff --git a/editor/icons/Camera.svg b/editor/icons/Camera.svg
new file mode 100644
index 0000000000..8612d458a7
--- /dev/null
+++ b/editor/icons/Camera.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16"><path fill="#e0e0e0" d="M9 2a3 3 0 0 0-3 2.777 3 3 0 1 0-3 5.047V12a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1v-1l3 2V7l-3 2V7.23A3 3 0 0 0 9 2z"/></svg> \ No newline at end of file
diff --git a/editor/icons/Game.svg b/editor/icons/Game.svg
new file mode 100644
index 0000000000..e75e5c5312
--- /dev/null
+++ b/editor/icons/Game.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M 1,15 V 12 C 1,11.5 1.5,11 2,11 H 3 V 10 C 3,9.5 3.5,9 4,9 h 1 c 0.5,0 1,0.5 1,1 v 1 H 8 V 5 h 2 v 6 h 4 c 0.5,0 1,0.5 1,1 v 3 z"/><circle cx="9" cy="4" r="3" fill="#e0e0e0"/></svg> \ No newline at end of file
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/icons/NextFrame.svg b/editor/icons/NextFrame.svg
new file mode 100644
index 0000000000..9609b2538b
--- /dev/null
+++ b/editor/icons/NextFrame.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m 12,3 c -0.552285,0 -1,0.4477153 -1,1 v 8 c 0,0.552285 0.447715,1 1,1 h 1 c 0.552285,0 1,-0.447715 1,-1 V 4 C 14,3.4477153 13.552285,3 13,3 Z M 2.975,3.002 C 2.4332786,3.0155465 2.0009144,3.45811 2,4 v 8 c -3.148e-4,0.838862 0.9701632,1.305289 1.625,0.781 l 5,-4 c 0.4989606,-0.4003069 0.4989606,-1.1596931 0,-1.56 l -5,-4 C 3.4409271,3.0736532 3.2107095,2.9960875 2.975,3.002 Z"/></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.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/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 51992a0b21..e3cf3dbbf2 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -3977,7 +3977,6 @@ void CanvasItemEditor::_update_editor_settings() {
grid_snap_button->set_button_icon(get_editor_theme_icon(SNAME("SnapGrid")));
snap_config_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
skeleton_menu->set_button_icon(get_editor_theme_icon(SNAME("Bone")));
- override_camera_button->set_button_icon(get_editor_theme_icon(SNAME("Camera2D")));
pan_button->set_button_icon(get_editor_theme_icon(SNAME("ToolPan")));
ruler_button->set_button_icon(get_editor_theme_icon(SNAME("Ruler")));
pivot_button->set_button_icon(get_editor_theme_icon(SNAME("EditPivot")));
@@ -4016,8 +4015,6 @@ void CanvasItemEditor::_notification(int p_what) {
case NOTIFICATION_READY: {
_update_lock_and_group_button();
- EditorRunBar::get_singleton()->connect("play_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(true));
- EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(false));
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &CanvasItemEditor::_project_settings_changed));
} break;
@@ -4116,15 +4113,6 @@ void CanvasItemEditor::_notification(int p_what) {
_update_editor_settings();
} break;
- case NOTIFICATION_VISIBILITY_CHANGED: {
- if (!is_visible() && override_camera_button->is_pressed()) {
- EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton();
-
- debugger->set_camera_override(EditorDebuggerNode::OVERRIDE_NONE);
- override_camera_button->set_pressed(false);
- }
- } break;
-
case NOTIFICATION_APPLICATION_FOCUS_OUT:
case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {
if (drag_type != DRAG_NONE) {
@@ -4282,16 +4270,6 @@ void CanvasItemEditor::_button_toggle_grid_snap(bool p_status) {
viewport->queue_redraw();
}
-void CanvasItemEditor::_button_override_camera(bool p_pressed) {
- EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton();
-
- if (p_pressed) {
- debugger->set_camera_override(EditorDebuggerNode::OVERRIDE_2D);
- } else {
- debugger->set_camera_override(EditorDebuggerNode::OVERRIDE_NONE);
- }
-}
-
void CanvasItemEditor::_button_tool_select(int p_index) {
Button *tb[TOOL_MAX] = { select_button, list_select_button, move_button, scale_button, rotate_button, pivot_button, pan_button, ruler_button };
for (int i = 0; i < TOOL_MAX; i++) {
@@ -4398,17 +4376,6 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
te->commit_insert_queue();
}
-void CanvasItemEditor::_update_override_camera_button(bool p_game_running) {
- if (p_game_running) {
- override_camera_button->set_disabled(false);
- override_camera_button->set_tooltip_text(TTR("Project Camera Override\nOverrides the running project's camera with the editor viewport camera."));
- } else {
- override_camera_button->set_disabled(true);
- override_camera_button->set_pressed(false);
- override_camera_button->set_tooltip_text(TTR("Project Camera Override\nNo project instance running. Run the project from the editor to use this feature."));
- }
-}
-
void CanvasItemEditor::_popup_callback(int p_op) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
last_option = MenuOption(p_op);
@@ -5514,16 +5481,6 @@ CanvasItemEditor::CanvasItemEditor() {
main_menu_hbox->add_child(memnew(VSeparator));
- override_camera_button = memnew(Button);
- override_camera_button->set_theme_type_variation("FlatButton");
- main_menu_hbox->add_child(override_camera_button);
- override_camera_button->connect(SceneStringName(toggled), callable_mp(this, &CanvasItemEditor::_button_override_camera));
- override_camera_button->set_toggle_mode(true);
- override_camera_button->set_disabled(true);
- _update_override_camera_button(false);
-
- main_menu_hbox->add_child(memnew(VSeparator));
-
view_menu = memnew(MenuButton);
view_menu->set_flat(false);
view_menu->set_theme_type_variation("FlatMenuButton");
diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h
index bae9efebc9..c5335bf9c1 100644
--- a/editor/plugins/canvas_item_editor_plugin.h
+++ b/editor/plugins/canvas_item_editor_plugin.h
@@ -335,7 +335,6 @@ private:
Button *group_button = nullptr;
Button *ungroup_button = nullptr;
- Button *override_camera_button = nullptr;
MenuButton *view_menu = nullptr;
PopupMenu *grid_menu = nullptr;
PopupMenu *theme_menu = nullptr;
@@ -518,11 +517,8 @@ private:
void _zoom_on_position(real_t p_zoom, Point2 p_position = Point2());
void _button_toggle_smart_snap(bool p_status);
void _button_toggle_grid_snap(bool p_status);
- void _button_override_camera(bool p_pressed);
void _button_tool_select(int p_index);
- void _update_override_camera_button(bool p_game_running);
-
HSplitContainer *left_panel_split = nullptr;
HSplitContainer *right_panel_split = nullptr;
VSplitContainer *bottom_split = nullptr;
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/game_view_plugin.cpp b/editor/plugins/game_view_plugin.cpp
new file mode 100644
index 0000000000..f45af72e90
--- /dev/null
+++ b/editor/plugins/game_view_plugin.cpp
@@ -0,0 +1,478 @@
+/**************************************************************************/
+/* game_view_plugin.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 "game_view_plugin.h"
+
+#include "editor/editor_main_screen.h"
+#include "editor/editor_node.h"
+#include "editor/editor_settings.h"
+#include "editor/themes/editor_scale.h"
+#include "scene/gui/button.h"
+#include "scene/gui/menu_button.h"
+#include "scene/gui/panel.h"
+#include "scene/gui/separator.h"
+
+void GameViewDebugger::_session_started(Ref<EditorDebuggerSession> p_session) {
+ p_session->send_message("scene:runtime_node_select_setup", Array());
+
+ Array type;
+ type.append(node_type);
+ p_session->send_message("scene:runtime_node_select_set_type", type);
+ Array visible;
+ visible.append(selection_visible);
+ p_session->send_message("scene:runtime_node_select_set_visible", visible);
+ Array mode;
+ mode.append(select_mode);
+ p_session->send_message("scene:runtime_node_select_set_mode", mode);
+
+ emit_signal(SNAME("session_started"));
+}
+
+void GameViewDebugger::_session_stopped() {
+ emit_signal(SNAME("session_stopped"));
+}
+
+void GameViewDebugger::set_suspend(bool p_enabled) {
+ Array message;
+ message.append(p_enabled);
+
+ for (Ref<EditorDebuggerSession> &I : sessions) {
+ if (I->is_active()) {
+ I->send_message("scene:suspend_changed", message);
+ }
+ }
+}
+
+void GameViewDebugger::next_frame() {
+ for (Ref<EditorDebuggerSession> &I : sessions) {
+ if (I->is_active()) {
+ I->send_message("scene:next_frame", Array());
+ }
+ }
+}
+
+void GameViewDebugger::set_node_type(int p_type) {
+ node_type = p_type;
+
+ Array message;
+ message.append(p_type);
+
+ for (Ref<EditorDebuggerSession> &I : sessions) {
+ if (I->is_active()) {
+ I->send_message("scene:runtime_node_select_set_type", message);
+ }
+ }
+}
+
+void GameViewDebugger::set_selection_visible(bool p_visible) {
+ selection_visible = p_visible;
+
+ Array message;
+ message.append(p_visible);
+
+ for (Ref<EditorDebuggerSession> &I : sessions) {
+ if (I->is_active()) {
+ I->send_message("scene:runtime_node_select_set_visible", message);
+ }
+ }
+}
+
+void GameViewDebugger::set_select_mode(int p_mode) {
+ select_mode = p_mode;
+
+ Array message;
+ message.append(p_mode);
+
+ for (Ref<EditorDebuggerSession> &I : sessions) {
+ if (I->is_active()) {
+ I->send_message("scene:runtime_node_select_set_mode", message);
+ }
+ }
+}
+
+void GameViewDebugger::set_camera_override(bool p_enabled) {
+ EditorDebuggerNode::get_singleton()->set_camera_override(p_enabled ? camera_override_mode : EditorDebuggerNode::OVERRIDE_NONE);
+}
+
+void GameViewDebugger::set_camera_manipulate_mode(EditorDebuggerNode::CameraOverride p_mode) {
+ camera_override_mode = p_mode;
+
+ if (EditorDebuggerNode::get_singleton()->get_camera_override() != EditorDebuggerNode::OVERRIDE_NONE) {
+ set_camera_override(true);
+ }
+}
+
+void GameViewDebugger::reset_camera_2d_position() {
+ for (Ref<EditorDebuggerSession> &I : sessions) {
+ if (I->is_active()) {
+ I->send_message("scene:runtime_node_select_reset_camera_2d", Array());
+ }
+ }
+}
+
+void GameViewDebugger::reset_camera_3d_position() {
+ for (Ref<EditorDebuggerSession> &I : sessions) {
+ if (I->is_active()) {
+ I->send_message("scene:runtime_node_select_reset_camera_3d", Array());
+ }
+ }
+}
+
+void GameViewDebugger::setup_session(int p_session_id) {
+ Ref<EditorDebuggerSession> session = get_session(p_session_id);
+ ERR_FAIL_COND(session.is_null());
+
+ sessions.append(session);
+
+ session->connect("started", callable_mp(this, &GameViewDebugger::_session_started).bind(session));
+ session->connect("stopped", callable_mp(this, &GameViewDebugger::_session_stopped));
+}
+
+void GameViewDebugger::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("session_started"));
+ ADD_SIGNAL(MethodInfo("session_stopped"));
+}
+
+///////
+
+void GameView::_sessions_changed() {
+ // The debugger session's `session_started/stopped` signal can be unreliable, so count it manually.
+ active_sessions = 0;
+ Array sessions = debugger->get_sessions();
+ for (int i = 0; i < sessions.size(); i++) {
+ if (Object::cast_to<EditorDebuggerSession>(sessions[i])->is_active()) {
+ active_sessions++;
+ }
+ }
+
+ _update_debugger_buttons();
+}
+
+void GameView::_update_debugger_buttons() {
+ bool empty = active_sessions == 0;
+
+ suspend_button->set_disabled(empty);
+ camera_override_button->set_disabled(empty);
+
+ PopupMenu *menu = camera_override_menu->get_popup();
+
+ bool disable_camera_reset = empty || !camera_override_button->is_pressed() || !menu->is_item_checked(menu->get_item_index(CAMERA_MODE_INGAME));
+ menu->set_item_disabled(CAMERA_RESET_2D, disable_camera_reset);
+ menu->set_item_disabled(CAMERA_RESET_3D, disable_camera_reset);
+
+ if (empty) {
+ suspend_button->set_pressed(false);
+ camera_override_button->set_pressed(false);
+ }
+ next_frame_button->set_disabled(!suspend_button->is_pressed());
+}
+
+void GameView::_suspend_button_toggled(bool p_pressed) {
+ _update_debugger_buttons();
+
+ debugger->set_suspend(p_pressed);
+}
+
+void GameView::_node_type_pressed(int p_option) {
+ RuntimeNodeSelect::NodeType type = (RuntimeNodeSelect::NodeType)p_option;
+ for (int i = 0; i < RuntimeNodeSelect::NODE_TYPE_MAX; i++) {
+ node_type_button[i]->set_pressed_no_signal(i == type);
+ }
+
+ _update_debugger_buttons();
+
+ debugger->set_node_type(type);
+}
+
+void GameView::_select_mode_pressed(int p_option) {
+ RuntimeNodeSelect::SelectMode mode = (RuntimeNodeSelect::SelectMode)p_option;
+ for (int i = 0; i < RuntimeNodeSelect::SELECT_MODE_MAX; i++) {
+ select_mode_button[i]->set_pressed_no_signal(i == mode);
+ }
+
+ debugger->set_select_mode(mode);
+}
+
+void GameView::_hide_selection_toggled(bool p_pressed) {
+ hide_selection->set_button_icon(get_editor_theme_icon(p_pressed ? SNAME("GuiVisibilityHidden") : SNAME("GuiVisibilityVisible")));
+
+ debugger->set_selection_visible(!p_pressed);
+}
+
+void GameView::_camera_override_button_toggled(bool p_pressed) {
+ _update_debugger_buttons();
+
+ debugger->set_camera_override(p_pressed);
+}
+
+void GameView::_camera_override_menu_id_pressed(int p_id) {
+ PopupMenu *menu = camera_override_menu->get_popup();
+ if (p_id != CAMERA_RESET_2D && p_id != CAMERA_RESET_3D) {
+ for (int i = 0; i < menu->get_item_count(); i++) {
+ menu->set_item_checked(i, false);
+ }
+ }
+
+ switch (p_id) {
+ case CAMERA_RESET_2D: {
+ debugger->reset_camera_2d_position();
+ } break;
+ case CAMERA_RESET_3D: {
+ debugger->reset_camera_3d_position();
+ } break;
+ case CAMERA_MODE_INGAME: {
+ debugger->set_camera_manipulate_mode(EditorDebuggerNode::OVERRIDE_INGAME);
+ menu->set_item_checked(menu->get_item_index(p_id), true);
+
+ _update_debugger_buttons();
+ } break;
+ case CAMERA_MODE_EDITORS: {
+ debugger->set_camera_manipulate_mode(EditorDebuggerNode::OVERRIDE_EDITORS);
+ menu->set_item_checked(menu->get_item_index(p_id), true);
+
+ _update_debugger_buttons();
+ } break;
+ }
+}
+
+void GameView::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_THEME_CHANGED: {
+ suspend_button->set_button_icon(get_editor_theme_icon(SNAME("Pause")));
+ next_frame_button->set_button_icon(get_editor_theme_icon(SNAME("NextFrame")));
+
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_button_icon(get_editor_theme_icon(SNAME("InputEventJoypadMotion")));
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]->set_button_icon(get_editor_theme_icon(SNAME("2DNodes")));
+#ifndef _3D_DISABLED
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->set_button_icon(get_editor_theme_icon(SNAME("Node3D")));
+#endif // _3D_DISABLED
+
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_button_icon(get_editor_theme_icon(SNAME("ToolSelect")));
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_button_icon(get_editor_theme_icon(SNAME("ListSelect")));
+
+ hide_selection->set_button_icon(get_editor_theme_icon(hide_selection->is_pressed() ? SNAME("GuiVisibilityHidden") : SNAME("GuiVisibilityVisible")));
+
+ camera_override_button->set_button_icon(get_editor_theme_icon(SNAME("Camera")));
+ camera_override_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
+ } break;
+ }
+}
+
+void GameView::set_state(const Dictionary &p_state) {
+ if (p_state.has("hide_selection")) {
+ hide_selection->set_pressed(p_state["hide_selection"]);
+ _hide_selection_toggled(hide_selection->is_pressed());
+ }
+ if (p_state.has("select_mode")) {
+ _select_mode_pressed(p_state["select_mode"]);
+ }
+ if (p_state.has("camera_override_mode")) {
+ _camera_override_menu_id_pressed(p_state["camera_override_mode"]);
+ }
+}
+
+Dictionary GameView::get_state() const {
+ Dictionary d;
+ d["hide_selection"] = hide_selection->is_pressed();
+
+ for (int i = 0; i < RuntimeNodeSelect::SELECT_MODE_MAX; i++) {
+ if (select_mode_button[i]->is_pressed()) {
+ d["select_mode"] = i;
+ break;
+ }
+ }
+
+ PopupMenu *menu = camera_override_menu->get_popup();
+ for (int i = CAMERA_MODE_INGAME; i < CAMERA_MODE_EDITORS + 1; i++) {
+ if (menu->is_item_checked(menu->get_item_index(i))) {
+ d["camera_override_mode"] = i;
+ break;
+ }
+ }
+
+ return d;
+}
+
+GameView::GameView(Ref<GameViewDebugger> p_debugger) {
+ debugger = p_debugger;
+
+ // Add some margin to the sides for better aesthetics.
+ // This prevents the first button's hover/pressed effect from "touching" the panel's border,
+ // which looks ugly.
+ MarginContainer *toolbar_margin = memnew(MarginContainer);
+ toolbar_margin->add_theme_constant_override("margin_left", 4 * EDSCALE);
+ toolbar_margin->add_theme_constant_override("margin_right", 4 * EDSCALE);
+ add_child(toolbar_margin);
+
+ HBoxContainer *main_menu_hbox = memnew(HBoxContainer);
+ toolbar_margin->add_child(main_menu_hbox);
+
+ suspend_button = memnew(Button);
+ main_menu_hbox->add_child(suspend_button);
+ suspend_button->set_toggle_mode(true);
+ suspend_button->set_theme_type_variation("FlatButton");
+ suspend_button->connect(SceneStringName(toggled), callable_mp(this, &GameView::_suspend_button_toggled));
+ suspend_button->set_tooltip_text(TTR("Suspend"));
+
+ next_frame_button = memnew(Button);
+ main_menu_hbox->add_child(next_frame_button);
+ next_frame_button->set_theme_type_variation("FlatButton");
+ next_frame_button->connect(SceneStringName(pressed), callable_mp(*debugger, &GameViewDebugger::next_frame));
+ next_frame_button->set_tooltip_text(TTR("Next Frame"));
+
+ main_menu_hbox->add_child(memnew(VSeparator));
+
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE] = memnew(Button);
+ main_menu_hbox->add_child(node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]);
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_text(TTR("Input"));
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_toggle_mode(true);
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_pressed(true);
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_theme_type_variation("FlatButton");
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->connect(SceneStringName(pressed), callable_mp(this, &GameView::_node_type_pressed).bind(RuntimeNodeSelect::NODE_TYPE_NONE));
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_tooltip_text(TTR("Allow game input."));
+
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_2D] = memnew(Button);
+ main_menu_hbox->add_child(node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]);
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]->set_text(TTR("2D"));
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]->set_toggle_mode(true);
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]->set_theme_type_variation("FlatButton");
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]->connect(SceneStringName(pressed), callable_mp(this, &GameView::_node_type_pressed).bind(RuntimeNodeSelect::NODE_TYPE_2D));
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]->set_tooltip_text(TTR("Disable game input and allow to select Node2Ds, Controls, and manipulate the 2D camera."));
+
+#ifndef _3D_DISABLED
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_3D] = memnew(Button);
+ main_menu_hbox->add_child(node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]);
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->set_text(TTR("3D"));
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->set_toggle_mode(true);
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->set_theme_type_variation("FlatButton");
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->connect(SceneStringName(pressed), callable_mp(this, &GameView::_node_type_pressed).bind(RuntimeNodeSelect::NODE_TYPE_3D));
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->set_tooltip_text(TTR("Disable game input and allow to select Node3Ds and manipulate the 3D camera."));
+#endif // _3D_DISABLED
+
+ main_menu_hbox->add_child(memnew(VSeparator));
+
+ hide_selection = memnew(Button);
+ main_menu_hbox->add_child(hide_selection);
+ hide_selection->set_toggle_mode(true);
+ hide_selection->set_theme_type_variation("FlatButton");
+ hide_selection->connect(SceneStringName(toggled), callable_mp(this, &GameView::_hide_selection_toggled));
+ hide_selection->set_tooltip_text(TTR("Toggle Selection Visibility"));
+
+ main_menu_hbox->add_child(memnew(VSeparator));
+
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE] = memnew(Button);
+ main_menu_hbox->add_child(select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]);
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_toggle_mode(true);
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_pressed(true);
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_theme_type_variation("FlatButton");
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->connect(SceneStringName(pressed), callable_mp(this, &GameView::_select_mode_pressed).bind(RuntimeNodeSelect::SELECT_MODE_SINGLE));
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_select", TTR("Select Mode"), Key::Q));
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_shortcut_context(this);
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_tooltip_text(keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Alt+RMB: Show list of all nodes at position clicked."));
+
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST] = memnew(Button);
+ main_menu_hbox->add_child(select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]);
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_toggle_mode(true);
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_theme_type_variation("FlatButton");
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->connect(SceneStringName(pressed), callable_mp(this, &GameView::_select_mode_pressed).bind(RuntimeNodeSelect::SELECT_MODE_LIST));
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_tooltip_text(TTR("Show list of selectable nodes at position clicked."));
+
+ main_menu_hbox->add_child(memnew(VSeparator));
+
+ camera_override_button = memnew(Button);
+ main_menu_hbox->add_child(camera_override_button);
+ camera_override_button->set_toggle_mode(true);
+ camera_override_button->set_theme_type_variation("FlatButton");
+ camera_override_button->connect(SceneStringName(toggled), callable_mp(this, &GameView::_camera_override_button_toggled));
+ camera_override_button->set_tooltip_text(TTR("Override the in-game camera."));
+
+ camera_override_menu = memnew(MenuButton);
+ main_menu_hbox->add_child(camera_override_menu);
+ camera_override_menu->set_flat(false);
+ camera_override_menu->set_theme_type_variation("FlatMenuButton");
+ camera_override_menu->set_h_size_flags(SIZE_SHRINK_END);
+ camera_override_menu->set_tooltip_text(TTR("Camera Override Options"));
+
+ PopupMenu *menu = camera_override_menu->get_popup();
+ menu->connect(SceneStringName(id_pressed), callable_mp(this, &GameView::_camera_override_menu_id_pressed));
+ menu->add_item(TTR("Reset 2D Camera"), CAMERA_RESET_2D);
+ menu->add_item(TTR("Reset 3D Camera"), CAMERA_RESET_3D);
+ menu->add_separator();
+ menu->add_radio_check_item(TTR("Manipulate In-Game"), CAMERA_MODE_INGAME);
+ menu->set_item_checked(menu->get_item_index(CAMERA_MODE_INGAME), true);
+ menu->add_radio_check_item(TTR("Manipulate From Editors"), CAMERA_MODE_EDITORS);
+
+ _update_debugger_buttons();
+
+ panel = memnew(Panel);
+ add_child(panel);
+ panel->set_theme_type_variation("GamePanel");
+ panel->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ p_debugger->connect("session_started", callable_mp(this, &GameView::_sessions_changed));
+ p_debugger->connect("session_stopped", callable_mp(this, &GameView::_sessions_changed));
+}
+
+///////
+
+void GameViewPlugin::make_visible(bool p_visible) {
+ game_view->set_visible(p_visible);
+}
+
+void GameViewPlugin::set_state(const Dictionary &p_state) {
+ game_view->set_state(p_state);
+}
+
+Dictionary GameViewPlugin::get_state() const {
+ return game_view->get_state();
+}
+
+void GameViewPlugin::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ add_debugger_plugin(debugger);
+ } break;
+ case NOTIFICATION_EXIT_TREE: {
+ remove_debugger_plugin(debugger);
+ } break;
+ }
+}
+
+GameViewPlugin::GameViewPlugin() {
+ debugger.instantiate();
+
+ game_view = memnew(GameView(debugger));
+ game_view->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(game_view);
+ game_view->hide();
+}
+
+GameViewPlugin::~GameViewPlugin() {
+}
diff --git a/editor/plugins/game_view_plugin.h b/editor/plugins/game_view_plugin.h
new file mode 100644
index 0000000000..f8701c3e76
--- /dev/null
+++ b/editor/plugins/game_view_plugin.h
@@ -0,0 +1,152 @@
+/**************************************************************************/
+/* game_view_plugin.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 GAME_VIEW_PLUGIN_H
+#define GAME_VIEW_PLUGIN_H
+
+#include "editor/debugger/editor_debugger_node.h"
+#include "editor/plugins/editor_debugger_plugin.h"
+#include "editor/plugins/editor_plugin.h"
+#include "scene/debugger/scene_debugger.h"
+#include "scene/gui/box_container.h"
+
+class GameViewDebugger : public EditorDebuggerPlugin {
+ GDCLASS(GameViewDebugger, EditorDebuggerPlugin);
+
+private:
+ Vector<Ref<EditorDebuggerSession>> sessions;
+
+ int node_type = RuntimeNodeSelect::NODE_TYPE_NONE;
+ bool selection_visible = true;
+ int select_mode = RuntimeNodeSelect::SELECT_MODE_SINGLE;
+ EditorDebuggerNode::CameraOverride camera_override_mode = EditorDebuggerNode::OVERRIDE_INGAME;
+
+ void _session_started(Ref<EditorDebuggerSession> p_session);
+ void _session_stopped();
+
+protected:
+ static void _bind_methods();
+
+public:
+ void set_suspend(bool p_enabled);
+ void next_frame();
+
+ void set_node_type(int p_type);
+ void set_select_mode(int p_mode);
+
+ void set_selection_visible(bool p_visible);
+
+ void set_camera_override(bool p_enabled);
+ void set_camera_manipulate_mode(EditorDebuggerNode::CameraOverride p_mode);
+
+ void reset_camera_2d_position();
+ void reset_camera_3d_position();
+
+ virtual void setup_session(int p_session_id) override;
+
+ GameViewDebugger() {}
+};
+
+class GameView : public VBoxContainer {
+ GDCLASS(GameView, VBoxContainer);
+
+ enum {
+ CAMERA_RESET_2D,
+ CAMERA_RESET_3D,
+ CAMERA_MODE_INGAME,
+ CAMERA_MODE_EDITORS,
+ };
+
+ Ref<GameViewDebugger> debugger;
+
+ int active_sessions = 0;
+
+ Button *suspend_button = nullptr;
+ Button *next_frame_button = nullptr;
+
+ Button *node_type_button[RuntimeNodeSelect::NODE_TYPE_MAX];
+ Button *select_mode_button[RuntimeNodeSelect::SELECT_MODE_MAX];
+
+ Button *hide_selection = nullptr;
+
+ Button *camera_override_button = nullptr;
+ MenuButton *camera_override_menu = nullptr;
+
+ Panel *panel = nullptr;
+
+ void _sessions_changed();
+
+ void _update_debugger_buttons();
+
+ void _suspend_button_toggled(bool p_pressed);
+
+ void _node_type_pressed(int p_option);
+ void _select_mode_pressed(int p_option);
+
+ void _hide_selection_toggled(bool p_pressed);
+
+ void _camera_override_button_toggled(bool p_pressed);
+ void _camera_override_menu_id_pressed(int p_id);
+
+protected:
+ void _notification(int p_what);
+
+public:
+ void set_state(const Dictionary &p_state);
+ Dictionary get_state() const;
+
+ GameView(Ref<GameViewDebugger> p_debugger);
+};
+
+class GameViewPlugin : public EditorPlugin {
+ GDCLASS(GameViewPlugin, EditorPlugin);
+
+ GameView *game_view = nullptr;
+
+ Ref<GameViewDebugger> debugger;
+
+protected:
+ void _notification(int p_what);
+
+public:
+ virtual String get_name() const override { return "Game"; }
+ bool has_main_screen() const override { return true; }
+ virtual void edit(Object *p_object) override {}
+ virtual bool handles(Object *p_object) const override { return false; }
+ virtual void make_visible(bool p_visible) override;
+
+ virtual void set_state(const Dictionary &p_state) override;
+ virtual Dictionary get_state() const override;
+
+ GameViewPlugin();
+ ~GameViewPlugin();
+};
+
+#endif // GAME_VIEW_PLUGIN_H
diff --git a/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp
index 4a784be064..573c686d57 100644
--- a/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp
+++ b/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp
@@ -538,20 +538,19 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
if (Object::cast_to<ConvexPolygonShape3D>(*s)) {
Vector<Vector3> points = Object::cast_to<ConvexPolygonShape3D>(*s)->get_points();
- if (points.size() > 3) {
+ if (points.size() > 1) { // Need at least 2 points for a line.
Vector<Vector3> varr = Variant(points);
Geometry3D::MeshData md;
Error err = ConvexHullComputer::convex_hull(varr, md);
if (err == OK) {
- Vector<Vector3> points2;
- points2.resize(md.edges.size() * 2);
+ Vector<Vector3> lines;
+ lines.resize(md.edges.size() * 2);
for (uint32_t i = 0; i < md.edges.size(); i++) {
- points2.write[i * 2 + 0] = md.vertices[md.edges[i].vertex_a];
- points2.write[i * 2 + 1] = md.vertices[md.edges[i].vertex_b];
+ lines.write[i * 2 + 0] = md.vertices[md.edges[i].vertex_a];
+ lines.write[i * 2 + 1] = md.vertices[md.edges[i].vertex_b];
}
-
- p_gizmo->add_lines(points2, material);
- p_gizmo->add_collision_segments(points2);
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_collision_segments(lines);
}
}
}
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/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 6fc73f69dd..5afe01025d 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -1692,7 +1692,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> b = p_event;
if (b.is_valid()) {
- emit_signal(SNAME("clicked"), this);
+ emit_signal(SNAME("clicked"));
ViewportNavMouseButton orbit_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/orbit_mouse_button").operator int();
ViewportNavMouseButton pan_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/pan_mouse_button").operator int();
@@ -4210,7 +4210,7 @@ Dictionary Node3DEditorViewport::get_state() const {
void Node3DEditorViewport::_bind_methods() {
ADD_SIGNAL(MethodInfo("toggle_maximize_view", PropertyInfo(Variant::OBJECT, "viewport")));
- ADD_SIGNAL(MethodInfo("clicked", PropertyInfo(Variant::OBJECT, "viewport")));
+ ADD_SIGNAL(MethodInfo("clicked"));
}
void Node3DEditorViewport::reset() {
@@ -6572,18 +6572,6 @@ void Node3DEditor::_menu_item_toggled(bool pressed, int p_option) {
tool_option_button[TOOL_OPT_USE_SNAP]->set_pressed(pressed);
snap_enabled = pressed;
} break;
-
- case MENU_TOOL_OVERRIDE_CAMERA: {
- EditorDebuggerNode *const debugger = EditorDebuggerNode::get_singleton();
-
- using Override = EditorDebuggerNode::CameraOverride;
- if (pressed) {
- debugger->set_camera_override((Override)(Override::OVERRIDE_3D_1 + camera_override_viewport_id));
- } else {
- debugger->set_camera_override(Override::OVERRIDE_NONE);
- }
-
- } break;
}
}
@@ -6610,36 +6598,6 @@ void Node3DEditor::_menu_gizmo_toggled(int p_option) {
update_all_gizmos();
}
-void Node3DEditor::_update_camera_override_button(bool p_game_running) {
- Button *const button = tool_option_button[TOOL_OPT_OVERRIDE_CAMERA];
-
- if (p_game_running) {
- button->set_disabled(false);
- button->set_tooltip_text(TTR("Project Camera Override\nOverrides the running project's camera with the editor viewport camera."));
- } else {
- button->set_disabled(true);
- button->set_pressed(false);
- button->set_tooltip_text(TTR("Project Camera Override\nNo project instance running. Run the project from the editor to use this feature."));
- }
-}
-
-void Node3DEditor::_update_camera_override_viewport(Object *p_viewport) {
- Node3DEditorViewport *current_viewport = Object::cast_to<Node3DEditorViewport>(p_viewport);
-
- if (!current_viewport) {
- return;
- }
-
- EditorDebuggerNode *const debugger = EditorDebuggerNode::get_singleton();
-
- camera_override_viewport_id = current_viewport->index;
- if (debugger->get_camera_override() >= EditorDebuggerNode::OVERRIDE_3D_1) {
- using Override = EditorDebuggerNode::CameraOverride;
-
- debugger->set_camera_override((Override)(Override::OVERRIDE_3D_1 + camera_override_viewport_id));
- }
-}
-
void Node3DEditor::_menu_item_pressed(int p_option) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
switch (p_option) {
@@ -6670,6 +6628,9 @@ void Node3DEditor::_menu_item_pressed(int p_option) {
} break;
case MENU_VIEW_USE_1_VIEWPORT: {
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_1_VIEWPORT);
+ if (last_used_viewport > 0) {
+ last_used_viewport = 0;
+ }
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), true);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
@@ -6681,6 +6642,9 @@ void Node3DEditor::_menu_item_pressed(int p_option) {
} break;
case MENU_VIEW_USE_2_VIEWPORTS: {
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_2_VIEWPORTS);
+ if (last_used_viewport > 1) {
+ last_used_viewport = 0;
+ }
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), true);
@@ -6692,6 +6656,9 @@ void Node3DEditor::_menu_item_pressed(int p_option) {
} break;
case MENU_VIEW_USE_2_VIEWPORTS_ALT: {
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_2_VIEWPORTS_ALT);
+ if (last_used_viewport > 1) {
+ last_used_viewport = 0;
+ }
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
@@ -6703,6 +6670,9 @@ void Node3DEditor::_menu_item_pressed(int p_option) {
} break;
case MENU_VIEW_USE_3_VIEWPORTS: {
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_3_VIEWPORTS);
+ if (last_used_viewport > 2) {
+ last_used_viewport = 0;
+ }
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
@@ -6714,6 +6684,9 @@ void Node3DEditor::_menu_item_pressed(int p_option) {
} break;
case MENU_VIEW_USE_3_VIEWPORTS_ALT: {
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_3_VIEWPORTS_ALT);
+ if (last_used_viewport > 2) {
+ last_used_viewport = 0;
+ }
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
@@ -8033,7 +8006,6 @@ void Node3DEditor::_update_theme() {
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_button_icon(get_editor_theme_icon(SNAME("Object")));
tool_option_button[TOOL_OPT_USE_SNAP]->set_button_icon(get_editor_theme_icon(SNAME("Snap")));
- tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_button_icon(get_editor_theme_icon(SNAME("Camera3D")));
view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), get_editor_theme_icon(SNAME("Panels1")));
view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), get_editor_theme_icon(SNAME("Panels2")));
@@ -8068,9 +8040,6 @@ void Node3DEditor::_notification(int p_what) {
SceneTreeDock::get_singleton()->get_tree_editor()->connect("node_changed", callable_mp(this, &Node3DEditor::_refresh_menu_icons));
editor_selection->connect("selection_changed", callable_mp(this, &Node3DEditor::_selection_changed));
- EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button).bind(false));
- EditorRunBar::get_singleton()->connect("play_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button).bind(true));
-
_update_preview_environment();
sun_state->set_custom_minimum_size(sun_vb->get_combined_minimum_size());
@@ -8106,15 +8075,6 @@ void Node3DEditor::_notification(int p_what) {
}
} break;
- case NOTIFICATION_VISIBILITY_CHANGED: {
- if (!is_visible() && tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->is_pressed()) {
- EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton();
-
- debugger->set_camera_override(EditorDebuggerNode::OVERRIDE_NONE);
- tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_pressed(false);
- }
- } break;
-
case NOTIFICATION_PHYSICS_PROCESS: {
if (do_snap_selected_nodes_to_floor) {
_snap_selected_nodes_to_floor();
@@ -8216,6 +8176,10 @@ VSplitContainer *Node3DEditor::get_shader_split() {
return shader_split;
}
+Node3DEditorViewport *Node3DEditor::get_last_used_viewport() {
+ return viewports[last_used_viewport];
+}
+
void Node3DEditor::add_control_to_left_panel(Control *p_control) {
left_panel_split->add_child(p_control);
left_panel_split->move_child(p_control, 0);
@@ -8393,6 +8357,10 @@ void Node3DEditor::_toggle_maximize_view(Object *p_viewport) {
}
}
+void Node3DEditor::_viewport_clicked(int p_viewport_idx) {
+ last_used_viewport = p_viewport_idx;
+}
+
void Node3DEditor::_node_added(Node *p_node) {
if (EditorNode::get_singleton()->get_scene_root()->is_ancestor_of(p_node)) {
if (Object::cast_to<WorldEnvironment>(p_node)) {
@@ -8512,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);
}
@@ -8684,8 +8652,6 @@ Node3DEditor::Node3DEditor() {
snap_key_enabled = false;
tool_mode = TOOL_MODE_SELECT;
- camera_override_viewport_id = 0;
-
// Add some margin to the sides for better aesthetics.
// This prevents the first button's hover/pressed effect from "touching" the panel's border,
// which looks ugly.
@@ -8803,16 +8769,6 @@ Node3DEditor::Node3DEditor() {
tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut_context(this);
main_menu_hbox->add_child(memnew(VSeparator));
-
- tool_option_button[TOOL_OPT_OVERRIDE_CAMERA] = memnew(Button);
- main_menu_hbox->add_child(tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]);
- tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_toggle_mode(true);
- tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_theme_type_variation("FlatButton");
- tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_disabled(true);
- tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_OVERRIDE_CAMERA));
- _update_camera_override_button(false);
-
- main_menu_hbox->add_child(memnew(VSeparator));
sun_button = memnew(Button);
sun_button->set_tooltip_text(TTR("Toggle preview sunlight.\nIf a DirectionalLight3D node is added to the scene, preview sunlight is disabled."));
sun_button->set_toggle_mode(true);
@@ -8955,7 +8911,7 @@ Node3DEditor::Node3DEditor() {
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
viewports[i] = memnew(Node3DEditorViewport(this, i));
viewports[i]->connect("toggle_maximize_view", callable_mp(this, &Node3DEditor::_toggle_maximize_view));
- viewports[i]->connect("clicked", callable_mp(this, &Node3DEditor::_update_camera_override_viewport));
+ viewports[i]->connect("clicked", callable_mp(this, &Node3DEditor::_viewport_clicked).bind(i));
viewports[i]->assign_pending_data_pointers(preview_node, &preview_bounds, accept);
viewport_base->add_child(viewports[i]);
}
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index 1b03362606..d35fcb7653 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -622,7 +622,6 @@ public:
enum ToolOptions {
TOOL_OPT_LOCAL_COORDS,
TOOL_OPT_USE_SNAP,
- TOOL_OPT_OVERRIDE_CAMERA,
TOOL_OPT_MAX
};
@@ -632,6 +631,8 @@ private:
Node3DEditorViewportContainer *viewport_base = nullptr;
Node3DEditorViewport *viewports[VIEWPORTS_COUNT];
+ int last_used_viewport = 0;
+
VSplitContainer *shader_split = nullptr;
HSplitContainer *left_panel_split = nullptr;
HSplitContainer *right_panel_split = nullptr;
@@ -704,7 +705,6 @@ private:
MENU_TOOL_LIST_SELECT,
MENU_TOOL_LOCAL_COORDS,
MENU_TOOL_USE_SNAP,
- MENU_TOOL_OVERRIDE_CAMERA,
MENU_TRANSFORM_CONFIGURE_SNAP,
MENU_TRANSFORM_DIALOG,
MENU_VIEW_USE_1_VIEWPORT,
@@ -759,8 +759,6 @@ private:
void _menu_item_pressed(int p_option);
void _menu_item_toggled(bool pressed, int p_option);
void _menu_gizmo_toggled(int p_option);
- void _update_camera_override_button(bool p_game_running);
- void _update_camera_override_viewport(Object *p_viewport);
// Used for secondary menu items which are displayed depending on the currently selected node
// (such as MeshInstance's "Mesh" menu).
PanelContainer *context_toolbar_panel = nullptr;
@@ -771,8 +769,6 @@ private:
void _generate_selection_boxes();
- int camera_override_viewport_id;
-
void _init_indicators();
void _update_gizmos_menu();
void _update_gizmos_menu_theme();
@@ -781,6 +777,7 @@ private:
void _finish_grid();
void _toggle_maximize_view(Object *p_viewport);
+ void _viewport_clicked(int p_viewport_idx);
Node *custom_camera = nullptr;
@@ -967,6 +964,7 @@ public:
ERR_FAIL_INDEX_V(p_idx, static_cast<int>(VIEWPORTS_COUNT), nullptr);
return viewports[p_idx];
}
+ Node3DEditorViewport *get_last_used_viewport();
void add_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin);
void remove_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin);
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/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/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp
index 4db43f0703..32079f3753 100644
--- a/editor/themes/editor_theme_manager.cpp
+++ b/editor/themes/editor_theme_manager.cpp
@@ -1857,6 +1857,12 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme
p_theme->set_stylebox("ScriptEditorPanelFloating", EditorStringName(EditorStyles), make_empty_stylebox(0, 0, 0, 0));
p_theme->set_stylebox("ScriptEditor", EditorStringName(EditorStyles), make_empty_stylebox(0, 0, 0, 0));
+ // Game view.
+ p_theme->set_type_variation("GamePanel", "Panel");
+ Ref<StyleBoxFlat> game_panel = p_theme->get_stylebox(SNAME("panel"), SNAME("Panel"))->duplicate();
+ game_panel->set_corner_radius_all(0);
+ p_theme->set_stylebox(SceneStringName(panel), "GamePanel", game_panel);
+
// Main menu.
Ref<StyleBoxFlat> menu_transparent_style = p_config.button_style->duplicate();
menu_transparent_style->set_bg_color(Color(1, 1, 1, 0));
diff --git a/methods.py b/methods.py
index 65b88a5c65..afaf5b80e5 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
@@ -563,40 +524,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/misc/extension_api_validation/4.3-stable.expected b/misc/extension_api_validation/4.3-stable.expected
index 9e169d474d..3770664115 100644
--- a/misc/extension_api_validation/4.3-stable.expected
+++ b/misc/extension_api_validation/4.3-stable.expected
@@ -7,7 +7,6 @@ should instead be used to justify these changes and describe how users should wo
Add new entries at the end of the file.
## Changes between 4.3-stable and 4.4-stable
-
GH-95374
--------
Validate extension JSON: Error: Field 'classes/ShapeCast2D/properties/collision_result': getter changed value in new API, from "_get_collision_result" to &"get_collision_result".
@@ -102,3 +101,10 @@ GH-97020
Validate extension JSON: Error: Field 'classes/AnimationNode/methods/_process': is_const changed value in new API, from true to false.
`_process` virtual method fixed to be non const instead.
+
+
+GH-97257
+--------
+Validate extension JSON: Error: Field 'classes/EditorFeatureProfile/enums/Feature/values/FEATURE_MAX': value changed value in new API, from 8.0 to 9.
+
+New entry to the `EditorFeatureProfile.Feature` enum added. Those need to go before `FEATURE_MAX`, which will always cause a compatibility break.
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/features/class_inference_is_weak.out b/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out
index 94e2ec2af8..fb616f1e94 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out
+++ b/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out
@@ -1,2 +1,2 @@
GDTEST_OK
-0
+0.0
diff --git a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
index 5318d11f33..e91c7386fe 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
@@ -7,17 +7,17 @@ const const_packed_ints: PackedFloat64Array = [52]
func test():
Utils.check(typeof(const_float_int) == TYPE_FLOAT)
- Utils.check(str(const_float_int) == '19')
+ Utils.check(str(const_float_int) == '19.0')
Utils.check(typeof(const_float_plus) == TYPE_FLOAT)
- Utils.check(str(const_float_plus) == '34')
+ Utils.check(str(const_float_plus) == '34.0')
Utils.check(typeof(const_float_cast) == TYPE_FLOAT)
- Utils.check(str(const_float_cast) == '76')
+ Utils.check(str(const_float_cast) == '76.0')
Utils.check(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY)
Utils.check(str(const_packed_empty) == '[]')
Utils.check(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY)
- Utils.check(str(const_packed_ints) == '[52]')
+ Utils.check(str(const_packed_ints) == '[52.0]')
Utils.check(typeof(const_packed_ints[0]) == TYPE_FLOAT)
- Utils.check(str(const_packed_ints[0]) == '52')
+ Utils.check(str(const_packed_ints[0]) == '52.0')
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out b/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out
index 15666c46ad..abf11548cb 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out
+++ b/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out
@@ -1,2 +1,2 @@
GDTEST_OK
-4
+4.0
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
index fe0274c27b..eb53d0a700 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
@@ -54,39 +54,39 @@ func test():
untyped_basic.push_back(430.0)
inferred_basic.push_back(263.0)
typed_basic.push_back(518.0)
- Utils.check(str(empty_floats) == '[705, 430, 263, 518]')
- Utils.check(str(untyped_basic) == '[705, 430, 263, 518]')
- Utils.check(str(inferred_basic) == '[705, 430, 263, 518]')
- Utils.check(str(typed_basic) == '[705, 430, 263, 518]')
+ Utils.check(str(empty_floats) == '[705.0, 430.0, 263.0, 518.0]')
+ Utils.check(str(untyped_basic) == '[705.0, 430.0, 263.0, 518.0]')
+ Utils.check(str(inferred_basic) == '[705.0, 430.0, 263.0, 518.0]')
+ Utils.check(str(typed_basic) == '[705.0, 430.0, 263.0, 518.0]')
const constant_float := 950.0
const constant_int := 170
var typed_float := 954.0
var filled_floats: Array[float] = [constant_float, constant_int, typed_float, empty_floats[1] + empty_floats[2]]
- Utils.check(str(filled_floats) == '[950, 170, 954, 693]')
+ Utils.check(str(filled_floats) == '[950.0, 170.0, 954.0, 693.0]')
Utils.check(filled_floats.get_typed_builtin() == TYPE_FLOAT)
var casted_floats := [empty_floats[2] * 2] as Array[float]
- Utils.check(str(casted_floats) == '[526]')
+ Utils.check(str(casted_floats) == '[526.0]')
Utils.check(casted_floats.get_typed_builtin() == TYPE_FLOAT)
var returned_floats = (func () -> Array[float]: return [554]).call()
- Utils.check(str(returned_floats) == '[554]')
+ Utils.check(str(returned_floats) == '[554.0]')
Utils.check(returned_floats.get_typed_builtin() == TYPE_FLOAT)
var passed_floats = floats_identity([663.0 if randf() > 0.5 else 663.0])
- Utils.check(str(passed_floats) == '[663]')
+ Utils.check(str(passed_floats) == '[663.0]')
Utils.check(passed_floats.get_typed_builtin() == TYPE_FLOAT)
var default_floats = (func (floats: Array[float] = [364.0]): return floats).call()
- Utils.check(str(default_floats) == '[364]')
+ Utils.check(str(default_floats) == '[364.0]')
Utils.check(default_floats.get_typed_builtin() == TYPE_FLOAT)
var typed_int := 556
var converted_floats: Array[float] = [typed_int]
converted_floats.push_back(498)
- Utils.check(str(converted_floats) == '[556, 498]')
+ Utils.check(str(converted_floats) == '[556.0, 498.0]')
Utils.check(converted_floats.get_typed_builtin() == TYPE_FLOAT)
@@ -95,7 +95,7 @@ func test():
Utils.check(constant_basic.get_typed_builtin() == TYPE_NIL)
const constant_floats: Array[float] = [constant_float - constant_basic[0] - constant_int]
- Utils.check(str(constant_floats) == '[552]')
+ Utils.check(str(constant_floats) == '[552.0]')
Utils.check(constant_floats.get_typed_builtin() == TYPE_FLOAT)
@@ -103,15 +103,15 @@ func test():
untyped_basic = source_floats
var destination_floats: Array[float] = untyped_basic
destination_floats[0] -= 0.74
- Utils.check(str(source_floats) == '[999]')
- Utils.check(str(untyped_basic) == '[999]')
- Utils.check(str(destination_floats) == '[999]')
+ Utils.check(str(source_floats) == '[999.0]')
+ Utils.check(str(untyped_basic) == '[999.0]')
+ Utils.check(str(destination_floats) == '[999.0]')
Utils.check(destination_floats.get_typed_builtin() == TYPE_FLOAT)
var duplicated_floats := empty_floats.duplicate().slice(2, 3)
duplicated_floats[0] *= 3
- Utils.check(str(duplicated_floats) == '[789]')
+ Utils.check(str(duplicated_floats) == '[789.0]')
Utils.check(duplicated_floats.get_typed_builtin() == TYPE_FLOAT)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd
index 9d3fffd1de..c9ab368f45 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd
@@ -62,44 +62,44 @@ func test():
untyped_basic[430.0] = 34.0
inferred_basic[263.0] = 362.0
typed_basic[518.0] = 815.0
- Utils.check(str(empty_floats) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
- Utils.check(str(untyped_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
- Utils.check(str(inferred_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
- Utils.check(str(typed_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
+ Utils.check(str(empty_floats) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }')
+ Utils.check(str(untyped_basic) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }')
+ Utils.check(str(inferred_basic) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }')
+ Utils.check(str(typed_basic) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }')
const constant_float := 950.0
const constant_int := 170
var typed_float := 954.0
var filled_floats: Dictionary[float, float] = { constant_float: constant_int, typed_float: empty_floats[430.0] + empty_floats[263.0] }
- Utils.check(str(filled_floats) == '{ 950: 170, 954: 396 }')
+ Utils.check(str(filled_floats) == '{ 950.0: 170.0, 954.0: 396.0 }')
Utils.check(filled_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(filled_floats.get_typed_value_builtin() == TYPE_FLOAT)
var casted_floats := { empty_floats[263.0] * 2: empty_floats[263.0] / 2 } as Dictionary[float, float]
- Utils.check(str(casted_floats) == '{ 724: 181 }')
+ Utils.check(str(casted_floats) == '{ 724.0: 181.0 }')
Utils.check(casted_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(casted_floats.get_typed_value_builtin() == TYPE_FLOAT)
var returned_floats = (func () -> Dictionary[float, float]: return { 554: 455 }).call()
- Utils.check(str(returned_floats) == '{ 554: 455 }')
+ Utils.check(str(returned_floats) == '{ 554.0: 455.0 }')
Utils.check(returned_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(returned_floats.get_typed_value_builtin() == TYPE_FLOAT)
var passed_floats = floats_identity({ 663.0 if randf() > 0.5 else 663.0: 366.0 if randf() <= 0.5 else 366.0 })
- Utils.check(str(passed_floats) == '{ 663: 366 }')
+ Utils.check(str(passed_floats) == '{ 663.0: 366.0 }')
Utils.check(passed_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(passed_floats.get_typed_value_builtin() == TYPE_FLOAT)
var default_floats = (func (floats: Dictionary[float, float] = { 364.0: 463.0 }): return floats).call()
- Utils.check(str(default_floats) == '{ 364: 463 }')
+ Utils.check(str(default_floats) == '{ 364.0: 463.0 }')
Utils.check(default_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(default_floats.get_typed_value_builtin() == TYPE_FLOAT)
var typed_int := 556
var converted_floats: Dictionary[float, float] = { typed_int: typed_int }
converted_floats[498.0] = 894
- Utils.check(str(converted_floats) == '{ 556: 556, 498: 894 }')
+ Utils.check(str(converted_floats) == '{ 556.0: 556.0, 498.0: 894.0 }')
Utils.check(converted_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(converted_floats.get_typed_value_builtin() == TYPE_FLOAT)
@@ -110,7 +110,7 @@ func test():
Utils.check(constant_basic.get_typed_value_builtin() == TYPE_NIL)
const constant_floats: Dictionary[float, float] = { constant_float - constant_basic[228] - constant_int: constant_float + constant_basic[228] + constant_int }
- Utils.check(str(constant_floats) == '{ -42: 1942 }')
+ Utils.check(str(constant_floats) == '{ -42.0: 1942.0 }')
Utils.check(constant_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(constant_floats.get_typed_value_builtin() == TYPE_FLOAT)
@@ -119,9 +119,9 @@ func test():
untyped_basic = source_floats
var destination_floats: Dictionary[float, float] = untyped_basic
destination_floats[999.74] -= 0.999
- Utils.check(str(source_floats) == '{ 999.74: 47 }')
- Utils.check(str(untyped_basic) == '{ 999.74: 47 }')
- Utils.check(str(destination_floats) == '{ 999.74: 47 }')
+ Utils.check(str(source_floats) == '{ 999.74: 47.0 }')
+ Utils.check(str(untyped_basic) == '{ 999.74: 47.0 }')
+ Utils.check(str(destination_floats) == '{ 999.74: 47.0 }')
Utils.check(destination_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(destination_floats.get_typed_value_builtin() == TYPE_FLOAT)
@@ -131,7 +131,7 @@ func test():
duplicated_floats.erase(430.0)
duplicated_floats.erase(518.0)
duplicated_floats[263.0] *= 3
- Utils.check(str(duplicated_floats) == '{ 263: 1086 }')
+ Utils.check(str(duplicated_floats) == '{ 263.0: 1086.0 }')
Utils.check(duplicated_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(duplicated_floats.get_typed_value_builtin() == TYPE_FLOAT)
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/features/export_arrays.out b/modules/gdscript/tests/scripts/parser/features/export_arrays.out
index f1522d096f..7201d8082d 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_arrays.out
+++ b/modules/gdscript/tests/scripts/parser/features/export_arrays.out
@@ -80,21 +80,21 @@ var test_placeholder: Array
var test_placeholder_packed: PackedStringArray
hint=TYPE_STRING hint_string="<String>/<PLACEHOLDER_TEXT>:Placeholder" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int: Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int_packed_byte: PackedByteArray
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int_packed32: PackedInt32Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int_packed64: PackedInt64Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int_float_step: Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10,0.01" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0,0.01" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_float: Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_float_packed32: PackedFloat32Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_float_packed64: PackedFloat64Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_exp_easing: Array
hint=TYPE_STRING hint_string="<float>/<EXP_EASING>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_exp_easing_packed32: PackedFloat32Array
@@ -126,14 +126,14 @@ var test_weak_packed_vector3_array: PackedVector3Array
var test_weak_packed_vector4_array: PackedVector4Array
hint=TYPE_STRING hint_string="<Vector4>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_byte_array: PackedByteArray
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_int32_array: PackedInt32Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_int64_array: PackedInt64Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_float32_array: PackedFloat32Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_float64_array: PackedFloat64Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_noalpha_weak_packed_color_array: PackedColorArray
hint=TYPE_STRING hint_string="<Color>/<COLOR_NO_ALPHA>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.out b/modules/gdscript/tests/scripts/parser/features/export_variable.out
index 0d915e00e6..c0bf4d6e06 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.out
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out
@@ -4,11 +4,11 @@ var test_weak_int: int = 1
var test_hard_int: int = 2
hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range: int = 100
- hint=RANGE hint_string="0,100" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=RANGE hint_string="0.0,100.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_step: int = 101
- hint=RANGE hint_string="0,100,1" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=RANGE hint_string="0.0,100.0,1.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_step_or_greater: int = 102
- hint=RANGE hint_string="0,100,1,or_greater" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=RANGE hint_string="0.0,100.0,1.0,or_greater" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_color: Color = Color(0, 0, 0, 1)
hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_color_no_alpha: Color = Color(0, 0, 0, 1)
diff --git a/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out b/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out
index c5958365ec..d94cbe5556 100644
--- a/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out
+++ b/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out
@@ -13,4 +13,4 @@ true
0
-255
256
-2
+2.0
diff --git a/modules/gdscript/tests/scripts/parser/features/number_separators.out b/modules/gdscript/tests/scripts/parser/features/number_separators.out
index b0d2fd94fe..9407af9cd8 100644
--- a/modules/gdscript/tests/scripts/parser/features/number_separators.out
+++ b/modules/gdscript/tests/scripts/parser/features/number_separators.out
@@ -13,12 +13,12 @@ GDTEST_OK
---
-1234.4567
-1234.4567
--1234
--1234
+-1234.0
+-1234.0
0.4567
0.4567
---
--1234500
--1234500
--1234500
--1234500
+-1234500.0
+-1234500.0
+-1234500.0
+-1234500.0
diff --git a/modules/gdscript/tests/scripts/parser/features/operator_assign.out b/modules/gdscript/tests/scripts/parser/features/operator_assign.out
index b0cb63ef59..29910adf38 100644
--- a/modules/gdscript/tests/scripts/parser/features/operator_assign.out
+++ b/modules/gdscript/tests/scripts/parser/features/operator_assign.out
@@ -1,2 +1,2 @@
GDTEST_OK
-8
+8.0
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/chain_assignment_works.out b/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out
index 22929bf636..04b0773991 100644
--- a/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out
+++ b/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out
@@ -1,7 +1,7 @@
GDTEST_OK
-{ 1: (2, 0) }
-{ 3: (4, 0) }
-[[(5, 0)]]
-[[(6, 0)]]
-[[(7, 0)]]
-[X: (8, 9, 7), Y: (0, 1, 0), Z: (0, 0, 1), O: (0, 0, 0)]
+{ 1: (2.0, 0.0) }
+{ 3: (4.0, 0.0) }
+[[(5.0, 0.0)]]
+[[(6.0, 0.0)]]
+[[(7.0, 0.0)]]
+[X: (8.0, 9.0, 7.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0), O: (0.0, 0.0, 0.0)]
diff --git a/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out b/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out
index a9ef4919cf..78ea2a2d80 100644
--- a/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out
+++ b/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out
@@ -1,8 +1,8 @@
GDTEST_OK
-x is 1
+x is 1.0
typeof x is 3
-x is 2
+x is 2.0
typeof x is 3
-x is 3
+x is 3.0
typeof x is 3
ok
diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out
index 5b981bc8bb..1650acadb5 100644
--- a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out
+++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out
@@ -2,16 +2,16 @@ 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
1
-(1, 1)
-(0, 0)
-(6, 1)
-(0, 0)
+(1.0, 1.0)
+(0.0, 0.0)
+(6.0, 1.0)
+(0.0, 0.0)
diff --git a/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out
index c51759f481..e82e31bbed 100644
--- a/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out
+++ b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out
@@ -1,26 +1,26 @@
GDTEST_OK
===
-prop1 setter (0, 0)
-prop1 setter (1, 0)
+prop1 setter (0.0, 0.0)
+prop1 setter (1.0, 0.0)
---
prop1 setter <Inner>
subprop getter
-subprop setter (1, 0)
+subprop setter (1.0, 0.0)
===
prop2 setter <Inner>
subprop getter
-subprop setter (1, 0)
+subprop setter (1.0, 0.0)
===
-prop3 setter (0, 0)
+prop3 setter (0.0, 0.0)
prop3 getter
-prop3 setter (1, 0)
+prop3 setter (1.0, 0.0)
---
prop3 setter <Inner>
prop3 getter
subprop getter
-subprop setter (1, 0)
+subprop setter (1.0, 0.0)
===
prop4 setter <Inner>
prop4 getter
subprop getter
-subprop setter (1, 0)
+subprop setter (1.0, 0.0)
diff --git a/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out
index 31b3b3a3a8..8617a65c33 100644
--- a/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out
+++ b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out
@@ -1,4 +1,4 @@
GDTEST_OK
-setting vec from (0, 0) to (2, 0)
-setting vec from (0, 0) to (0, 2)
-vec is (0, 0)
+setting vec from (0.0, 0.0) to (2.0, 0.0)
+setting vec from (0.0, 0.0) to (0.0, 2.0)
+vec is (0.0, 0.0)
diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.out b/modules/gdscript/tests/scripts/runtime/features/stringify.out
index 7833b6e213..2463d70ef4 100644
--- a/modules/gdscript/tests/scripts/runtime/features/stringify.out
+++ b/modules/gdscript/tests/scripts/runtime/features/stringify.out
@@ -9,13 +9,13 @@ hello world
[P: (0, 0), S: (0, 0)]
(0.25, 0.25, 0.25)
(0, 0, 0)
-[X: (1, 0), Y: (0, 1), O: (0, 0)]
-[N: (1, 2, 3), D: 4]
-(1, 2, 3, 4)
-[P: (0, 0, 0), S: (1, 1, 1)]
-[X: (1, 0, 0), Y: (0, 1, 0), Z: (0, 0, 1)]
-[X: (1, 0, 0), Y: (0, 1, 0), Z: (0, 0, 1), O: (0, 0, 0)]
+[X: (1.0, 0.0), Y: (0.0, 1.0), O: (0.0, 0.0)]
+[N: (1.0, 2.0, 3.0), D: 4]
(1, 2, 3, 4)
+[P: (0.0, 0.0, 0.0), S: (1.0, 1.0, 1.0)]
+[X: (1.0, 0.0, 0.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0)]
+[X: (1.0, 0.0, 0.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0), O: (0.0, 0.0, 0.0)]
+(1.0, 2.0, 3.0, 4.0)
hello
hello/world
RID(0)
@@ -26,10 +26,10 @@ Node::[signal]property_list_changed
[255, 0, 1]
[-1, 0, 1]
[-1, 0, 1]
-[-1, 0, 1]
-[-1, 0, 1]
+[-1.0, 0.0, 1.0]
+[-1.0, 0.0, 1.0]
["hello", "world"]
-[(1, 1), (0, 0)]
-[(1, 1, 1), (0, 0, 0)]
-[(1, 0, 0, 1), (0, 0, 1, 1), (0, 1, 0, 1)]
+[(1.0, 1.0), (0.0, 0.0)]
+[(1.0, 1.0, 1.0), (0.0, 0.0, 0.0)]
+[(1.0, 0.0, 0.0, 1.0), (0.0, 0.0, 1.0, 1.0), (0.0, 1.0, 0.0, 1.0)]
[(1, 1, 1, 1), (0, 0, 0, 0)]
diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd
index fa289e442f..225bcb3008 100644
--- a/modules/gdscript/tests/scripts/utils.notest.gd
+++ b/modules/gdscript/tests/scripts/utils.notest.gd
@@ -1,6 +1,5 @@
class_name Utils
-
# `assert()` is not evaluated in non-debug builds. Do not use `assert()`
# for anything other than testing the `assert()` itself.
static func check(condition: Variant) -> void:
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/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml
index 10534594d3..ffc3ab926c 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,6 +80,14 @@
[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" />
diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
index b33e296e1c..8fcb925a48 100644
--- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml
+++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
@@ -33,6 +33,20 @@
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.
</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 null, 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">
<return type="int" enum="Error" />
<param index="0" name="state" type="GLTFState" />
@@ -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 null, 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/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml
index a242a0d1d8..eb92723a06 100644
--- a/modules/gltf/doc_classes/GLTFNode.xml
+++ b/modules/gltf/doc_classes/GLTFNode.xml
@@ -27,6 +27,15 @@
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.
</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 true, 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 false will become [code]^"A/B/C/Skeleton3D:Bone3"[/code].
+ </description>
+ </method>
<method name="set_additional_data">
<return type="void" />
<param index="0" name="extension_name" type="StringName" />
diff --git a/modules/gltf/doc_classes/GLTFObjectModelProperty.xml b/modules/gltf/doc_classes/GLTFObjectModelProperty.xml
new file mode 100644
index 0000000000..e983269ccc
--- /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 0 is false, and any other value is true.
+ </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/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 ab7f3bdc52..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) {
@@ -3103,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;
}
@@ -3135,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);
@@ -3268,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);
@@ -3289,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,
@@ -3301,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;
@@ -3311,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);
@@ -3502,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;
@@ -3560,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) {
@@ -3650,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
@@ -4782,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;
}
@@ -4791,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);
@@ -4820,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);
@@ -4840,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);
@@ -4865,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;
@@ -4906,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);
@@ -4934,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;
@@ -4958,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);
@@ -4981,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") {
@@ -5033,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 + "'.");
}
}
@@ -5090,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];
@@ -5255,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());
@@ -5372,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);
@@ -5570,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())) {
@@ -5855,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.");
@@ -5926,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();
@@ -5948,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
@@ -5961,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;
}
@@ -6202,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;
@@ -6372,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;
}
@@ -6417,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;
}
@@ -6452,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;
}
@@ -6487,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;
@@ -6798,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;
@@ -6820,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);
}
}
@@ -7076,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"),
@@ -7137,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");
@@ -7314,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);
@@ -7330,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;
}
@@ -7351,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) {
@@ -7464,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) {
@@ -7481,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;
@@ -7495,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/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/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index e97229c621..db90ac5a6e 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -4372,9 +4372,45 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
return true;
}
+static String _get_vector2_cs_ctor_args(const Vector2 &p_vec2) {
+ return String::num_real(p_vec2.x, true) + "f, " +
+ String::num_real(p_vec2.y, true) + "f";
+}
+
+static String _get_vector3_cs_ctor_args(const Vector3 &p_vec3) {
+ return String::num_real(p_vec3.x, true) + "f, " +
+ String::num_real(p_vec3.y, true) + "f, " +
+ String::num_real(p_vec3.z, true) + "f";
+}
+
+static String _get_vector4_cs_ctor_args(const Vector4 &p_vec4) {
+ return String::num_real(p_vec4.x, true) + "f, " +
+ String::num_real(p_vec4.y, true) + "f, " +
+ String::num_real(p_vec4.z, true) + "f, " +
+ String::num_real(p_vec4.w, true) + "f";
+}
+
+static String _get_vector2i_cs_ctor_args(const Vector2i &p_vec2i) {
+ return itos(p_vec2i.x) + ", " + itos(p_vec2i.y);
+}
+
+static String _get_vector3i_cs_ctor_args(const Vector3i &p_vec3i) {
+ return itos(p_vec3i.x) + ", " + itos(p_vec3i.y) + ", " + itos(p_vec3i.z);
+}
+
+static String _get_vector4i_cs_ctor_args(const Vector4i &p_vec4i) {
+ return itos(p_vec4i.x) + ", " + itos(p_vec4i.y) + ", " + itos(p_vec4i.z) + ", " + itos(p_vec4i.w);
+}
+
+static String _get_color_cs_ctor_args(const Color &p_color) {
+ return String::num(p_color.r, 4) + "f, " +
+ String::num(p_color.g, 4) + "f, " +
+ String::num(p_color.b, 4) + "f, " +
+ String::num(p_color.a, 4) + "f";
+}
+
bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, ArgumentInterface &r_iarg) {
r_iarg.def_param_value = p_val;
- r_iarg.default_argument = p_val.operator String();
switch (p_val.get_type()) {
case Variant::NIL:
@@ -4387,10 +4423,14 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
break;
case Variant::INT:
if (r_iarg.type.cname != name_cache.type_int) {
- r_iarg.default_argument = "(%s)(" + r_iarg.default_argument + ")";
+ r_iarg.default_argument = "(%s)(" + p_val.operator String() + ")";
+ } else {
+ r_iarg.default_argument = p_val.operator String();
}
break;
case Variant::FLOAT:
+ r_iarg.default_argument = p_val.operator String();
+
if (r_iarg.type.cname == name_cache.type_float) {
r_iarg.default_argument += "f";
}
@@ -4400,7 +4440,7 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
case Variant::NODE_PATH:
if (r_iarg.type.cname == name_cache.type_StringName || r_iarg.type.cname == name_cache.type_NodePath) {
if (r_iarg.default_argument.length() > 0) {
- r_iarg.default_argument = "(%s)\"" + r_iarg.default_argument + "\"";
+ r_iarg.default_argument = "(%s)\"" + p_val.operator String() + "\"";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;
} else {
// No need for a special `in` statement to change `null` to `""`. Marshaling takes care of this already.
@@ -4408,40 +4448,62 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
}
} else {
CRASH_COND(r_iarg.type.cname != name_cache.type_String);
- r_iarg.default_argument = "\"" + r_iarg.default_argument + "\"";
+ r_iarg.default_argument = "\"" + p_val.operator String() + "\"";
}
break;
case Variant::PLANE: {
Plane plane = p_val.operator Plane();
- r_iarg.default_argument = "new Plane(new Vector3" + plane.normal.operator String() + ", " + rtos(plane.d) + ")";
+ r_iarg.default_argument = "new Plane(new Vector3(" +
+ _get_vector3_cs_ctor_args(plane.normal) + "), " + rtos(plane.d) + "f)";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
case Variant::AABB: {
AABB aabb = p_val.operator ::AABB();
- r_iarg.default_argument = "new Aabb(new Vector3" + aabb.position.operator String() + ", new Vector3" + aabb.size.operator String() + ")";
+ r_iarg.default_argument = "new Aabb(new Vector3(" +
+ _get_vector3_cs_ctor_args(aabb.position) + "), new Vector3(" +
+ _get_vector3_cs_ctor_args(aabb.size) + "))";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
case Variant::RECT2: {
Rect2 rect = p_val.operator Rect2();
- r_iarg.default_argument = "new Rect2(new Vector2" + rect.position.operator String() + ", new Vector2" + rect.size.operator String() + ")";
+ r_iarg.default_argument = "new Rect2(new Vector2(" +
+ _get_vector2_cs_ctor_args(rect.position) + "), new Vector2(" +
+ _get_vector2_cs_ctor_args(rect.size) + "))";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
case Variant::RECT2I: {
Rect2i rect = p_val.operator Rect2i();
- r_iarg.default_argument = "new Rect2I(new Vector2I" + rect.position.operator String() + ", new Vector2I" + rect.size.operator String() + ")";
+ r_iarg.default_argument = "new Rect2I(new Vector2I(" +
+ _get_vector2i_cs_ctor_args(rect.position) + "), new Vector2I(" +
+ _get_vector2i_cs_ctor_args(rect.size) + "))";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
case Variant::COLOR:
+ r_iarg.default_argument = "new Color(" + _get_color_cs_ctor_args(p_val.operator Color()) + ")";
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ break;
case Variant::VECTOR2:
+ r_iarg.default_argument = "new Vector2(" + _get_vector2_cs_ctor_args(p_val.operator Vector2()) + ")";
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ break;
case Variant::VECTOR2I:
+ r_iarg.default_argument = "new Vector2I(" + _get_vector2i_cs_ctor_args(p_val.operator Vector2i()) + ")";
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ break;
case Variant::VECTOR3:
+ r_iarg.default_argument = "new Vector3(" + _get_vector3_cs_ctor_args(p_val.operator Vector3()) + ")";
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ break;
case Variant::VECTOR3I:
- r_iarg.default_argument = "new %s" + r_iarg.default_argument;
+ r_iarg.default_argument = "new Vector3I(" + _get_vector3i_cs_ctor_args(p_val.operator Vector3i()) + ")";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
break;
case Variant::VECTOR4:
+ r_iarg.default_argument = "new Vector4(" + _get_vector4_cs_ctor_args(p_val.operator Vector4()) + ")";
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ break;
case Variant::VECTOR4I:
- r_iarg.default_argument = "new %s" + r_iarg.default_argument;
+ r_iarg.default_argument = "new Vector4I(" + _get_vector4i_cs_ctor_args(p_val.operator Vector4i()) + ")";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
break;
case Variant::OBJECT:
@@ -4491,7 +4553,10 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
if (transform == Transform2D()) {
r_iarg.default_argument = "Transform2D.Identity";
} else {
- r_iarg.default_argument = "new Transform2D(new Vector2" + transform.columns[0].operator String() + ", new Vector2" + transform.columns[1].operator String() + ", new Vector2" + transform.columns[2].operator String() + ")";
+ r_iarg.default_argument = "new Transform2D(new Vector2(" +
+ _get_vector2_cs_ctor_args(transform.columns[0]) + "), new Vector2(" +
+ _get_vector2_cs_ctor_args(transform.columns[1]) + "), new Vector2(" +
+ _get_vector2_cs_ctor_args(transform.columns[2]) + "))";
}
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
@@ -4501,7 +4566,11 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
r_iarg.default_argument = "Transform3D.Identity";
} else {
Basis basis = transform.basis;
- r_iarg.default_argument = "new Transform3D(new Vector3" + basis.get_column(0).operator String() + ", new Vector3" + basis.get_column(1).operator String() + ", new Vector3" + basis.get_column(2).operator String() + ", new Vector3" + transform.origin.operator String() + ")";
+ r_iarg.default_argument = "new Transform3D(new Vector3(" +
+ _get_vector3_cs_ctor_args(basis.get_column(0)) + "), new Vector3(" +
+ _get_vector3_cs_ctor_args(basis.get_column(1)) + "), new Vector3(" +
+ _get_vector3_cs_ctor_args(basis.get_column(2)) + "), new Vector3(" +
+ _get_vector3_cs_ctor_args(transform.origin) + "))";
}
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
@@ -4510,7 +4579,11 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
if (projection == Projection()) {
r_iarg.default_argument = "Projection.Identity";
} else {
- r_iarg.default_argument = "new Projection(new Vector4" + projection.columns[0].operator String() + ", new Vector4" + projection.columns[1].operator String() + ", new Vector4" + projection.columns[2].operator String() + ", new Vector4" + projection.columns[3].operator String() + ")";
+ r_iarg.default_argument = "new Projection(new Vector4(" +
+ _get_vector4_cs_ctor_args(projection.columns[0]) + "), new Vector4(" +
+ _get_vector4_cs_ctor_args(projection.columns[1]) + "), new Vector4(" +
+ _get_vector4_cs_ctor_args(projection.columns[2]) + "), new Vector4(" +
+ _get_vector4_cs_ctor_args(projection.columns[3]) + "))";
}
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
@@ -4519,7 +4592,10 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
if (basis == Basis()) {
r_iarg.default_argument = "Basis.Identity";
} else {
- r_iarg.default_argument = "new Basis(new Vector3" + basis.get_column(0).operator String() + ", new Vector3" + basis.get_column(1).operator String() + ", new Vector3" + basis.get_column(2).operator String() + ")";
+ r_iarg.default_argument = "new Basis(new Vector3(" +
+ _get_vector3_cs_ctor_args(basis.get_column(0)) + "), new Vector3(" +
+ _get_vector3_cs_ctor_args(basis.get_column(1)) + "), new Vector3(" +
+ _get_vector3_cs_ctor_args(basis.get_column(2)) + "))";
}
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
@@ -4528,7 +4604,11 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
if (quaternion == Quaternion()) {
r_iarg.default_argument = "Quaternion.Identity";
} else {
- r_iarg.default_argument = "new Quaternion" + quaternion.operator String();
+ r_iarg.default_argument = "new Quaternion(" +
+ String::num_real(quaternion.x, false) + "f, " +
+ String::num_real(quaternion.y, false) + "f, " +
+ String::num_real(quaternion.z, false) + "f, " +
+ String::num_real(quaternion.w, false) + "f)";
}
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
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/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 f5032eaa40..38f6931c8a 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -72,7 +72,8 @@ bool DisplayServerAndroid::has_feature(Feature p_feature) const {
//case FEATURE_MOUSE_WARP:
//case FEATURE_NATIVE_DIALOG:
case FEATURE_NATIVE_DIALOG_INPUT:
- //case FEATURE_NATIVE_DIALOG_FILE:
+ case FEATURE_NATIVE_DIALOG_FILE:
+ //case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
//case FEATURE_NATIVE_ICON:
//case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_CLIPBOARD:
@@ -189,6 +190,25 @@ void DisplayServerAndroid::emit_input_dialog_callback(String p_text) {
}
}
+Error DisplayServerAndroid::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+ GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
+ ERR_FAIL_NULL_V(godot_java, FAILED);
+ file_picker_callback = p_callback;
+ return godot_java->show_file_picker(p_current_directory, p_filename, p_mode, p_filters);
+}
+
+void DisplayServerAndroid::emit_file_picker_callback(bool p_ok, const Vector<String> &p_selected_paths) {
+ if (file_picker_callback.is_valid()) {
+ file_picker_callback.call_deferred(p_ok, p_selected_paths, 0);
+ }
+}
+
+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());
@@ -229,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 0b8b4dd6e8..1744ad3069 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -88,6 +88,7 @@ class DisplayServerAndroid : public DisplayServer {
Callable system_theme_changed;
Callable input_dialog_callback;
+ Callable file_picker_callback;
void _window_callback(const Callable &p_callable, const Variant &p_arg, bool p_deferred = false) const;
@@ -121,6 +122,11 @@ public:
virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override;
void emit_input_dialog_callback(String p_text);
+ 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;
@@ -129,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 9ad1e0b740..3ad8e6bc9e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -57,6 +57,7 @@ import com.google.android.vending.expansion.downloader.*
import org.godotengine.godot.error.Error
import org.godotengine.godot.input.GodotEditText
import org.godotengine.godot.input.GodotInputHandler
+import org.godotengine.godot.io.FilePicker
import org.godotengine.godot.io.directory.DirectoryAccessHandler
import org.godotengine.godot.io.file.FileAccessHandler
import org.godotengine.godot.plugin.AndroidRuntimePlugin
@@ -677,6 +678,9 @@ class Godot(private val context: Context) {
for (plugin in pluginRegistry.allPlugins) {
plugin.onMainActivityResult(requestCode, resultCode, data)
}
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ FilePicker.handleActivityResult(context, requestCode, resultCode, data)
+ }
}
/**
@@ -890,6 +894,13 @@ class Godot(private val context: Context) {
mClipboard.setPrimaryClip(clip)
}
+ @Keep
+ private fun showFilePicker(currentDirectory: String, filename: String, fileMode: Int, filters: Array<String>) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ FilePicker.showFilePicker(context, getActivity(), currentDirectory, filename, fileMode, filters)
+ }
+ }
+
/**
* Popup a dialog to input text.
*/
@@ -914,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/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
index 3c58e05dda..13ae2150d7 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
@@ -230,6 +230,11 @@ public class GodotLib {
public static native void inputDialogCallback(String p_text);
/**
+ * Invoked on the file picker closed.
+ */
+ public static native void filePickerCallback(boolean p_ok, String[] p_selected_paths);
+
+ /**
* Invoked on the GL thread to configure the height of the virtual keyboard.
*/
public static native void setVirtualKeyboardHeight(int p_height);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/FilePicker.kt b/platform/android/java/lib/src/org/godotengine/godot/io/FilePicker.kt
new file mode 100644
index 0000000000..2befe0583b
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/FilePicker.kt
@@ -0,0 +1,160 @@
+/**************************************************************************/
+/* FilePicker.kt */
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+package org.godotengine.godot.io
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.provider.DocumentsContract
+import android.util.Log
+import androidx.annotation.RequiresApi
+import org.godotengine.godot.GodotLib
+import org.godotengine.godot.io.file.MediaStoreData
+
+/**
+ * Utility class for managing file selection and file picker activities.
+ *
+ * It provides methods to launch a file picker and handle the result, supporting various file modes,
+ * including opening files, directories, and saving files.
+ */
+internal class FilePicker {
+ companion object {
+ private const val FILE_PICKER_REQUEST = 1000
+ private val TAG = FilePicker::class.java.simpleName
+
+ // Constants for fileMode values
+ private const val FILE_MODE_OPEN_FILE = 0
+ private const val FILE_MODE_OPEN_FILES = 1
+ private const val FILE_MODE_OPEN_DIR = 2
+ private const val FILE_MODE_OPEN_ANY = 3
+ private const val FILE_MODE_SAVE_FILE = 4
+
+ /**
+ * Handles the result from a file picker activity and processes the selected file(s) or directory.
+ *
+ * @param context The context from which the file picker was launched.
+ * @param requestCode The request code used when starting the file picker activity.
+ * @param resultCode The result code returned by the activity.
+ * @param data The intent data containing the selected file(s) or directory.
+ */
+ @RequiresApi(Build.VERSION_CODES.Q)
+ fun handleActivityResult(context: Context, requestCode: Int, resultCode: Int, data: Intent?) {
+ if (requestCode == FILE_PICKER_REQUEST) {
+ if (resultCode == Activity.RESULT_CANCELED) {
+ Log.d(TAG, "File picker canceled")
+ GodotLib.filePickerCallback(false, emptyArray())
+ return
+ }
+ if (resultCode == Activity.RESULT_OK) {
+ val selectedPaths: MutableList<String> = mutableListOf()
+ // Handle multiple file selection.
+ val clipData = data?.clipData
+ if (clipData != null) {
+ for (i in 0 until clipData.itemCount) {
+ val uri = clipData.getItemAt(i).uri
+ uri?.let {
+ val filepath = MediaStoreData.getFilePathFromUri(context, uri)
+ if (filepath != null) {
+ selectedPaths.add(filepath)
+ } else {
+ Log.d(TAG, "null filepath URI: $it")
+ }
+ }
+ }
+ } else {
+ val uri: Uri? = data?.data
+ uri?.let {
+ val filepath = MediaStoreData.getFilePathFromUri(context, uri)
+ if (filepath != null) {
+ selectedPaths.add(filepath)
+ } else {
+ Log.d(TAG, "null filepath URI: $it")
+ }
+ }
+ }
+
+ if (selectedPaths.isNotEmpty()) {
+ GodotLib.filePickerCallback(true, selectedPaths.toTypedArray())
+ } else {
+ GodotLib.filePickerCallback(false, emptyArray())
+ }
+ }
+ }
+ }
+
+ /**
+ * Launches a file picker activity with specified settings based on the mode, initial directory,
+ * file type filters, and other parameters.
+ *
+ * @param context The context from which to start the file picker.
+ * @param activity The activity instance used to initiate the picker. Required for activity results.
+ * @param currentDirectory The directory path to start the file picker in.
+ * @param filename The name of the file when using save mode.
+ * @param fileMode The mode to operate in, specifying open, save, or directory select.
+ * @param filters Array of MIME types to filter file selection.
+ */
+ @RequiresApi(Build.VERSION_CODES.Q)
+ fun showFilePicker(context: Context, activity: Activity?, currentDirectory: String, filename: String, fileMode: Int, filters: Array<String>) {
+ val intent = when (fileMode) {
+ FILE_MODE_OPEN_DIR -> Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
+ FILE_MODE_SAVE_FILE -> Intent(Intent.ACTION_CREATE_DOCUMENT)
+ else -> Intent(Intent.ACTION_OPEN_DOCUMENT)
+ }
+ val initialDirectory = MediaStoreData.getUriFromDirectoryPath(context, currentDirectory)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && initialDirectory != null) {
+ intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialDirectory)
+ } else {
+ Log.d(TAG, "Error cannot set initial directory")
+ }
+ if (fileMode == FILE_MODE_OPEN_FILES) {
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) // Set multi select for FILE_MODE_OPEN_FILES
+ } else if (fileMode == FILE_MODE_SAVE_FILE) {
+ intent.putExtra(Intent.EXTRA_TITLE, filename) // Set filename for FILE_MODE_SAVE_FILE
+ }
+ // ACTION_OPEN_DOCUMENT_TREE does not support intent type
+ if (fileMode != FILE_MODE_OPEN_DIR) {
+ intent.type = "*/*"
+ if (filters.isNotEmpty()) {
+ if (filters.size == 1) {
+ intent.type = filters[0]
+ } else {
+ intent.putExtra(Intent.EXTRA_MIME_TYPES, filters)
+ }
+ }
+ intent.addCategory(Intent.CATEGORY_OPENABLE)
+ }
+ intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true)
+ activity?.startActivityForResult(intent, FILE_PICKER_REQUEST)
+ }
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt
index 97362e2542..46bd465e90 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt
@@ -38,6 +38,7 @@ import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
+import android.util.Log
import androidx.annotation.RequiresApi
import java.io.File
@@ -46,6 +47,7 @@ import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.nio.channels.FileChannel
+
/**
* Implementation of [DataAccess] which handles access and interactions with file and data
* under scoped storage via the MediaStore API.
@@ -81,6 +83,10 @@ internal class MediaStoreData(context: Context, filePath: String, accessFlag: Fi
private const val SELECTION_BY_PATH = "${MediaStore.Files.FileColumns.DISPLAY_NAME} = ? " +
" AND ${MediaStore.Files.FileColumns.RELATIVE_PATH} = ?"
+ private const val AUTHORITY_MEDIA_DOCUMENTS = "com.android.providers.media.documents"
+ private const val AUTHORITY_EXTERNAL_STORAGE_DOCUMENTS = "com.android.externalstorage.documents"
+ private const val AUTHORITY_DOWNLOADS_DOCUMENTS = "com.android.providers.downloads.documents"
+
private fun getSelectionByPathArguments(path: String): Array<String> {
return arrayOf(getMediaStoreDisplayName(path), getMediaStoreRelativePath(path))
}
@@ -230,6 +236,72 @@ internal class MediaStoreData(context: Context, filePath: String, accessFlag: Fi
)
return updated > 0
}
+
+ fun getUriFromDirectoryPath(context: Context, directoryPath: String): Uri? {
+ if (!directoryExists(directoryPath)) {
+ return null
+ }
+ // Check if the path is under external storage.
+ val externalStorageRoot = Environment.getExternalStorageDirectory().absolutePath
+ if (directoryPath.startsWith(externalStorageRoot)) {
+ val relativePath = directoryPath.replaceFirst(externalStorageRoot, "").trim('/')
+ val uri = Uri.Builder()
+ .scheme("content")
+ .authority(AUTHORITY_EXTERNAL_STORAGE_DOCUMENTS)
+ .appendPath("document")
+ .appendPath("primary:$relativePath")
+ .build()
+ return uri
+ }
+ return null
+ }
+
+ fun getFilePathFromUri(context: Context, uri: Uri): String? {
+ // Converts content uri to filepath.
+ val id = getIdFromUri(uri) ?: return null
+
+ if (uri.authority == AUTHORITY_EXTERNAL_STORAGE_DOCUMENTS) {
+ val split = id.split(":")
+ val fileName = split.last()
+ val relativePath = split.dropLast(1).joinToString("/")
+ val fullPath = File(Environment.getExternalStorageDirectory(), "$relativePath/$fileName").absolutePath
+ return fullPath
+ } else {
+ val id = id.toLongOrNull() ?: return null
+ val dataItems = queryById(context, id)
+ return if (dataItems.isNotEmpty()) {
+ val dataItem = dataItems[0]
+ File(Environment.getExternalStorageDirectory(), File(dataItem.relativePath, dataItem.displayName).toString()).absolutePath
+ } else {
+ null
+ }
+ }
+ }
+
+ private fun getIdFromUri(uri: Uri): String? {
+ return try {
+ if (uri.authority == AUTHORITY_EXTERNAL_STORAGE_DOCUMENTS || uri.authority == AUTHORITY_MEDIA_DOCUMENTS || uri.authority == AUTHORITY_DOWNLOADS_DOCUMENTS) {
+ val documentId = uri.lastPathSegment ?: throw IllegalArgumentException("Invalid URI: $uri")
+ documentId.substringAfter(":")
+ } else {
+ throw IllegalArgumentException("Unsupported URI format: $uri")
+ }
+ } catch (e: Exception) {
+ Log.d(TAG, "Failed to parse ID from URI: $uri", e)
+ null
+ }
+ }
+
+ private fun directoryExists(path: String): Boolean {
+ return try {
+ val file = File(path)
+ file.isDirectory && file.exists()
+ } catch (e: SecurityException) {
+ Log.d(TAG, "Failed to check directoryExists: $path", e)
+ false
+ }
+ }
+
}
private val id: Long
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_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 997534ada3..5c1e78dcc4 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -548,6 +548,22 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_inputDialogCallback(J
}
}
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_filePickerCallback(JNIEnv *env, jclass clazz, jboolean p_ok, jobjectArray p_selected_paths) {
+ DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton();
+ if (ds) {
+ Vector<String> selected_paths;
+
+ jint length = env->GetArrayLength(p_selected_paths);
+ for (jint i = 0; i < length; ++i) {
+ jstring java_string = (jstring)env->GetObjectArrayElement(p_selected_paths, i);
+ String path = jstring_to_string(java_string, env);
+ selected_paths.push_back(path);
+ env->DeleteLocalRef(java_string);
+ }
+ ds->emit_file_picker_callback(p_ok, selected_paths);
+ }
+}
+
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result) {
String permission = jstring_to_string(p_permission, env);
if (permission == "android.permission.RECORD_AUDIO" && p_result) {
diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h
index 65ba1b2953..31a7598a7b 100644
--- a/platform/android/java_godot_lib_jni.h
+++ b/platform/android/java_godot_lib_jni.h
@@ -68,6 +68,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHei
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JNIEnv *env, jclass clazz);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_inputDialogCallback(JNIEnv *env, jclass clazz, jstring p_text);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_filePickerCallback(JNIEnv *env, jclass clazz, jboolean p_ok, jobjectArray p_selected_paths);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz);
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_shouldDispatchInputToRenderThread(JNIEnv *env, jclass clazz);
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index aae6ff23da..5ecd789d43 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -64,10 +64,12 @@ 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");
_show_input_dialog = p_env->GetMethodID(godot_class, "showInputDialog", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
+ _show_file_picker = p_env->GetMethodID(godot_class, "showFilePicker", "(Ljava/lang/String;Ljava/lang/String;I[Ljava/lang/String;)V");
_request_permission = p_env->GetMethodID(godot_class, "requestPermission", "(Ljava/lang/String;)Z");
_request_permissions = p_env->GetMethodID(godot_class, "requestPermissions", "()Z");
_get_granted_permissions = p_env->GetMethodID(godot_class, "getGrantedPermissions", "()[Ljava/lang/String;");
@@ -213,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;
}
@@ -286,6 +305,29 @@ Error GodotJavaWrapper::show_input_dialog(const String &p_title, const String &p
}
}
+Error GodotJavaWrapper::show_file_picker(const String &p_current_directory, const String &p_filename, int p_mode, const Vector<String> &p_filters) {
+ if (_show_file_picker) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, ERR_UNCONFIGURED);
+ jstring j_current_directory = env->NewStringUTF(p_current_directory.utf8().get_data());
+ jstring j_filename = env->NewStringUTF(p_filename.utf8().get_data());
+ jint j_mode = p_mode;
+ jobjectArray j_filters = env->NewObjectArray(p_filters.size(), env->FindClass("java/lang/String"), nullptr);
+ for (int i = 0; i < p_filters.size(); ++i) {
+ jstring j_filter = env->NewStringUTF(p_filters[i].utf8().get_data());
+ env->SetObjectArrayElement(j_filters, i, j_filter);
+ env->DeleteLocalRef(j_filter);
+ }
+ env->CallVoidMethod(godot_instance, _show_file_picker, j_current_directory, j_filename, j_mode, j_filters);
+ env->DeleteLocalRef(j_current_directory);
+ env->DeleteLocalRef(j_filename);
+ env->DeleteLocalRef(j_filters);
+ return OK;
+ } else {
+ return ERR_UNCONFIGURED;
+ }
+}
+
bool GodotJavaWrapper::request_permission(const String &p_name) {
if (_request_permission) {
JNIEnv *env = get_jni_env();
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index 4fa3098397..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,10 +56,12 @@ 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;
jmethodID _show_input_dialog = nullptr;
+ jmethodID _show_file_picker = nullptr;
jmethodID _request_permission = nullptr;
jmethodID _request_permissions = nullptr;
jmethodID _get_granted_permissions = nullptr;
@@ -98,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();
@@ -105,6 +109,7 @@ public:
bool has_has_clipboard();
bool has_clipboard();
Error show_input_dialog(const String &p_title, const String &p_message, const String &p_existing_text);
+ Error show_file_picker(const String &p_current_directory, const String &p_filename, int p_mode, const Vector<String> &p_filters);
bool request_permission(const String &p_name);
bool request_permissions();
Vector<String> get_granted_permissions() const;
diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm
index 7649828189..5d9179bd9a 100644
--- a/platform/ios/display_server_ios.mm
+++ b/platform/ios/display_server_ios.mm
@@ -370,6 +370,7 @@ bool DisplayServerIOS::has_feature(Feature p_feature) const {
// case FEATURE_NATIVE_DIALOG:
// case FEATURE_NATIVE_DIALOG_INPUT:
// case FEATURE_NATIVE_DIALOG_FILE:
+ // case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
// case FEATURE_NATIVE_ICON:
// case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_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/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp
index d5271f9ad4..fe359532bb 100644
--- a/platform/linuxbsd/wayland/display_server_wayland.cpp
+++ b/platform/linuxbsd/wayland/display_server_wayland.cpp
@@ -216,7 +216,8 @@ bool DisplayServerWayland::has_feature(Feature p_feature) const {
//case FEATURE_NATIVE_DIALOG:
//case FEATURE_NATIVE_DIALOG_INPUT:
#ifdef DBUS_ENABLED
- case FEATURE_NATIVE_DIALOG_FILE: {
+ case FEATURE_NATIVE_DIALOG_FILE:
+ case FEATURE_NATIVE_DIALOG_FILE_EXTRA: {
return true;
} break;
#endif
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index 48ff06cad2..a9c94bd823 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -129,6 +129,7 @@ bool DisplayServerX11::has_feature(Feature p_feature) const {
case FEATURE_ICON:
#ifdef DBUS_ENABLED
case FEATURE_NATIVE_DIALOG_FILE:
+ case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
#endif
//case FEATURE_NATIVE_DIALOG:
//case FEATURE_NATIVE_DIALOG_INPUT:
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index 714df18228..f1078d9868 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -752,6 +752,7 @@ bool DisplayServerMacOS::has_feature(Feature p_feature) const {
case FEATURE_NATIVE_DIALOG:
case FEATURE_NATIVE_DIALOG_INPUT:
case FEATURE_NATIVE_DIALOG_FILE:
+ case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
case FEATURE_IME:
case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_HIDPI:
diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp
index 4e55cc137a..b2db62ea2f 100644
--- a/platform/web/display_server_web.cpp
+++ b/platform/web/display_server_web.cpp
@@ -1133,6 +1133,7 @@ bool DisplayServerWeb::has_feature(Feature p_feature) const {
//case FEATURE_NATIVE_DIALOG:
//case FEATURE_NATIVE_DIALOG_INPUT:
//case FEATURE_NATIVE_DIALOG_FILE:
+ //case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
//case FEATURE_NATIVE_ICON:
//case FEATURE_WINDOW_TRANSPARENCY:
//case FEATURE_KEEP_SCREEN_ON:
diff --git a/platform/web/emscripten_helpers.py b/platform/web/emscripten_helpers.py
index 8fcabb21c7..3122271a71 100644
--- a/platform/web/emscripten_helpers.py
+++ b/platform/web/emscripten_helpers.py
@@ -3,6 +3,8 @@ import os
from SCons.Util import WhereIs
+from platform_methods import get_build_version
+
def run_closure_compiler(target, source, env, for_signature):
closure_bin = os.path.join(
@@ -21,22 +23,6 @@ def run_closure_compiler(target, source, env, for_signature):
return " ".join(cmd)
-def get_build_version():
- import version
-
- name = "custom_build"
- if os.getenv("BUILD_NAME") is not None:
- name = os.getenv("BUILD_NAME")
- v = "%d.%d" % (version.major, version.minor)
- if version.patch > 0:
- v += ".%d" % version.patch
- status = version.status
- if os.getenv("GODOT_VERSION_STATUS") is not None:
- status = str(os.getenv("GODOT_VERSION_STATUS"))
- v += ".%s.%s" % (status, name)
- return v
-
-
def create_engine_file(env, target, source, externs, threads_enabled):
if env["use_closure_compiler"]:
return env.BuildJS(target, source, JSEXTERNS=externs)
@@ -84,7 +70,7 @@ def create_template_zip(env, js, wasm, worker, side):
cache.append("godot.editor.worker.js")
opt_cache = ["godot.editor.wasm"]
subst_dict = {
- "___GODOT_VERSION___": get_build_version(),
+ "___GODOT_VERSION___": get_build_version(False),
"___GODOT_NAME___": "GodotEngine",
"___GODOT_CACHE___": json.dumps(cache),
"___GODOT_OPT_CACHE___": json.dumps(opt_cache),
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 07424a3b4c..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"
@@ -129,6 +130,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const {
case FEATURE_NATIVE_DIALOG:
case FEATURE_NATIVE_DIALOG_INPUT:
case FEATURE_NATIVE_DIALOG_FILE:
+ case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
case FEATURE_SWAP_BUFFERS:
case FEATURE_KEEP_SCREEN_ON:
case FEATURE_TEXT_TO_SPEECH:
@@ -734,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;
@@ -1616,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) {
@@ -1730,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) {
@@ -5319,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);
@@ -5449,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);
}
@@ -5525,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);
}
@@ -6173,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;
@@ -6605,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);
}
@@ -6636,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/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/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp
index 1cab29f383..108f200613 100644
--- a/scene/2d/animated_sprite_2d.cpp
+++ b/scene/2d/animated_sprite_2d.cpp
@@ -56,7 +56,9 @@ Point2 AnimatedSprite2D::_edit_get_pivot() const {
bool AnimatedSprite2D::_edit_use_pivot() const {
return true;
}
+#endif // TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 AnimatedSprite2D::_edit_get_rect() const {
return _get_rect();
}
@@ -75,7 +77,7 @@ bool AnimatedSprite2D::_edit_use_rect() const {
}
return t.is_valid();
}
-#endif
+#endif // DEBUG_ENABLED
Rect2 AnimatedSprite2D::get_anchorable_rect() const {
return _get_rect();
@@ -593,7 +595,7 @@ void AnimatedSprite2D::get_argument_options(const StringName &p_function, int p_
}
Node2D::get_argument_options(p_function, p_idx, r_options);
}
-#endif
+#endif // TOOLS_ENABLED
#ifndef DISABLE_DEPRECATED
bool AnimatedSprite2D::_set(const StringName &p_name, const Variant &p_value) {
diff --git a/scene/2d/animated_sprite_2d.h b/scene/2d/animated_sprite_2d.h
index 914dc405ab..3de36e7cab 100644
--- a/scene/2d/animated_sprite_2d.h
+++ b/scene/2d/animated_sprite_2d.h
@@ -66,7 +66,7 @@ class AnimatedSprite2D : public Node2D {
protected:
#ifndef DISABLE_DEPRECATED
bool _set(const StringName &p_name, const Variant &p_value);
-#endif
+#endif // DISABLE_DEPRECATED
static void _bind_methods();
void _notification(int p_what);
void _validate_property(PropertyInfo &p_property) const;
@@ -79,9 +79,12 @@ public:
virtual void _edit_set_pivot(const Point2 &p_pivot) override;
virtual Point2 _edit_get_pivot() const override;
virtual bool _edit_use_pivot() const override;
+#endif // TOOLS_ENABLED
+
+#ifdef DEBUG_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
-#endif
+#endif // DEBUG_ENABLED
virtual Rect2 get_anchorable_rect() const override;
@@ -129,7 +132,7 @@ public:
#ifdef TOOLS_ENABLED
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
-#endif
+#endif // TOOLS_ENABLED
AnimatedSprite2D();
};
diff --git a/scene/2d/back_buffer_copy.cpp b/scene/2d/back_buffer_copy.cpp
index 4cae1affc3..045dd3be02 100644
--- a/scene/2d/back_buffer_copy.cpp
+++ b/scene/2d/back_buffer_copy.cpp
@@ -45,7 +45,7 @@ void BackBufferCopy::_update_copy_mode() {
}
}
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 BackBufferCopy::_edit_get_rect() const {
return rect;
}
@@ -53,7 +53,7 @@ Rect2 BackBufferCopy::_edit_get_rect() const {
bool BackBufferCopy::_edit_use_rect() const {
return true;
}
-#endif
+#endif // DEBUG_ENABLED
Rect2 BackBufferCopy::get_anchorable_rect() const {
return rect;
diff --git a/scene/2d/back_buffer_copy.h b/scene/2d/back_buffer_copy.h
index e918995708..9bcb731b50 100644
--- a/scene/2d/back_buffer_copy.h
+++ b/scene/2d/back_buffer_copy.h
@@ -54,10 +54,10 @@ protected:
void _validate_property(PropertyInfo &p_property) const;
public:
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
-#endif
+#endif // DEBUG_ENABLED
void set_rect(const Rect2 &p_rect);
Rect2 get_rect() const;
diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp
index ebed1e84e6..74f26df706 100644
--- a/scene/2d/camera_2d.cpp
+++ b/scene/2d/camera_2d.cpp
@@ -302,6 +302,7 @@ void Camera2D::_notification(int p_what) {
_interpolation_data.xform_prev = _interpolation_data.xform_curr;
} break;
+ case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (is_physics_interpolated_and_enabled()) {
_update_scroll();
diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp
index cfdcbee86a..b50881cb89 100644
--- a/scene/2d/gpu_particles_2d.cpp
+++ b/scene/2d/gpu_particles_2d.cpp
@@ -696,6 +696,8 @@ void GPUParticles2D::_notification(int p_what) {
RS::get_singleton()->particles_set_subemitter(particles, RID());
} break;
+ case NOTIFICATION_SUSPENDED:
+ case NOTIFICATION_UNSUSPENDED:
case NOTIFICATION_PAUSED:
case NOTIFICATION_UNPAUSED: {
if (is_inside_tree()) {
diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp
index 50c5873781..21648bbc49 100644
--- a/scene/2d/light_2d.cpp
+++ b/scene/2d/light_2d.cpp
@@ -54,7 +54,7 @@ void Light2D::_update_light_visibility() {
if (editor_only) {
editor_ok = false;
}
-#endif
+#endif // TOOLS_ENABLED
RS::get_singleton()->canvas_light_set_enabled(canvas_light, enabled && is_visible_in_tree() && editor_ok);
}
@@ -343,7 +343,6 @@ Light2D::~Light2D() {
//////////////////////////////
#ifdef TOOLS_ENABLED
-
Dictionary PointLight2D::_edit_get_state() const {
Dictionary state = Node2D::_edit_get_state();
state["offset"] = get_texture_offset();
@@ -367,7 +366,9 @@ Point2 PointLight2D::_edit_get_pivot() const {
bool PointLight2D::_edit_use_pivot() const {
return true;
}
+#endif // TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 PointLight2D::_edit_get_rect() const {
if (texture.is_null()) {
return Rect2();
@@ -380,7 +381,7 @@ Rect2 PointLight2D::_edit_get_rect() const {
bool PointLight2D::_edit_use_rect() const {
return !texture.is_null();
}
-#endif
+#endif // DEBUG_ENABLED
Rect2 PointLight2D::get_anchorable_rect() const {
if (texture.is_null()) {
diff --git a/scene/2d/light_2d.h b/scene/2d/light_2d.h
index 8a0c2a2a92..0c1dae071c 100644
--- a/scene/2d/light_2d.h
+++ b/scene/2d/light_2d.h
@@ -160,9 +160,12 @@ public:
virtual void _edit_set_pivot(const Point2 &p_pivot) override;
virtual Point2 _edit_get_pivot() const override;
virtual bool _edit_use_pivot() const override;
+#endif // TOOLS_ENABLED
+
+#ifdef DEBUG_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
-#endif
+#endif // DEBUG_ENABLED
virtual Rect2 get_anchorable_rect() const override;
diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp
index 7c3fb61d04..e2e4125d1f 100644
--- a/scene/2d/light_occluder_2d.cpp
+++ b/scene/2d/light_occluder_2d.cpp
@@ -35,7 +35,7 @@
#define LINE_GRAB_WIDTH 8
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 OccluderPolygon2D::_edit_get_rect() const {
if (rect_cache_dirty) {
if (closed) {
@@ -83,13 +83,14 @@ bool OccluderPolygon2D::_edit_is_selected_on_click(const Point2 &p_point, double
return false;
}
}
-#endif
+#endif // DEBUG_ENABLED
void OccluderPolygon2D::set_polygon(const Vector<Vector2> &p_polygon) {
polygon = p_polygon;
rect_cache_dirty = true;
RS::get_singleton()->canvas_occluder_polygon_set_shape(occ_polygon, p_polygon, closed);
emit_changed();
+ update_configuration_warning();
}
Vector<Vector2> OccluderPolygon2D::get_polygon() const {
@@ -155,7 +156,7 @@ OccluderPolygon2D::~OccluderPolygon2D() {
void LightOccluder2D::_poly_changed() {
#ifdef DEBUG_ENABLED
queue_redraw();
-#endif
+#endif // DEBUG_ENABLED
}
void LightOccluder2D::_physics_interpolated_changed() {
@@ -217,7 +218,7 @@ void LightOccluder2D::_notification(int p_what) {
}
}
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 LightOccluder2D::_edit_get_rect() const {
return occluder_polygon.is_valid() ? occluder_polygon->_edit_get_rect() : Rect2();
}
@@ -225,14 +226,14 @@ Rect2 LightOccluder2D::_edit_get_rect() const {
bool LightOccluder2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
return occluder_polygon.is_valid() ? occluder_polygon->_edit_is_selected_on_click(p_point, p_tolerance) : false;
}
-#endif
+#endif // DEBUG_ENABLED
void LightOccluder2D::set_occluder_polygon(const Ref<OccluderPolygon2D> &p_polygon) {
#ifdef DEBUG_ENABLED
if (occluder_polygon.is_valid()) {
occluder_polygon->disconnect_changed(callable_mp(this, &LightOccluder2D::_poly_changed));
}
-#endif
+#endif // DEBUG_ENABLED
occluder_polygon = p_polygon;
if (occluder_polygon.is_valid()) {
@@ -246,7 +247,7 @@ void LightOccluder2D::set_occluder_polygon(const Ref<OccluderPolygon2D> &p_polyg
occluder_polygon->connect_changed(callable_mp(this, &LightOccluder2D::_poly_changed));
}
queue_redraw();
-#endif
+#endif // DEBUG_ENABLED
}
Ref<OccluderPolygon2D> LightOccluder2D::get_occluder_polygon() const {
diff --git a/scene/2d/light_occluder_2d.h b/scene/2d/light_occluder_2d.h
index 4c499d0465..b9f7eec33d 100644
--- a/scene/2d/light_occluder_2d.h
+++ b/scene/2d/light_occluder_2d.h
@@ -56,11 +56,10 @@ protected:
static void _bind_methods();
public:
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
virtual Rect2 _edit_get_rect() const;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const;
-#endif
-
+#endif // DEBUG_ENABLED
void set_polygon(const Vector<Vector2> &p_polygon);
Vector<Vector2> get_polygon() const;
@@ -93,10 +92,10 @@ protected:
static void _bind_methods();
public:
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
-#endif
+#endif // DEBUG_ENABLED
void set_occluder_polygon(const Ref<OccluderPolygon2D> &p_polygon);
Ref<OccluderPolygon2D> get_occluder_polygon() const;
diff --git a/scene/2d/line_2d.cpp b/scene/2d/line_2d.cpp
index be58875e0e..734db2ea2b 100644
--- a/scene/2d/line_2d.cpp
+++ b/scene/2d/line_2d.cpp
@@ -36,7 +36,7 @@
Line2D::Line2D() {
}
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 Line2D::_edit_get_rect() const {
if (_points.size() == 0) {
return Rect2(0, 0, 0, 0);
diff --git a/scene/2d/line_2d.h b/scene/2d/line_2d.h
index 1d750ca456..0156336403 100644
--- a/scene/2d/line_2d.h
+++ b/scene/2d/line_2d.h
@@ -55,7 +55,7 @@ public:
LINE_TEXTURE_STRETCH
};
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
diff --git a/scene/2d/marker_2d.cpp b/scene/2d/marker_2d.cpp
index b1b9705bf8..1f4ea172e9 100644
--- a/scene/2d/marker_2d.cpp
+++ b/scene/2d/marker_2d.cpp
@@ -61,7 +61,7 @@ void Marker2D::_draw_cross() {
draw_multiline_colors(points, colors);
}
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 Marker2D::_edit_get_rect() const {
real_t extents = get_gizmo_extents();
return Rect2(Point2(-extents, -extents), Size2(extents * 2, extents * 2));
@@ -70,7 +70,7 @@ Rect2 Marker2D::_edit_get_rect() const {
bool Marker2D::_edit_use_rect() const {
return false;
}
-#endif
+#endif // DEBUG_ENABLED
void Marker2D::_notification(int p_what) {
switch (p_what) {
diff --git a/scene/2d/marker_2d.h b/scene/2d/marker_2d.h
index e81454797b..6a1dfb1cb2 100644
--- a/scene/2d/marker_2d.h
+++ b/scene/2d/marker_2d.h
@@ -45,10 +45,10 @@ protected:
static void _bind_methods();
public:
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
-#endif
+#endif // DEBUG_ENABLED
void set_gizmo_extents(real_t p_extents);
real_t get_gizmo_extents() const;
diff --git a/scene/2d/mesh_instance_2d.cpp b/scene/2d/mesh_instance_2d.cpp
index 1031a67343..9b7a89fe3b 100644
--- a/scene/2d/mesh_instance_2d.cpp
+++ b/scene/2d/mesh_instance_2d.cpp
@@ -88,7 +88,7 @@ Ref<Texture2D> MeshInstance2D::get_texture() const {
return texture;
}
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 MeshInstance2D::_edit_get_rect() const {
if (mesh.is_valid()) {
AABB aabb = mesh->get_aabb();
@@ -101,7 +101,7 @@ Rect2 MeshInstance2D::_edit_get_rect() const {
bool MeshInstance2D::_edit_use_rect() const {
return mesh.is_valid();
}
-#endif
+#endif // DEBUG_ENABLED
MeshInstance2D::MeshInstance2D() {
}
diff --git a/scene/2d/mesh_instance_2d.h b/scene/2d/mesh_instance_2d.h
index c914f13ade..b23b2ca907 100644
--- a/scene/2d/mesh_instance_2d.h
+++ b/scene/2d/mesh_instance_2d.h
@@ -45,10 +45,10 @@ protected:
static void _bind_methods();
public:
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
-#endif
+#endif // DEBUG_ENABLED
void set_mesh(const Ref<Mesh> &p_mesh);
Ref<Mesh> get_mesh() const;
diff --git a/scene/2d/multimesh_instance_2d.cpp b/scene/2d/multimesh_instance_2d.cpp
index 417e628517..811dccab4a 100644
--- a/scene/2d/multimesh_instance_2d.cpp
+++ b/scene/2d/multimesh_instance_2d.cpp
@@ -84,7 +84,7 @@ Ref<Texture2D> MultiMeshInstance2D::get_texture() const {
return texture;
}
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 MultiMeshInstance2D::_edit_get_rect() const {
if (multimesh.is_valid()) {
AABB aabb = multimesh->get_aabb();
@@ -93,7 +93,7 @@ Rect2 MultiMeshInstance2D::_edit_get_rect() const {
return Node2D::_edit_get_rect();
}
-#endif
+#endif // DEBUG_ENABLED
MultiMeshInstance2D::MultiMeshInstance2D() {
}
diff --git a/scene/2d/multimesh_instance_2d.h b/scene/2d/multimesh_instance_2d.h
index 0647412294..fec27a601a 100644
--- a/scene/2d/multimesh_instance_2d.h
+++ b/scene/2d/multimesh_instance_2d.h
@@ -46,9 +46,9 @@ protected:
static void _bind_methods();
public:
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
virtual Rect2 _edit_get_rect() const override;
-#endif
+#endif // DEBUG_ENABLED
void set_multimesh(const Ref<MultiMesh> &p_multimesh);
Ref<MultiMesh> get_multimesh() const;
diff --git a/scene/2d/navigation_agent_2d.cpp b/scene/2d/navigation_agent_2d.cpp
index d0fae611d8..f030473c4b 100644
--- a/scene/2d/navigation_agent_2d.cpp
+++ b/scene/2d/navigation_agent_2d.cpp
@@ -253,12 +253,20 @@ void NavigationAgent2D::_notification(int p_what) {
#endif // DEBUG_ENABLED
} break;
+ case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (agent_parent) {
NavigationServer2D::get_singleton()->agent_set_paused(get_rid(), !agent_parent->can_process());
}
} break;
+ case NOTIFICATION_UNSUSPENDED: {
+ if (get_tree()->is_paused()) {
+ break;
+ }
+ [[fallthrough]];
+ }
+
case NOTIFICATION_UNPAUSED: {
if (agent_parent) {
NavigationServer2D::get_singleton()->agent_set_paused(get_rid(), !agent_parent->can_process());
diff --git a/scene/2d/navigation_link_2d.cpp b/scene/2d/navigation_link_2d.cpp
index 4961e18dc9..d90793a4c5 100644
--- a/scene/2d/navigation_link_2d.cpp
+++ b/scene/2d/navigation_link_2d.cpp
@@ -132,7 +132,7 @@ void NavigationLink2D::_notification(int p_what) {
}
}
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 NavigationLink2D::_edit_get_rect() const {
if (!is_inside_tree()) {
return Rect2();
@@ -152,7 +152,7 @@ bool NavigationLink2D::_edit_is_selected_on_click(const Point2 &p_point, double
Vector2 closest_point = Geometry2D::get_closest_point_to_segment(p_point, segment);
return p_point.distance_to(closest_point) < p_tolerance;
}
-#endif // TOOLS_ENABLED
+#endif // DEBUG_ENABLED
RID NavigationLink2D::get_rid() const {
return link;
diff --git a/scene/2d/navigation_link_2d.h b/scene/2d/navigation_link_2d.h
index c724096607..232efdd0e5 100644
--- a/scene/2d/navigation_link_2d.h
+++ b/scene/2d/navigation_link_2d.h
@@ -62,10 +62,10 @@ protected:
#endif // DISABLE_DEPRECATED
public:
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
-#endif
+#endif // DEBUG_ENABLED
RID get_rid() const;
void set_enabled(bool p_enabled);
diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp
index 3bf90249f8..f6502a77e9 100644
--- a/scene/2d/navigation_obstacle_2d.cpp
+++ b/scene/2d/navigation_obstacle_2d.cpp
@@ -104,6 +104,7 @@ void NavigationObstacle2D::_notification(int p_what) {
#endif // DEBUG_ENABLED
} break;
+ case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (!can_process()) {
map_before_pause = map_current;
@@ -115,6 +116,13 @@ void NavigationObstacle2D::_notification(int p_what) {
NavigationServer2D::get_singleton()->obstacle_set_paused(obstacle, !can_process());
} break;
+ case NOTIFICATION_UNSUSPENDED: {
+ if (get_tree()->is_paused()) {
+ break;
+ }
+ [[fallthrough]];
+ }
+
case NOTIFICATION_UNPAUSED: {
if (!can_process()) {
map_before_pause = map_current;
diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp
index f65a3c0ecc..08fbf78c33 100644
--- a/scene/2d/navigation_region_2d.cpp
+++ b/scene/2d/navigation_region_2d.cpp
@@ -142,7 +142,7 @@ RID NavigationRegion2D::get_region_rid() const {
return get_rid();
}
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 NavigationRegion2D::_edit_get_rect() const {
return navigation_polygon.is_valid() ? navigation_polygon->_edit_get_rect() : Rect2();
}
@@ -150,7 +150,7 @@ Rect2 NavigationRegion2D::_edit_get_rect() const {
bool NavigationRegion2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
return navigation_polygon.is_valid() ? navigation_polygon->_edit_is_selected_on_click(p_point, p_tolerance) : false;
}
-#endif
+#endif // DEBUG_ENABLED
void NavigationRegion2D::_notification(int p_what) {
switch (p_what) {
diff --git a/scene/2d/navigation_region_2d.h b/scene/2d/navigation_region_2d.h
index 52101cb93e..f2bed0c44f 100644
--- a/scene/2d/navigation_region_2d.h
+++ b/scene/2d/navigation_region_2d.h
@@ -75,10 +75,10 @@ protected:
#endif // DISABLE_DEPRECATED
public:
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
-#endif
+#endif // DEBUG_ENABLED
RID get_rid() const;
void set_enabled(bool p_enabled);
diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp
index 7d645ba448..fe0cb770e0 100644
--- a/scene/2d/path_2d.cpp
+++ b/scene/2d/path_2d.cpp
@@ -37,7 +37,7 @@
#include "editor/themes/editor_scale.h"
#endif
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 Path2D::_edit_get_rect() const {
if (!curve.is_valid() || curve->get_point_count() == 0) {
return Rect2(0, 0, 0, 0);
diff --git a/scene/2d/path_2d.h b/scene/2d/path_2d.h
index bfd5cde5e9..5162f5144a 100644
--- a/scene/2d/path_2d.h
+++ b/scene/2d/path_2d.h
@@ -48,7 +48,7 @@ protected:
static void _bind_methods();
public:
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
diff --git a/scene/2d/physics/collision_polygon_2d.cpp b/scene/2d/physics/collision_polygon_2d.cpp
index b49badac1f..a3e9fcce7e 100644
--- a/scene/2d/physics/collision_polygon_2d.cpp
+++ b/scene/2d/physics/collision_polygon_2d.cpp
@@ -217,7 +217,7 @@ CollisionPolygon2D::BuildMode CollisionPolygon2D::get_build_mode() const {
return build_mode;
}
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 CollisionPolygon2D::_edit_get_rect() const {
return aabb;
}
diff --git a/scene/2d/physics/collision_polygon_2d.h b/scene/2d/physics/collision_polygon_2d.h
index f1ee30babe..93920124fe 100644
--- a/scene/2d/physics/collision_polygon_2d.h
+++ b/scene/2d/physics/collision_polygon_2d.h
@@ -65,7 +65,7 @@ protected:
static void _bind_methods();
public:
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
diff --git a/scene/2d/physics/collision_shape_2d.h b/scene/2d/physics/collision_shape_2d.h
index 65436f1539..e4f04e77e4 100644
--- a/scene/2d/physics/collision_shape_2d.h
+++ b/scene/2d/physics/collision_shape_2d.h
@@ -59,11 +59,11 @@ protected:
static void _bind_methods();
public:
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
#else
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const;
-#endif // TOOLS_ENABLED
+#endif // DEBUG_ENABLED
void set_shape(const Ref<Shape2D> &p_shape);
Ref<Shape2D> get_shape() const;
diff --git a/scene/2d/polygon_2d.cpp b/scene/2d/polygon_2d.cpp
index 42f7a75c0a..dc5a08142d 100644
--- a/scene/2d/polygon_2d.cpp
+++ b/scene/2d/polygon_2d.cpp
@@ -57,7 +57,9 @@ Point2 Polygon2D::_edit_get_pivot() const {
bool Polygon2D::_edit_use_pivot() const {
return true;
}
+#endif // TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 Polygon2D::_edit_get_rect() const {
if (rect_cache_dirty) {
int l = polygon.size();
@@ -88,7 +90,7 @@ bool Polygon2D::_edit_is_selected_on_click(const Point2 &p_point, double p_toler
}
return Geometry2D::is_point_in_polygon(p_point - get_offset(), polygon2d);
}
-#endif
+#endif // DEBUG_ENABLED
void Polygon2D::_validate_property(PropertyInfo &p_property) const {
if (!invert && p_property.name == "invert_border") {
diff --git a/scene/2d/polygon_2d.h b/scene/2d/polygon_2d.h
index b2cd06de53..0928fa150c 100644
--- a/scene/2d/polygon_2d.h
+++ b/scene/2d/polygon_2d.h
@@ -87,11 +87,14 @@ public:
virtual void _edit_set_pivot(const Point2 &p_pivot) override;
virtual Point2 _edit_get_pivot() const override;
virtual bool _edit_use_pivot() const override;
+#endif // TOOLS_ENABLED
+
+#ifdef DEBUG_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
-#endif
+#endif // DEBUG_ENABLED
void set_polygon(const Vector<Vector2> &p_polygon);
Vector<Vector2> get_polygon() const;
diff --git a/scene/2d/sprite_2d.cpp b/scene/2d/sprite_2d.cpp
index 2b5c40f212..a182f63171 100644
--- a/scene/2d/sprite_2d.cpp
+++ b/scene/2d/sprite_2d.cpp
@@ -56,7 +56,9 @@ Point2 Sprite2D::_edit_get_pivot() const {
bool Sprite2D::_edit_use_pivot() const {
return true;
}
+#endif // TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
bool Sprite2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
return is_pixel_opaque(p_point);
}
@@ -68,7 +70,7 @@ Rect2 Sprite2D::_edit_get_rect() const {
bool Sprite2D::_edit_use_rect() const {
return texture.is_valid();
}
-#endif
+#endif // DEBUG_ENABLED
Rect2 Sprite2D::get_anchorable_rect() const {
return get_rect();
diff --git a/scene/2d/sprite_2d.h b/scene/2d/sprite_2d.h
index d084e9575d..5198e5efcf 100644
--- a/scene/2d/sprite_2d.h
+++ b/scene/2d/sprite_2d.h
@@ -74,11 +74,14 @@ public:
virtual void _edit_set_pivot(const Point2 &p_pivot) override;
virtual Point2 _edit_get_pivot() const override;
virtual bool _edit_use_pivot() const override;
+#endif // TOOLS_ENABLED
+
+#ifdef DEBUG_ENABLED
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
-#endif
+#endif // DEBUG_ENABLED
bool is_pixel_opaque(const Point2 &p_point) const;
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index 45cfb8cf33..ddb0635f6b 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -89,8 +89,8 @@ void TileMap::_set_tile_map_data_using_compatibility_format(int p_layer, TileMap
SWAP(local[8], local[11]);
SWAP(local[9], local[10]);
}
-#endif
- // Extracts position in TileMap.
+#endif // BIG_ENDIAN_ENABLED
+ // Extracts position in TileMap.
int16_t x = decode_uint16(&local[0]);
int16_t y = decode_uint16(&local[2]);
@@ -172,7 +172,7 @@ void TileMap::_notification(int p_what) {
bool in_editor = false;
#ifdef TOOLS_ENABLED
in_editor = Engine::get_singleton()->is_editor_hint();
-#endif
+#endif // TOOLS_ENABLED
if (is_inside_tree() && collision_animatable && !in_editor) {
// Update transform on the physics tick when in animatable mode.
last_valid_transform = new_transform;
@@ -188,7 +188,7 @@ void TileMap::_notification(int p_what) {
bool in_editor = false;
#ifdef TOOLS_ENABLED
in_editor = Engine::get_singleton()->is_editor_hint();
-#endif
+#endif // TOOLS_ENABLED
if (is_inside_tree() && collision_animatable && !in_editor) {
// Store last valid transform.
@@ -209,7 +209,7 @@ void TileMap::force_update(int p_layer) {
notify_runtime_tile_data_update(p_layer);
update_internals();
}
-#endif
+#endif // DISABLE_DEPRECATED
void TileMap::set_rendering_quadrant_size(int p_size) {
ERR_FAIL_COND_MSG(p_size < 1, "TileMapQuadrant size cannot be smaller than 1.");
@@ -649,7 +649,7 @@ void TileMap::notify_runtime_tile_data_update(int p_layer) {
}
}
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 TileMap::_edit_get_rect() const {
// Return the visible rect of the tilemap.
if (layers.is_empty()) {
@@ -667,7 +667,7 @@ Rect2 TileMap::_edit_get_rect() const {
const_cast<TileMap *>(this)->item_rect_changed(any_changed);
return rect;
}
-#endif
+#endif // DEBUG_ENABLED
bool TileMap::_set(const StringName &p_name, const Variant &p_value) {
int index;
@@ -726,7 +726,7 @@ bool TileMap::_get(const StringName &p_name, Variant &r_ret) const {
r_ret = get_rendering_quadrant_size();
return true;
}
-#endif
+#endif // DISABLE_DEPRECATED
else {
return property_helper.property_get_value(sname, r_ret);
}
diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h
index 142dc1193f..47f5c8d996 100644
--- a/scene/2d/tile_map.h
+++ b/scene/2d/tile_map.h
@@ -107,16 +107,16 @@ protected:
VisibilityMode _get_navigation_visibility_mode_bind_compat_87115();
static void _bind_compatibility_methods();
-#endif
+#endif // DISABLE_DEPRECATED
public:
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
virtual Rect2 _edit_get_rect() const override;
-#endif
+#endif // DEBUG_ENABLED
#ifndef DISABLE_DEPRECATED
void force_update(int p_layer);
-#endif
+#endif // DISABLE_DEPRECATED
void set_rendering_quadrant_size(int p_size);
int get_rendering_quadrant_size() const;
diff --git a/scene/2d/touch_screen_button.cpp b/scene/2d/touch_screen_button.cpp
index ff409272c5..b968f39ccb 100644
--- a/scene/2d/touch_screen_button.cpp
+++ b/scene/2d/touch_screen_button.cpp
@@ -185,6 +185,7 @@ void TouchScreenButton::_notification(int p_what) {
}
} break;
+ case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (is_pressed()) {
_release();
@@ -329,7 +330,7 @@ void TouchScreenButton::_release(bool p_exiting_tree) {
}
}
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 TouchScreenButton::_edit_get_rect() const {
if (texture_normal.is_null()) {
return CanvasItem::_edit_get_rect();
@@ -341,7 +342,7 @@ Rect2 TouchScreenButton::_edit_get_rect() const {
bool TouchScreenButton::_edit_use_rect() const {
return !texture_normal.is_null();
}
-#endif
+#endif // DEBUG_ENABLED
Rect2 TouchScreenButton::get_anchorable_rect() const {
if (texture_normal.is_null()) {
diff --git a/scene/2d/touch_screen_button.h b/scene/2d/touch_screen_button.h
index 4467604e2b..da75fabfa0 100644
--- a/scene/2d/touch_screen_button.h
+++ b/scene/2d/touch_screen_button.h
@@ -76,10 +76,10 @@ protected:
#endif // DISABLE_DEPRECATED
public:
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
-#endif
+#endif // DEBUG_ENABLED
void set_texture_normal(const Ref<Texture2D> &p_texture);
Ref<Texture2D> get_texture_normal() const;
diff --git a/scene/2d/visible_on_screen_notifier_2d.cpp b/scene/2d/visible_on_screen_notifier_2d.cpp
index c64507fe32..7bf6371ee9 100644
--- a/scene/2d/visible_on_screen_notifier_2d.cpp
+++ b/scene/2d/visible_on_screen_notifier_2d.cpp
@@ -30,7 +30,7 @@
#include "visible_on_screen_notifier_2d.h"
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 VisibleOnScreenNotifier2D::_edit_get_rect() const {
return rect;
}
@@ -38,7 +38,7 @@ Rect2 VisibleOnScreenNotifier2D::_edit_get_rect() const {
bool VisibleOnScreenNotifier2D::_edit_use_rect() const {
return true;
}
-#endif
+#endif // DEBUG_ENABLED
void VisibleOnScreenNotifier2D::_visibility_enter() {
if (!is_inside_tree() || Engine::get_singleton()->is_editor_hint()) {
diff --git a/scene/2d/visible_on_screen_notifier_2d.h b/scene/2d/visible_on_screen_notifier_2d.h
index df2832a976..84545a2d6a 100644
--- a/scene/2d/visible_on_screen_notifier_2d.h
+++ b/scene/2d/visible_on_screen_notifier_2d.h
@@ -54,10 +54,10 @@ protected:
static void _bind_methods();
public:
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
-#endif
+#endif // DEBUG_ENABLED
void set_rect(const Rect2 &p_rect);
Rect2 get_rect() const;
diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp
index aafc2141af..2b841c4e0d 100644
--- a/scene/3d/camera_3d.cpp
+++ b/scene/3d/camera_3d.cpp
@@ -234,6 +234,7 @@ void Camera3D::_notification(int p_what) {
}
} break;
+ case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (is_physics_interpolated_and_enabled() && is_inside_tree() && is_visible_in_tree()) {
_physics_interpolation_ensure_transform_calculated(true);
diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp
index 2cef607d29..b48a3a87c7 100644
--- a/scene/3d/gpu_particles_3d.cpp
+++ b/scene/3d/gpu_particles_3d.cpp
@@ -511,6 +511,8 @@ void GPUParticles3D::_notification(int p_what) {
RS::get_singleton()->particles_set_subemitter(particles, RID());
} break;
+ case NOTIFICATION_SUSPENDED:
+ case NOTIFICATION_UNSUSPENDED:
case NOTIFICATION_PAUSED:
case NOTIFICATION_UNPAUSED: {
if (is_inside_tree()) {
diff --git a/scene/3d/navigation_agent_3d.cpp b/scene/3d/navigation_agent_3d.cpp
index 5bbb724e2f..9242c2a2ea 100644
--- a/scene/3d/navigation_agent_3d.cpp
+++ b/scene/3d/navigation_agent_3d.cpp
@@ -272,12 +272,20 @@ void NavigationAgent3D::_notification(int p_what) {
#endif // DEBUG_ENABLED
} break;
+ case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (agent_parent) {
NavigationServer3D::get_singleton()->agent_set_paused(get_rid(), !agent_parent->can_process());
}
} break;
+ case NOTIFICATION_UNSUSPENDED: {
+ if (get_tree()->is_paused()) {
+ break;
+ }
+ [[fallthrough]];
+ }
+
case NOTIFICATION_UNPAUSED: {
if (agent_parent) {
NavigationServer3D::get_singleton()->agent_set_paused(get_rid(), !agent_parent->can_process());
diff --git a/scene/3d/navigation_obstacle_3d.cpp b/scene/3d/navigation_obstacle_3d.cpp
index f2ac8f789c..2eb04a0054 100644
--- a/scene/3d/navigation_obstacle_3d.cpp
+++ b/scene/3d/navigation_obstacle_3d.cpp
@@ -119,6 +119,7 @@ void NavigationObstacle3D::_notification(int p_what) {
#endif // DEBUG_ENABLED
} break;
+ case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (!can_process()) {
map_before_pause = map_current;
@@ -130,6 +131,13 @@ void NavigationObstacle3D::_notification(int p_what) {
NavigationServer3D::get_singleton()->obstacle_set_paused(obstacle, !can_process());
} break;
+ case NOTIFICATION_UNSUSPENDED: {
+ if (get_tree()->is_paused()) {
+ break;
+ }
+ [[fallthrough]];
+ }
+
case NOTIFICATION_UNPAUSED: {
if (!can_process()) {
map_before_pause = map_current;
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/audio/audio_stream_player_internal.cpp b/scene/audio/audio_stream_player_internal.cpp
index 7d1ed56ca8..621aa31fc6 100644
--- a/scene/audio/audio_stream_player_internal.cpp
+++ b/scene/audio/audio_stream_player_internal.cpp
@@ -112,6 +112,7 @@ void AudioStreamPlayerInternal::notification(int p_what) {
stream_playbacks.clear();
} break;
+ case Node::NOTIFICATION_SUSPENDED:
case Node::NOTIFICATION_PAUSED: {
if (!node->can_process()) {
// Node can't process so we start fading out to silence
@@ -119,6 +120,13 @@ void AudioStreamPlayerInternal::notification(int p_what) {
}
} break;
+ case Node::NOTIFICATION_UNSUSPENDED: {
+ if (node->get_tree()->is_paused()) {
+ break;
+ }
+ [[fallthrough]];
+ }
+
case Node::NOTIFICATION_UNPAUSED: {
set_stream_paused(false);
} break;
diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp
index 22e5238fae..b87285ed74 100644
--- a/scene/debugger/scene_debugger.cpp
+++ b/scene/debugger/scene_debugger.cpp
@@ -31,20 +31,34 @@
#include "scene_debugger.h"
#include "core/debugger/engine_debugger.h"
-#include "core/debugger/engine_profiler.h"
#include "core/io/marshalls.h"
#include "core/object/script_language.h"
#include "core/templates/local_vector.h"
+#include "scene/2d/physics/collision_object_2d.h"
+#include "scene/2d/physics/collision_polygon_2d.h"
+#include "scene/2d/physics/collision_shape_2d.h"
+#ifndef _3D_DISABLED
+#include "scene/3d/label_3d.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/3d/physics/collision_object_3d.h"
+#include "scene/3d/physics/collision_shape_3d.h"
+#include "scene/3d/sprite_3d.h"
+#include "scene/resources/surface_tool.h"
+#endif // _3D_DISABLED
+#include "scene/gui/popup_menu.h"
+#include "scene/main/canvas_layer.h"
#include "scene/main/scene_tree.h"
#include "scene/main/window.h"
#include "scene/resources/packed_scene.h"
-
-SceneDebugger *SceneDebugger::singleton = nullptr;
+#include "scene/theme/theme_db.h"
SceneDebugger::SceneDebugger() {
singleton = this;
+
#ifdef DEBUG_ENABLED
LiveEditor::singleton = memnew(LiveEditor);
+ RuntimeNodeSelect::singleton = memnew(RuntimeNodeSelect);
+
EngineDebugger::register_message_capture("scene", EngineDebugger::Capture(nullptr, SceneDebugger::parse_message));
#endif
}
@@ -56,7 +70,13 @@ SceneDebugger::~SceneDebugger() {
memdelete(LiveEditor::singleton);
LiveEditor::singleton = nullptr;
}
+
+ if (RuntimeNodeSelect::singleton) {
+ memdelete(RuntimeNodeSelect::singleton);
+ RuntimeNodeSelect::singleton = nullptr;
+ }
#endif
+
singleton = nullptr;
}
@@ -78,10 +98,15 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra
if (!scene_tree) {
return ERR_UNCONFIGURED;
}
+
LiveEditor *live_editor = LiveEditor::get_singleton();
if (!live_editor) {
return ERR_UNCONFIGURED;
}
+ RuntimeNodeSelect *runtime_node_select = RuntimeNodeSelect::get_singleton();
+ if (!runtime_node_select) {
+ return ERR_UNCONFIGURED;
+ }
r_captured = true;
if (p_msg == "request_scene_tree") { // Scene tree
@@ -99,22 +124,34 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra
ObjectID id = p_args[0];
_send_object_id(id);
- } else if (p_msg == "override_camera_2D:set") { // Camera
+ } else if (p_msg == "suspend_changed") {
ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
- bool enforce = p_args[0];
- scene_tree->get_root()->enable_canvas_transform_override(enforce);
+ bool suspended = p_args[0];
+ scene_tree->set_suspend(suspended);
+ runtime_node_select->_update_input_state();
- } else if (p_msg == "override_camera_2D:transform") {
- 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);
-#ifndef _3D_DISABLED
- } else if (p_msg == "override_camera_3D:set") {
+ } else if (p_msg == "next_frame") {
+ _next_frame();
+
+ } else if (p_msg == "override_cameras") { // Camera
ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
bool enable = p_args[0];
+ bool from_editor = p_args[1];
+ scene_tree->get_root()->enable_canvas_transform_override(enable);
+#ifndef _3D_DISABLED
scene_tree->get_root()->enable_camera_3d_override(enable);
+#endif // _3D_DISABLED
+ runtime_node_select->_set_camera_override_enabled(enable && !from_editor);
- } else if (p_msg == "override_camera_3D:transform") {
+ } else if (p_msg == "transform_camera_2d") {
+ 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
+ } else if (p_msg == "transform_camera_3d") {
ERR_FAIL_COND_V(p_args.size() < 5, ERR_INVALID_DATA);
Transform3D transform = p_args[0];
bool is_perspective = p_args[1];
@@ -127,95 +164,142 @@ 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]);
- } else if (!p_msg.begins_with("live_")) { // Live edits below.
- return ERR_SKIP;
- } else if (p_msg == "live_set_root") {
- ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
- live_editor->_root_func(p_args[0], p_args[1]);
+ runtime_node_select->_queue_selection_update();
+
+ } 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);
+ live_editor->_root_func(p_args[0], p_args[1]);
+
+ } else if (p_msg == "live_node_path") {
+ ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
+ live_editor->_node_path_func(p_args[0], p_args[1]);
+
+ } else if (p_msg == "live_res_path") {
+ ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
+ live_editor->_res_path_func(p_args[0], p_args[1]);
+
+ } else if (p_msg == "live_node_prop_res") {
+ ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
+ live_editor->_node_set_res_func(p_args[0], p_args[1], p_args[2]);
+
+ } else if (p_msg == "live_node_prop") {
+ ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
+ live_editor->_node_set_func(p_args[0], p_args[1], p_args[2]);
+
+ } else if (p_msg == "live_res_prop_res") {
+ ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
+ live_editor->_res_set_res_func(p_args[0], p_args[1], p_args[2]);
+
+ } else if (p_msg == "live_res_prop") {
+ ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
+ live_editor->_res_set_func(p_args[0], p_args[1], p_args[2]);
+
+ } else if (p_msg == "live_node_call") {
+ ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
+ LocalVector<Variant> args;
+ LocalVector<Variant *> argptrs;
+ args.resize(p_args.size() - 2);
+ argptrs.resize(args.size());
+ for (uint32_t i = 0; i < args.size(); i++) {
+ args[i] = p_args[i + 2];
+ argptrs[i] = &args[i];
+ }
+ live_editor->_node_call_func(p_args[0], p_args[1], argptrs.size() ? (const Variant **)argptrs.ptr() : nullptr, argptrs.size());
+
+ } else if (p_msg == "live_res_call") {
+ ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
+ LocalVector<Variant> args;
+ LocalVector<Variant *> argptrs;
+ args.resize(p_args.size() - 2);
+ argptrs.resize(args.size());
+ for (uint32_t i = 0; i < args.size(); i++) {
+ args[i] = p_args[i + 2];
+ argptrs[i] = &args[i];
+ }
+ live_editor->_res_call_func(p_args[0], p_args[1], argptrs.size() ? (const Variant **)argptrs.ptr() : nullptr, argptrs.size());
- } else if (p_msg == "live_node_path") {
- ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
- live_editor->_node_path_func(p_args[0], p_args[1]);
+ } else if (p_msg == "live_create_node") {
+ ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
+ live_editor->_create_node_func(p_args[0], p_args[1], p_args[2]);
- } else if (p_msg == "live_res_path") {
- ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
- live_editor->_res_path_func(p_args[0], p_args[1]);
+ } else if (p_msg == "live_instantiate_node") {
+ ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
+ live_editor->_instance_node_func(p_args[0], p_args[1], p_args[2]);
- } else if (p_msg == "live_node_prop_res") {
- ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
- live_editor->_node_set_res_func(p_args[0], p_args[1], p_args[2]);
+ } else if (p_msg == "live_remove_node") {
+ ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
+ live_editor->_remove_node_func(p_args[0]);
- } else if (p_msg == "live_node_prop") {
- ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
- live_editor->_node_set_func(p_args[0], p_args[1], p_args[2]);
+ if (!runtime_node_select->has_selection) {
+ runtime_node_select->_clear_selection();
+ }
- } else if (p_msg == "live_res_prop_res") {
- ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
- live_editor->_res_set_res_func(p_args[0], p_args[1], p_args[2]);
+ } else if (p_msg == "live_remove_and_keep_node") {
+ ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
+ live_editor->_remove_and_keep_node_func(p_args[0], p_args[1]);
- } else if (p_msg == "live_res_prop") {
- ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
- live_editor->_res_set_func(p_args[0], p_args[1], p_args[2]);
+ if (!runtime_node_select->has_selection) {
+ runtime_node_select->_clear_selection();
+ }
- } else if (p_msg == "live_node_call") {
- ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
- LocalVector<Variant> args;
- LocalVector<Variant *> argptrs;
- args.resize(p_args.size() - 2);
- argptrs.resize(args.size());
- for (uint32_t i = 0; i < args.size(); i++) {
- args[i] = p_args[i + 2];
- argptrs[i] = &args[i];
- }
- live_editor->_node_call_func(p_args[0], p_args[1], argptrs.size() ? (const Variant **)argptrs.ptr() : nullptr, argptrs.size());
+ } else if (p_msg == "live_restore_node") {
+ ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
+ live_editor->_restore_node_func(p_args[0], p_args[1], p_args[2]);
- } else if (p_msg == "live_res_call") {
- ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
- LocalVector<Variant> args;
- LocalVector<Variant *> argptrs;
- args.resize(p_args.size() - 2);
- argptrs.resize(args.size());
- for (uint32_t i = 0; i < args.size(); i++) {
- args[i] = p_args[i + 2];
- argptrs[i] = &args[i];
+ } else if (p_msg == "live_duplicate_node") {
+ ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
+ live_editor->_duplicate_node_func(p_args[0], p_args[1]);
+
+ } else if (p_msg == "live_reparent_node") {
+ ERR_FAIL_COND_V(p_args.size() < 4, ERR_INVALID_DATA);
+ live_editor->_reparent_node_func(p_args[0], p_args[1], p_args[2], p_args[3]);
+
+ } else {
+ return ERR_SKIP;
}
- live_editor->_res_call_func(p_args[0], p_args[1], argptrs.size() ? (const Variant **)argptrs.ptr() : nullptr, argptrs.size());
- } else if (p_msg == "live_create_node") {
- ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
- live_editor->_create_node_func(p_args[0], p_args[1], p_args[2]);
+ } else if (p_msg.begins_with("runtime_node_select_")) { /// Runtime Node Selection
+ if (p_msg == "runtime_node_select_setup") {
+ runtime_node_select->_setup();
- } else if (p_msg == "live_instantiate_node") {
- ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
- live_editor->_instance_node_func(p_args[0], p_args[1], p_args[2]);
+ } else if (p_msg == "runtime_node_select_set_type") {
+ ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
+ RuntimeNodeSelect::NodeType type = (RuntimeNodeSelect::NodeType)(int)p_args[0];
+ runtime_node_select->_node_set_type(type);
- } else if (p_msg == "live_remove_node") {
- ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
- live_editor->_remove_node_func(p_args[0]);
+ } else if (p_msg == "runtime_node_select_set_mode") {
+ ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
+ RuntimeNodeSelect::SelectMode mode = (RuntimeNodeSelect::SelectMode)(int)p_args[0];
+ runtime_node_select->_select_set_mode(mode);
- } else if (p_msg == "live_remove_and_keep_node") {
- ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
- live_editor->_remove_and_keep_node_func(p_args[0], p_args[1]);
+ } else if (p_msg == "runtime_node_select_set_visible") {
+ ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
+ bool visible = p_args[0];
+ runtime_node_select->_set_selection_visible(visible);
- } else if (p_msg == "live_restore_node") {
- ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
- live_editor->_restore_node_func(p_args[0], p_args[1], p_args[2]);
+ } else if (p_msg == "runtime_node_select_reset_camera_2d") {
+ runtime_node_select->_reset_camera_2d();
- } else if (p_msg == "live_duplicate_node") {
- ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
- live_editor->_duplicate_node_func(p_args[0], p_args[1]);
+ } else if (p_msg == "runtime_node_select_reset_camera_3d") {
+ runtime_node_select->_reset_camera_3d();
+
+ } else {
+ return ERR_SKIP;
+ }
- } else if (p_msg == "live_reparent_node") {
- ERR_FAIL_COND_V(p_args.size() < 4, ERR_INVALID_DATA);
- live_editor->_reparent_node_func(p_args[0], p_args[1], p_args[2], p_args[3]);
} else {
r_captured = false;
}
+
return OK;
}
@@ -260,6 +344,9 @@ void SceneDebugger::_send_object_id(ObjectID p_id, int p_max_size) {
return;
}
+ Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id));
+ RuntimeNodeSelect::get_singleton()->_select_node(node);
+
Array arr;
obj.serialize(arr);
EngineDebugger::get_singleton()->send_message("scene:inspect_object", arr);
@@ -280,6 +367,16 @@ void SceneDebugger::_set_object_property(ObjectID p_id, const String &p_property
obj->set(prop_name, p_value);
}
+void SceneDebugger::_next_frame() {
+ SceneTree *scene_tree = SceneTree::get_singleton();
+ if (!scene_tree->is_suspended()) {
+ return;
+ }
+
+ scene_tree->set_suspend(false);
+ RenderingServer::get_singleton()->connect("frame_post_draw", callable_mp(scene_tree, &SceneTree::set_suspend).bind(true), Object::CONNECT_ONE_SHOT);
+}
+
void SceneDebugger::add_to_cache(const String &p_filename, Node *p_node) {
LiveEditor *debugger = LiveEditor::get_singleton();
if (!debugger) {
@@ -580,7 +677,6 @@ void SceneDebuggerTree::deserialize(const Array &p_arr) {
}
/// LiveEditor
-LiveEditor *LiveEditor::singleton = nullptr;
LiveEditor *LiveEditor::get_singleton() {
return singleton;
}
@@ -1089,4 +1185,942 @@ void LiveEditor::_reparent_node_func(const NodePath &p_at, const NodePath &p_new
}
}
+/// RuntimeNodeSelect
+RuntimeNodeSelect *RuntimeNodeSelect::get_singleton() {
+ return singleton;
+}
+
+RuntimeNodeSelect::~RuntimeNodeSelect() {
+ if (selection_list && !selection_list->is_visible()) {
+ memdelete(selection_list);
+ }
+
+ if (sbox_2d_canvas.is_valid()) {
+ RS::get_singleton()->free(sbox_2d_canvas);
+ RS::get_singleton()->free(sbox_2d_ci);
+ }
+
+#ifndef _3D_DISABLED
+ if (sbox_3d_instance.is_valid()) {
+ RS::get_singleton()->free(sbox_3d_instance);
+ RS::get_singleton()->free(sbox_3d_instance_ofs);
+ RS::get_singleton()->free(sbox_3d_instance_xray);
+ RS::get_singleton()->free(sbox_3d_instance_xray_ofs);
+ }
+#endif // _3D_DISABLED
+}
+
+void RuntimeNodeSelect::_setup() {
+ Window *root = SceneTree::get_singleton()->get_root();
+ ERR_FAIL_COND(root->is_connected(SceneStringName(window_input), callable_mp(this, &RuntimeNodeSelect::_root_window_input)));
+
+ root->connect(SceneStringName(window_input), callable_mp(this, &RuntimeNodeSelect::_root_window_input));
+ root->connect("size_changed", callable_mp(this, &RuntimeNodeSelect::_queue_selection_update), CONNECT_DEFERRED);
+
+ selection_list = memnew(PopupMenu);
+ selection_list->set_theme(ThemeDB::get_singleton()->get_default_theme());
+ selection_list->set_auto_translate_mode(Node::AUTO_TRANSLATE_MODE_DISABLED);
+ selection_list->set_force_native(true);
+ selection_list->connect("index_pressed", callable_mp(this, &RuntimeNodeSelect::_items_popup_index_pressed).bind(selection_list));
+ selection_list->connect("popup_hide", callable_mp(Object::cast_to<Node>(root), &Node::remove_child).bind(selection_list));
+
+ panner.instantiate();
+ panner->set_callbacks(callable_mp(this, &RuntimeNodeSelect::_pan_callback), callable_mp(this, &RuntimeNodeSelect::_zoom_callback));
+
+ /// 2D Selection Box Generation
+
+ sbox_2d_canvas = RS::get_singleton()->canvas_create();
+ sbox_2d_ci = RS::get_singleton()->canvas_item_create();
+ RS::get_singleton()->viewport_attach_canvas(root->get_viewport_rid(), sbox_2d_canvas);
+ RS::get_singleton()->canvas_item_set_parent(sbox_2d_ci, sbox_2d_canvas);
+
+#ifndef _3D_DISABLED
+ cursor = Cursor();
+
+ /// 3D Selection Box Generation
+ // Copied from the Node3DEditor implementation.
+
+ // Use two AABBs to create the illusion of a slightly thicker line.
+ AABB aabb(Vector3(), Vector3(1, 1, 1));
+
+ // Create a x-ray (visible through solid surfaces) and standard version of the selection box.
+ // Both will be drawn at the same position, but with different opacity.
+ // This lets the user see where the selection is while still having a sense of depth.
+ Ref<SurfaceTool> st = memnew(SurfaceTool);
+ Ref<SurfaceTool> st_xray = memnew(SurfaceTool);
+
+ st->begin(Mesh::PRIMITIVE_LINES);
+ st_xray->begin(Mesh::PRIMITIVE_LINES);
+ for (int i = 0; i < 12; i++) {
+ Vector3 a, b;
+ aabb.get_edge(i, a, b);
+
+ st->add_vertex(a);
+ st->add_vertex(b);
+ st_xray->add_vertex(a);
+ st_xray->add_vertex(b);
+ }
+
+ Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);
+ mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
+ // In the original Node3DEditor, this value would be fetched from the "editors/3d/selection_box_color" editor property,
+ // but since this is not accessible from here, we will just use the default value.
+ const Color selection_color_3d = Color(1, 0.5, 0);
+ mat->set_albedo(selection_color_3d);
+ mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+ st->set_material(mat);
+ sbox_3d_mesh = st->commit();
+
+ Ref<StandardMaterial3D> mat_xray = memnew(StandardMaterial3D);
+ mat_xray->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
+ mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);
+ mat_xray->set_albedo(selection_color_3d * Color(1, 1, 1, 0.15));
+ mat_xray->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+ st_xray->set_material(mat_xray);
+ sbox_3d_mesh_xray = st_xray->commit();
+#endif // _3D_DISABLED
+
+ SceneTree::get_singleton()->connect("process_frame", callable_mp(this, &RuntimeNodeSelect::_process_frame));
+ SceneTree::get_singleton()->connect("physics_frame", callable_mp(this, &RuntimeNodeSelect::_physics_frame));
+
+ // This function will be called before the root enters the tree at first when the Game view is passing its settings to
+ // the debugger, so queue the update for after it enters.
+ root->connect(SceneStringName(tree_entered), callable_mp(this, &RuntimeNodeSelect::_update_input_state), Object::CONNECT_ONE_SHOT);
+}
+
+void RuntimeNodeSelect::_node_set_type(NodeType p_type) {
+ node_select_type = p_type;
+ _update_input_state();
+}
+
+void RuntimeNodeSelect::_select_set_mode(SelectMode p_mode) {
+ node_select_mode = p_mode;
+}
+
+void RuntimeNodeSelect::_set_camera_override_enabled(bool p_enabled) {
+ camera_override = p_enabled;
+
+ if (p_enabled) {
+ _update_view_2d();
+ }
+
+#ifndef _3D_DISABLED
+ if (camera_first_override) {
+ _reset_camera_2d();
+ _reset_camera_3d();
+
+ camera_first_override = false;
+ } else if (p_enabled) {
+ _update_view_2d();
+
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform());
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_perspective(CAMERA_BASE_FOV * cursor.fov_scale, CAMERA_ZNEAR, CAMERA_ZFAR);
+ }
+#endif // _3D_DISABLED
+}
+
+void RuntimeNodeSelect::_root_window_input(const Ref<InputEvent> &p_event) {
+ Window *root = SceneTree::get_singleton()->get_root();
+ if (node_select_type == NODE_TYPE_NONE || selection_list->is_visible()) {
+ // Workaround for platforms that don't allow subwindows.
+ if (selection_list->is_visible() && selection_list->is_embedded()) {
+ root->set_disable_input_override(false);
+ selection_list->push_input(p_event);
+ callable_mp(root->get_viewport(), &Viewport::set_disable_input_override).call_deferred(true);
+ }
+
+ return;
+ }
+
+ if (camera_override) {
+ if (node_select_type == NODE_TYPE_2D) {
+ if (panner->gui_input(p_event, Rect2(Vector2(), root->get_size()))) {
+ return;
+ }
+ } else if (node_select_type == NODE_TYPE_3D) {
+#ifndef _3D_DISABLED
+ if (root->get_camera_3d() && _handle_3d_input(p_event)) {
+ return;
+ }
+#endif // _3D_DISABLED
+ }
+ }
+
+ Ref<InputEventMouseButton> b = p_event;
+ if (!b.is_valid() || !b->is_pressed()) {
+ return;
+ }
+
+ list_shortcut_pressed = node_select_mode == SELECT_MODE_SINGLE && b->get_button_index() == MouseButton::RIGHT && b->is_alt_pressed();
+ if (list_shortcut_pressed || b->get_button_index() == MouseButton::LEFT) {
+ selection_position = b->get_position();
+ }
+}
+
+void RuntimeNodeSelect::_items_popup_index_pressed(int p_index, PopupMenu *p_popup) {
+ Object *obj = p_popup->get_item_metadata(p_index).get_validated_object();
+ if (!obj) {
+ return;
+ }
+
+ Array message;
+ message.append(obj->get_instance_id());
+ EngineDebugger::get_singleton()->send_message("remote_node_clicked", message);
+}
+
+void RuntimeNodeSelect::_update_input_state() {
+ SceneTree *scene_tree = SceneTree::get_singleton();
+ // This function can be called at the very beginning, when the root hasn't entered the tree yet.
+ // So check first to avoid a crash.
+ if (!scene_tree->get_root()->is_inside_tree()) {
+ return;
+ }
+
+ bool disable_input = scene_tree->is_suspended() || node_select_type != RuntimeNodeSelect::NODE_TYPE_NONE;
+ Input::get_singleton()->set_disable_input(disable_input);
+ Input::get_singleton()->set_mouse_mode_override_enabled(disable_input);
+ scene_tree->get_root()->set_disable_input_override(disable_input);
+}
+
+void RuntimeNodeSelect::_process_frame() {
+#ifndef _3D_DISABLED
+ if (camera_freelook) {
+ Transform3D transform = _get_cursor_transform();
+ Vector3 forward = transform.basis.xform(Vector3(0, 0, -1));
+ const Vector3 right = transform.basis.xform(Vector3(1, 0, 0));
+ Vector3 up = transform.basis.xform(Vector3(0, 1, 0));
+
+ Vector3 direction;
+
+ Input *input = Input::get_singleton();
+ bool was_input_disabled = input->is_input_disabled();
+ if (was_input_disabled) {
+ input->set_disable_input(false);
+ }
+
+ if (input->is_physical_key_pressed(Key::A)) {
+ direction -= right;
+ }
+ if (input->is_physical_key_pressed(Key::D)) {
+ direction += right;
+ }
+ if (input->is_physical_key_pressed(Key::W)) {
+ direction += forward;
+ }
+ if (input->is_physical_key_pressed(Key::S)) {
+ direction -= forward;
+ }
+ if (input->is_physical_key_pressed(Key::E)) {
+ direction += up;
+ }
+ if (input->is_physical_key_pressed(Key::Q)) {
+ direction -= up;
+ }
+
+ real_t speed = FREELOOK_BASE_SPEED;
+ if (input->is_physical_key_pressed(Key::SHIFT)) {
+ speed *= 3.0;
+ }
+ if (input->is_physical_key_pressed(Key::ALT)) {
+ speed *= 0.333333;
+ }
+
+ if (was_input_disabled) {
+ input->set_disable_input(true);
+ }
+
+ if (direction != Vector3()) {
+ // Calculate the process time manually, as the time scale is frozen.
+ const double process_time = (1.0 / Engine::get_singleton()->get_frames_per_second()) * Engine::get_singleton()->get_unfrozen_time_scale();
+ const Vector3 motion = direction * speed * process_time;
+ cursor.pos += motion;
+ cursor.eye_pos += motion;
+
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform());
+ }
+ }
+#endif // _3D_DISABLED
+
+ if (selection_update_queued || !SceneTree::get_singleton()->is_suspended()) {
+ selection_update_queued = false;
+ if (has_selection) {
+ _update_selection();
+ }
+ }
+}
+
+void RuntimeNodeSelect::_physics_frame() {
+ if (!Math::is_inf(selection_position.x) || !Math::is_inf(selection_position.y)) {
+ _click_point();
+ selection_position = Point2(INFINITY, INFINITY);
+ }
+}
+
+void RuntimeNodeSelect::_click_point() {
+ Window *root = SceneTree::get_singleton()->get_root();
+ Point2 pos = root->get_screen_transform().affine_inverse().xform(selection_position);
+ Vector<SelectResult> items;
+
+ if (node_select_type == NODE_TYPE_2D) {
+ for (int i = 0; i < root->get_child_count(); i++) {
+ _find_canvas_items_at_pos(pos, root->get_child(i), items);
+ }
+
+ // Remove possible duplicates.
+ for (int i = 0; i < items.size(); i++) {
+ Node *item = items[i].item;
+ for (int j = 0; j < i; j++) {
+ if (items[j].item == item) {
+ items.remove_at(i);
+ i--;
+
+ break;
+ }
+ }
+ }
+ } else if (node_select_type == NODE_TYPE_3D) {
+#ifndef _3D_DISABLED
+ _find_3d_items_at_pos(pos, items);
+#endif // _3D_DISABLED
+ }
+
+ if (items.is_empty()) {
+ return;
+ }
+
+ items.sort();
+
+ if ((!list_shortcut_pressed && node_select_mode == SELECT_MODE_SINGLE) || items.size() == 1) {
+ Array message;
+ message.append(items[0].item->get_instance_id());
+ EngineDebugger::get_singleton()->send_message("remote_node_clicked", message);
+ } else if (list_shortcut_pressed || node_select_mode == SELECT_MODE_LIST) {
+ if (!selection_list->is_inside_tree()) {
+ root->add_child(selection_list);
+ }
+
+ selection_list->clear();
+ for (const SelectResult &I : items) {
+ selection_list->add_item(I.item->get_name());
+ selection_list->set_item_metadata(-1, I.item);
+ }
+
+ selection_list->set_position(selection_list->is_embedded() ? pos : selection_position + root->get_position());
+ selection_list->reset_size();
+ selection_list->popup();
+ // FIXME: Ugly hack that stops the popup from hiding when the button is released.
+ selection_list->call_deferred(SNAME("set_position"), selection_list->get_position() + Point2(1, 0));
+ }
+}
+
+void RuntimeNodeSelect::_select_node(Node *p_node) {
+ if (p_node == selected_node) {
+ return;
+ }
+
+ _clear_selection();
+
+ CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);
+ if (ci) {
+ selected_node = p_node;
+ } else {
+#ifndef _3D_DISABLED
+ Node3D *node_3d = Object::cast_to<Node3D>(p_node);
+ if (node_3d) {
+ if (!node_3d->is_inside_world()) {
+ return;
+ }
+
+ selected_node = p_node;
+
+ sbox_3d_instance = RS::get_singleton()->instance_create2(sbox_3d_mesh->get_rid(), node_3d->get_world_3d()->get_scenario());
+ sbox_3d_instance_ofs = RS::get_singleton()->instance_create2(sbox_3d_mesh->get_rid(), node_3d->get_world_3d()->get_scenario());
+ RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sbox_3d_instance, RS::SHADOW_CASTING_SETTING_OFF);
+ RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sbox_3d_instance_ofs, RS::SHADOW_CASTING_SETTING_OFF);
+ RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
+ RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
+ RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance_ofs, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
+ RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance_ofs, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
+
+ sbox_3d_instance_xray = RS::get_singleton()->instance_create2(sbox_3d_mesh_xray->get_rid(), node_3d->get_world_3d()->get_scenario());
+ sbox_3d_instance_xray_ofs = RS::get_singleton()->instance_create2(sbox_3d_mesh_xray->get_rid(), node_3d->get_world_3d()->get_scenario());
+ RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sbox_3d_instance_xray, RS::SHADOW_CASTING_SETTING_OFF);
+ RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sbox_3d_instance_xray_ofs, RS::SHADOW_CASTING_SETTING_OFF);
+ RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance_xray, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
+ RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance_xray, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
+ RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance_xray_ofs, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
+ RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance_xray_ofs, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
+ }
+#endif // _3D_DISABLED
+ }
+
+ has_selection = selected_node;
+ _queue_selection_update();
+}
+
+void RuntimeNodeSelect::_queue_selection_update() {
+ if (has_selection && selection_visible) {
+ if (SceneTree::get_singleton()->is_suspended()) {
+ _update_selection();
+ } else {
+ selection_update_queued = true;
+ }
+ }
+}
+
+void RuntimeNodeSelect::_update_selection() {
+ if (has_selection && (!selected_node || !selected_node->is_inside_tree())) {
+ _clear_selection();
+ return;
+ }
+
+ CanvasItem *ci = Object::cast_to<CanvasItem>(selected_node);
+ if (ci) {
+ Window *root = SceneTree::get_singleton()->get_root();
+ Transform2D xform;
+ if (root->is_canvas_transform_override_enabled() && !ci->get_canvas_layer_node()) {
+ RS::get_singleton()->canvas_item_set_transform(sbox_2d_ci, (root->get_canvas_transform_override()));
+ xform = ci->get_global_transform();
+ } else {
+ RS::get_singleton()->canvas_item_set_transform(sbox_2d_ci, Transform2D());
+ xform = ci->get_global_transform_with_canvas();
+ }
+
+ // Fallback.
+ Rect2 rect = Rect2(Vector2(), Vector2(10, 10));
+
+ if (ci->_edit_use_rect()) {
+ rect = ci->_edit_get_rect();
+ } else {
+ CollisionShape2D *collision_shape = Object::cast_to<CollisionShape2D>(ci);
+ if (collision_shape) {
+ Ref<Shape2D> shape = collision_shape->get_shape();
+ if (shape.is_valid()) {
+ rect = shape->get_rect();
+ }
+ }
+ }
+
+ RS::get_singleton()->canvas_item_set_visible(sbox_2d_ci, selection_visible);
+
+ if (xform == sbox_2d_xform && rect == sbox_2d_rect) {
+ return; // Nothing changed.
+ }
+ sbox_2d_xform = xform;
+ sbox_2d_rect = rect;
+
+ RS::get_singleton()->canvas_item_clear(sbox_2d_ci);
+
+ const Vector2 endpoints[4] = {
+ xform.xform(rect.position),
+ xform.xform(rect.position + Vector2(rect.size.x, 0)),
+ xform.xform(rect.position + rect.size),
+ xform.xform(rect.position + Vector2(0, rect.size.y))
+ };
+
+ const Color selection_color_2d = Color(1, 0.6, 0.4, 0.7);
+ for (int i = 0; i < 4; i++) {
+ RS::get_singleton()->canvas_item_add_line(sbox_2d_ci, endpoints[i], endpoints[(i + 1) % 4], selection_color_2d, Math::round(2.f));
+ }
+ } else {
+#ifndef _3D_DISABLED
+ Node3D *node_3d = Object::cast_to<Node3D>(selected_node);
+
+ // Fallback.
+ AABB bounds(Vector3(-0.5, -0.5, -0.5), Vector3(1, 1, 1));
+
+ VisualInstance3D *visual_instance = Object::cast_to<VisualInstance3D>(node_3d);
+ if (visual_instance) {
+ bounds = visual_instance->get_aabb();
+ } else {
+ CollisionShape3D *collision_shape = Object::cast_to<CollisionShape3D>(node_3d);
+ if (collision_shape) {
+ Ref<Shape3D> shape = collision_shape->get_shape();
+ if (shape.is_valid()) {
+ bounds = shape->get_debug_mesh()->get_aabb();
+ }
+ }
+ }
+
+ RS::get_singleton()->instance_set_visible(sbox_3d_instance, selection_visible);
+ RS::get_singleton()->instance_set_visible(sbox_3d_instance_ofs, selection_visible);
+ RS::get_singleton()->instance_set_visible(sbox_3d_instance_xray, selection_visible);
+ RS::get_singleton()->instance_set_visible(sbox_3d_instance_xray_ofs, selection_visible);
+
+ Transform3D xform_to_top_level_parent_space = node_3d->get_global_transform().affine_inverse() * node_3d->get_global_transform();
+ bounds = xform_to_top_level_parent_space.xform(bounds);
+ Transform3D t = node_3d->get_global_transform();
+
+ if (t == sbox_3d_xform && bounds == sbox_3d_bounds) {
+ return; // Nothing changed.
+ }
+ sbox_3d_xform = t;
+ sbox_3d_bounds = bounds;
+
+ Transform3D t_offset = t;
+
+ // Apply AABB scaling before item's global transform.
+ {
+ const Vector3 offset(0.005, 0.005, 0.005);
+ Basis aabb_s;
+ aabb_s.scale(bounds.size + offset);
+ t.translate_local(bounds.position - offset / 2);
+ t.basis = t.basis * aabb_s;
+ }
+ {
+ const Vector3 offset(0.01, 0.01, 0.01);
+ Basis aabb_s;
+ aabb_s.scale(bounds.size + offset);
+ t_offset.translate_local(bounds.position - offset / 2);
+ t_offset.basis = t_offset.basis * aabb_s;
+ }
+
+ RS::get_singleton()->instance_set_transform(sbox_3d_instance, t);
+ RS::get_singleton()->instance_set_transform(sbox_3d_instance_ofs, t_offset);
+ RS::get_singleton()->instance_set_transform(sbox_3d_instance_xray, t);
+ RS::get_singleton()->instance_set_transform(sbox_3d_instance_xray_ofs, t_offset);
+#endif // _3D_DISABLED
+ }
+}
+
+void RuntimeNodeSelect::_clear_selection() {
+ selected_node = nullptr;
+ has_selection = false;
+
+ if (sbox_2d_canvas.is_valid()) {
+ RS::get_singleton()->canvas_item_clear(sbox_2d_ci);
+ }
+
+#ifndef _3D_DISABLED
+ if (sbox_3d_instance.is_valid()) {
+ RS::get_singleton()->free(sbox_3d_instance);
+ RS::get_singleton()->free(sbox_3d_instance_ofs);
+ RS::get_singleton()->free(sbox_3d_instance_xray);
+ RS::get_singleton()->free(sbox_3d_instance_xray_ofs);
+ }
+#endif // _3D_DISABLED
+}
+
+void RuntimeNodeSelect::_set_selection_visible(bool p_visible) {
+ selection_visible = p_visible;
+
+ if (has_selection) {
+ _update_selection();
+ }
+}
+
+// Copied and trimmed from the CanvasItemEditor implementation.
+void RuntimeNodeSelect::_find_canvas_items_at_pos(const Point2 &p_pos, Node *p_node, Vector<SelectResult> &r_items, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) {
+ if (!p_node || Object::cast_to<Viewport>(p_node)) {
+ return;
+ }
+
+ // In the original CanvasItemEditor, this value would be fetched from the "editors/polygon_editor/point_grab_radius" editor property,
+ // but since this is not accessible from here, we will just use the default value.
+ const real_t grab_distance = 8;
+ CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);
+
+ for (int i = p_node->get_child_count() - 1; i >= 0; i--) {
+ if (ci) {
+ if (!ci->is_set_as_top_level()) {
+ _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, p_parent_xform * ci->get_transform(), p_canvas_xform);
+ } else {
+ _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, ci->get_transform(), p_canvas_xform);
+ }
+ } else {
+ CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node);
+ _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, Transform2D(), cl ? cl->get_transform() : p_canvas_xform);
+ }
+ }
+
+ if (ci && ci->is_visible_in_tree()) {
+ Transform2D xform = p_canvas_xform;
+ if (!ci->is_set_as_top_level()) {
+ xform *= p_parent_xform;
+ }
+
+ Vector2 pos;
+ // Cameras (overridden or not) don't affect `CanvasLayer`s.
+ if (!ci->get_canvas_layer_node()) {
+ Window *root = SceneTree::get_singleton()->get_root();
+ pos = (root->is_canvas_transform_override_enabled() ? root->get_canvas_transform_override() : root->get_canvas_transform()).affine_inverse().xform(p_pos);
+ } else {
+ pos = p_pos;
+ }
+
+ xform = (xform * ci->get_transform()).affine_inverse();
+ const real_t local_grab_distance = xform.basis_xform(Vector2(grab_distance, 0)).length() / view_2d_zoom;
+ if (ci->_edit_is_selected_on_click(xform.xform(pos), local_grab_distance)) {
+ SelectResult res;
+ res.item = ci;
+ res.order = ci->get_effective_z_index() + ci->get_canvas_layer();
+ r_items.push_back(res);
+
+ // If it's a shape, get the collision object it's from.
+ // FIXME: If the collision object has multiple shapes, only the topmost will be above it in the list.
+ if (Object::cast_to<CollisionShape2D>(ci) || Object::cast_to<CollisionPolygon2D>(ci)) {
+ CollisionObject2D *collision_object = Object::cast_to<CollisionObject2D>(ci->get_parent());
+ if (collision_object) {
+ SelectResult res_col;
+ res_col.item = ci->get_parent();
+ res_col.order = collision_object->get_z_index() + ci->get_canvas_layer();
+ r_items.push_back(res_col);
+ }
+ }
+ }
+ }
+}
+
+void RuntimeNodeSelect::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
+ view_2d_offset.x -= p_scroll_vec.x / view_2d_zoom;
+ view_2d_offset.y -= p_scroll_vec.y / view_2d_zoom;
+
+ _update_view_2d();
+}
+
+// A very shallow copy of the same function inside CanvasItemEditor.
+void RuntimeNodeSelect::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
+ real_t prev_zoom = view_2d_zoom;
+ view_2d_zoom = CLAMP(view_2d_zoom * p_zoom_factor, VIEW_2D_MIN_ZOOM, VIEW_2D_MAX_ZOOM);
+
+ Vector2 pos = SceneTree::get_singleton()->get_root()->get_screen_transform().affine_inverse().xform(p_origin);
+ view_2d_offset += pos / prev_zoom - pos / view_2d_zoom;
+
+ // We want to align in-scene pixels to screen pixels, this prevents blurry rendering
+ // of small details (texts, lines).
+ // This correction adds a jitter movement when zooming, so we correct only when the
+ // zoom factor is an integer. (in the other cases, all pixels won't be aligned anyway)
+ const real_t closest_zoom_factor = Math::round(view_2d_zoom);
+ if (Math::is_zero_approx(view_2d_zoom - closest_zoom_factor)) {
+ // Make sure scene pixel at view_offset is aligned on a screen pixel.
+ Vector2 view_offset_int = view_2d_offset.floor();
+ Vector2 view_offset_frac = view_2d_offset - view_offset_int;
+ view_2d_offset = view_offset_int + (view_offset_frac * closest_zoom_factor).round() / closest_zoom_factor;
+ }
+
+ _update_view_2d();
+}
+
+void RuntimeNodeSelect::_reset_camera_2d() {
+ view_2d_offset = -SceneTree::get_singleton()->get_root()->get_canvas_transform().get_origin();
+ view_2d_zoom = 1;
+
+ _update_view_2d();
+}
+
+void RuntimeNodeSelect::_update_view_2d() {
+ Transform2D transform = Transform2D();
+ transform.scale_basis(Size2(view_2d_zoom, view_2d_zoom));
+ transform.columns[2] = -view_2d_offset * view_2d_zoom;
+
+ SceneTree::get_singleton()->get_root()->set_canvas_transform_override(transform);
+
+ _queue_selection_update();
+}
+
+#ifndef _3D_DISABLED
+void RuntimeNodeSelect::_find_3d_items_at_pos(const Point2 &p_pos, Vector<SelectResult> &r_items) {
+ Window *root = SceneTree::get_singleton()->get_root();
+ Camera3D *camera = root->get_viewport()->get_camera_3d();
+ if (!camera) {
+ return;
+ }
+
+ Vector3 ray, pos, to;
+ if (root->get_viewport()->is_camera_3d_override_enabled()) {
+ Viewport *vp = root->get_viewport();
+ ray = vp->camera_3d_override_project_ray_normal(p_pos);
+ pos = vp->camera_3d_override_project_ray_origin(p_pos);
+ to = pos + ray * vp->get_camera_3d_override_properties()["z_far"];
+ } else {
+ ray = camera->project_ray_normal(p_pos);
+ pos = camera->project_ray_origin(p_pos);
+ to = pos + ray * camera->get_far();
+ }
+
+ // Start with physical objects.
+ PhysicsDirectSpaceState3D *ss = root->get_world_3d()->get_direct_space_state();
+ PhysicsDirectSpaceState3D::RayResult result;
+ HashSet<RID> excluded;
+ PhysicsDirectSpaceState3D::RayParameters ray_params;
+ ray_params.from = pos;
+ ray_params.to = to;
+ ray_params.collide_with_areas = true;
+ while (true) {
+ ray_params.exclude = excluded;
+ if (ss->intersect_ray(ray_params, result)) {
+ SelectResult res;
+ res.item = Object::cast_to<Node>(result.collider);
+ res.order = -pos.distance_to(Object::cast_to<Node3D>(res.item)->get_global_transform().xform(result.position));
+
+ // Fetch collision shapes.
+ CollisionObject3D *collision = Object::cast_to<CollisionObject3D>(result.collider);
+ if (collision) {
+ List<uint32_t> owners;
+ collision->get_shape_owners(&owners);
+ for (const uint32_t &I : owners) {
+ SelectResult res_shape;
+ res_shape.item = Object::cast_to<Node>(collision->shape_owner_get_owner(I));
+ res_shape.order = res.order;
+ r_items.push_back(res_shape);
+ }
+ }
+
+ r_items.push_back(res);
+
+ excluded.insert(result.rid);
+ } else {
+ break;
+ }
+ }
+
+ // Then go for the meshes.
+ Vector<ObjectID> items = RS::get_singleton()->instances_cull_ray(pos, to, root->get_world_3d()->get_scenario());
+ for (int i = 0; i < items.size(); i++) {
+ Object *obj = ObjectDB::get_instance(items[i]);
+ GeometryInstance3D *geo_instance = nullptr;
+ Ref<TriangleMesh> mesh_collision;
+
+ MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(obj);
+ if (mesh_instance) {
+ if (mesh_instance->get_mesh().is_valid()) {
+ geo_instance = mesh_instance;
+ mesh_collision = mesh_instance->get_mesh()->generate_triangle_mesh();
+ }
+ } else {
+ Label3D *label = Object::cast_to<Label3D>(obj);
+ if (label) {
+ geo_instance = label;
+ mesh_collision = label->generate_triangle_mesh();
+ } else {
+ Sprite3D *sprite = Object::cast_to<Sprite3D>(obj);
+ if (sprite) {
+ geo_instance = sprite;
+ mesh_collision = sprite->generate_triangle_mesh();
+ }
+ }
+ }
+
+ if (mesh_collision.is_valid()) {
+ Transform3D gt = geo_instance->get_global_transform();
+ Transform3D ai = gt.affine_inverse();
+ Vector3 point, normal;
+ if (mesh_collision->intersect_ray(ai.xform(pos), ai.basis.xform(ray).normalized(), point, normal)) {
+ SelectResult res;
+ res.item = Object::cast_to<Node>(obj);
+ res.order = -pos.distance_to(gt.xform(point));
+ r_items.push_back(res);
+
+ continue;
+ }
+ }
+
+ items.remove_at(i);
+ i--;
+ }
+}
+
+bool RuntimeNodeSelect::_handle_3d_input(const Ref<InputEvent> &p_event) {
+ Ref<InputEventMouseButton> b = p_event;
+
+ if (b.is_valid()) {
+ const real_t zoom_factor = 1.08 * b->get_factor();
+ switch (b->get_button_index()) {
+ case MouseButton::WHEEL_UP: {
+ if (!camera_freelook) {
+ _cursor_scale_distance(1.0 / zoom_factor);
+ }
+
+ return true;
+ } break;
+ case MouseButton::WHEEL_DOWN: {
+ if (!camera_freelook) {
+ _cursor_scale_distance(zoom_factor);
+ }
+
+ return true;
+ } break;
+ case MouseButton::RIGHT: {
+ _set_camera_freelook_enabled(b->is_pressed());
+ return true;
+ } break;
+ default: {
+ }
+ }
+ }
+
+ Ref<InputEventMouseMotion> m = p_event;
+
+ if (m.is_valid()) {
+ if (camera_freelook) {
+ _cursor_look(m);
+ } else if (m->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) {
+ if (m->is_shift_pressed()) {
+ _cursor_pan(m);
+ } else {
+ _cursor_orbit(m);
+ }
+ }
+
+ return true;
+ }
+
+ Ref<InputEventKey> k = p_event;
+
+ if (k.is_valid()) {
+ if (k->get_physical_keycode() == Key::ESCAPE) {
+ _set_camera_freelook_enabled(false);
+ return true;
+ } else if (k->is_ctrl_pressed()) {
+ switch (k->get_physical_keycode()) {
+ case Key::EQUAL: {
+ cursor.fov_scale = CLAMP(cursor.fov_scale - 0.05, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_perspective(CAMERA_BASE_FOV * cursor.fov_scale, CAMERA_ZNEAR, CAMERA_ZFAR);
+
+ return true;
+ } break;
+ case Key::MINUS: {
+ cursor.fov_scale = CLAMP(cursor.fov_scale + 0.05, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_perspective(CAMERA_BASE_FOV * cursor.fov_scale, CAMERA_ZNEAR, CAMERA_ZFAR);
+
+ return true;
+ } break;
+ case Key::KEY_0: {
+ cursor.fov_scale = 1;
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_perspective(CAMERA_BASE_FOV, CAMERA_ZNEAR, CAMERA_ZFAR);
+
+ return true;
+ } break;
+ default: {
+ }
+ }
+ }
+ }
+
+ // TODO: Handle magnify and pan input gestures.
+
+ return false;
+}
+
+void RuntimeNodeSelect::_set_camera_freelook_enabled(bool p_enabled) {
+ camera_freelook = p_enabled;
+
+ if (p_enabled) {
+ // Make sure eye_pos is synced, because freelook referential is eye pos rather than orbit pos
+ Vector3 forward = _get_cursor_transform().basis.xform(Vector3(0, 0, -1));
+ cursor.eye_pos = cursor.pos - cursor.distance * forward;
+
+ previous_mouse_position = SceneTree::get_singleton()->get_root()->get_mouse_position();
+
+ // Hide mouse like in an FPS (warping doesn't work).
+ Input::get_singleton()->set_mouse_mode_override(Input::MOUSE_MODE_CAPTURED);
+
+ } else {
+ // Restore mouse.
+ Input::get_singleton()->set_mouse_mode_override(Input::MOUSE_MODE_VISIBLE);
+
+ // Restore the previous mouse position when leaving freelook mode.
+ // This is done because leaving `Input.MOUSE_MODE_CAPTURED` will center the cursor
+ // due to OS limitations.
+ Input::get_singleton()->warp_mouse(previous_mouse_position);
+ }
+}
+
+void RuntimeNodeSelect::_cursor_scale_distance(real_t p_scale) {
+ real_t min_distance = MAX(CAMERA_ZNEAR * 4, VIEW_3D_MIN_ZOOM);
+ real_t max_distance = MIN(CAMERA_ZFAR / 4, VIEW_3D_MAX_ZOOM);
+ cursor.distance = CLAMP(cursor.distance * p_scale, min_distance, max_distance);
+
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform());
+}
+
+void RuntimeNodeSelect::_cursor_look(Ref<InputEventWithModifiers> p_event) {
+ Window *root = SceneTree::get_singleton()->get_root();
+ const Vector2 relative = Input::get_singleton()->warp_mouse_motion(p_event, Rect2(Vector2(), root->get_size()));
+ const Transform3D prev_camera_transform = _get_cursor_transform();
+
+ cursor.x_rot += relative.y * RADS_PER_PIXEL;
+ // Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
+ cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57);
+
+ cursor.y_rot += relative.x * RADS_PER_PIXEL;
+
+ // Look is like the opposite of Orbit: the focus point rotates around the camera.
+ Transform3D camera_transform = _get_cursor_transform();
+ Vector3 pos = camera_transform.xform(Vector3(0, 0, 0));
+ Vector3 prev_pos = prev_camera_transform.xform(Vector3(0, 0, 0));
+ Vector3 diff = prev_pos - pos;
+ cursor.pos += diff;
+
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform());
+}
+
+void RuntimeNodeSelect::_cursor_pan(Ref<InputEventWithModifiers> p_event) {
+ Window *root = SceneTree::get_singleton()->get_root();
+ // Reduce all sides of the area by 1, so warping works when windows are maximized/fullscreen.
+ const Vector2 relative = Input::get_singleton()->warp_mouse_motion(p_event, Rect2(Vector2(1, 1), root->get_size() - Vector2(2, 2)));
+ const real_t pan_speed = 1 / 150.0;
+
+ Transform3D camera_transform;
+ camera_transform.translate_local(cursor.pos);
+ camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);
+ camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);
+
+ Vector3 translation(1 * -relative.x * pan_speed, relative.y * pan_speed, 0);
+ translation *= cursor.distance / 4;
+ camera_transform.translate_local(translation);
+ cursor.pos = camera_transform.origin;
+
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform());
+}
+
+void RuntimeNodeSelect::_cursor_orbit(Ref<InputEventWithModifiers> p_event) {
+ Window *root = SceneTree::get_singleton()->get_root();
+ // Reduce all sides of the area by 1, so warping works when windows are maximized/fullscreen.
+ const Vector2 relative = Input::get_singleton()->warp_mouse_motion(p_event, Rect2(Vector2(1, 1), root->get_size() - Vector2(2, 2)));
+
+ cursor.x_rot += relative.y * RADS_PER_PIXEL;
+ // Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
+ cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57);
+
+ cursor.y_rot += relative.x * RADS_PER_PIXEL;
+
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform());
+}
+
+Transform3D RuntimeNodeSelect::_get_cursor_transform() {
+ Transform3D camera_transform;
+ camera_transform.translate_local(cursor.pos);
+ camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);
+ camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);
+ camera_transform.translate_local(0, 0, cursor.distance);
+
+ return camera_transform;
+}
+
+void RuntimeNodeSelect::_reset_camera_3d() {
+ camera_first_override = true;
+
+ Window *root = SceneTree::get_singleton()->get_root();
+ Camera3D *camera = root->get_camera_3d();
+ if (!camera) {
+ return;
+ }
+
+ cursor = Cursor();
+ Transform3D transform = camera->get_global_transform();
+ transform.translate_local(0, 0, -cursor.distance);
+ cursor.pos = transform.origin;
+
+ cursor.x_rot = -camera->get_global_rotation().x;
+ cursor.y_rot = -camera->get_global_rotation().y;
+
+ cursor.fov_scale = CLAMP(camera->get_fov() / CAMERA_BASE_FOV, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);
+
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform());
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_perspective(CAMERA_BASE_FOV * cursor.fov_scale, CAMERA_ZNEAR, CAMERA_ZFAR);
+}
+#endif // _3D_DISABLED
#endif
diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h
index be2642a2ae..f9dd6161aa 100644
--- a/scene/debugger/scene_debugger.h
+++ b/scene/debugger/scene_debugger.h
@@ -31,19 +31,21 @@
#ifndef SCENE_DEBUGGER_H
#define SCENE_DEBUGGER_H
-#include "core/object/class_db.h"
+#include "core/input/shortcut.h"
#include "core/object/ref_counted.h"
#include "core/string/ustring.h"
#include "core/templates/pair.h"
#include "core/variant/array.h"
+#include "scene/gui/view_panner.h"
+#include "scene/resources/mesh.h"
+class PopupMenu;
class Script;
class Node;
class SceneDebugger {
-public:
private:
- static SceneDebugger *singleton;
+ inline static SceneDebugger *singleton = nullptr;
SceneDebugger();
@@ -59,6 +61,7 @@ private:
static void _set_node_owner_recursive(Node *p_node, Node *p_owner);
static void _set_object_property(ObjectID p_id, const String &p_property, const Variant &p_value);
static void _send_object_id(ObjectID p_id, int p_max_size = 1 << 20);
+ static void _next_frame();
public:
static Error parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured);
@@ -160,11 +163,161 @@ private:
live_edit_root = NodePath("/root");
}
- static LiveEditor *singleton;
+ inline static LiveEditor *singleton = nullptr;
public:
static LiveEditor *get_singleton();
};
+
+class RuntimeNodeSelect : public Object {
+ GDCLASS(RuntimeNodeSelect, Object);
+
+public:
+ enum NodeType {
+ NODE_TYPE_NONE,
+ NODE_TYPE_2D,
+ NODE_TYPE_3D,
+ NODE_TYPE_MAX
+ };
+
+ enum SelectMode {
+ SELECT_MODE_SINGLE,
+ SELECT_MODE_LIST,
+ SELECT_MODE_MAX
+ };
+
+private:
+ friend class SceneDebugger;
+
+ struct SelectResult {
+ Node *item = nullptr;
+ real_t order = 0;
+ _FORCE_INLINE_ bool operator<(const SelectResult &p_rr) const { return p_rr.order < order; }
+ };
+
+ bool has_selection = false;
+ Node *selected_node = nullptr;
+ PopupMenu *selection_list = nullptr;
+ bool selection_visible = true;
+ bool selection_update_queued = false;
+
+ bool camera_override = false;
+
+ // Values taken from EditorZoomWidget.
+ const float VIEW_2D_MIN_ZOOM = 1.0 / 128;
+ const float VIEW_2D_MAX_ZOOM = 128;
+
+ Ref<ViewPanner> panner;
+ Vector2 view_2d_offset;
+ real_t view_2d_zoom = 1.0;
+
+ RID sbox_2d_canvas;
+ RID sbox_2d_ci;
+ Transform2D sbox_2d_xform;
+ Rect2 sbox_2d_rect;
+
+#ifndef _3D_DISABLED
+ struct Cursor {
+ Vector3 pos;
+ real_t x_rot, y_rot, distance, fov_scale;
+ Vector3 eye_pos; // Used in freelook mode.
+
+ Cursor() {
+ // These rotations place the camera in +X +Y +Z, aka south east, facing north west.
+ x_rot = 0.5;
+ y_rot = -0.5;
+ distance = 4;
+ fov_scale = 1.0;
+ }
+ };
+ Cursor cursor;
+
+ // Values taken from Node3DEditor.
+ const float VIEW_3D_MIN_ZOOM = 0.01;
+#ifdef REAL_T_IS_DOUBLE
+ const double VIEW_3D_MAX_ZOOM = 1'000'000'000'000;
+#else
+ const float VIEW_3D_MAX_ZOOM = 10'000;
+#endif
+ const float CAMERA_ZNEAR = 0.05;
+ const float CAMERA_ZFAR = 4'000;
+
+ const float CAMERA_BASE_FOV = 75;
+ const float CAMERA_MIN_FOV_SCALE = 0.1;
+ const float CAMERA_MAX_FOV_SCALE = 2.5;
+
+ const float FREELOOK_BASE_SPEED = 4;
+ const float RADS_PER_PIXEL = 0.004;
+
+ bool camera_first_override = true;
+ bool camera_freelook = false;
+
+ Vector2 previous_mouse_position;
+
+ Ref<ArrayMesh> sbox_3d_mesh;
+ Ref<ArrayMesh> sbox_3d_mesh_xray;
+ RID sbox_3d_instance;
+ RID sbox_3d_instance_ofs;
+ RID sbox_3d_instance_xray;
+ RID sbox_3d_instance_xray_ofs;
+ Transform3D sbox_3d_xform;
+ AABB sbox_3d_bounds;
+#endif
+
+ Point2 selection_position = Point2(INFINITY, INFINITY);
+ bool list_shortcut_pressed = false;
+
+ NodeType node_select_type = NODE_TYPE_2D;
+ SelectMode node_select_mode = SELECT_MODE_SINGLE;
+
+ void _setup();
+
+ void _node_set_type(NodeType p_type);
+ void _select_set_mode(SelectMode p_mode);
+
+ void _set_camera_override_enabled(bool p_enabled);
+
+ void _root_window_input(const Ref<InputEvent> &p_event);
+ void _items_popup_index_pressed(int p_index, PopupMenu *p_popup);
+ void _update_input_state();
+
+ void _process_frame();
+ void _physics_frame();
+
+ void _click_point();
+ void _select_node(Node *p_node);
+ void _queue_selection_update();
+ void _update_selection();
+ void _clear_selection();
+ void _set_selection_visible(bool p_visible);
+
+ void _find_canvas_items_at_pos(const Point2 &p_pos, Node *p_node, Vector<SelectResult> &r_items, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D());
+ void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
+ void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
+ void _reset_camera_2d();
+ void _update_view_2d();
+
+#ifndef _3D_DISABLED
+ void _find_3d_items_at_pos(const Point2 &p_pos, Vector<SelectResult> &r_items);
+ bool _handle_3d_input(const Ref<InputEvent> &p_event);
+ void _set_camera_freelook_enabled(bool p_enabled);
+ void _cursor_scale_distance(real_t p_scale);
+ void _cursor_look(Ref<InputEventWithModifiers> p_event);
+ void _cursor_pan(Ref<InputEventWithModifiers> p_event);
+ void _cursor_orbit(Ref<InputEventWithModifiers> p_event);
+ Transform3D _get_cursor_transform();
+ void _reset_camera_3d();
+#endif
+
+ RuntimeNodeSelect() { singleton = this; }
+
+ inline static RuntimeNodeSelect *singleton = nullptr;
+
+public:
+ static RuntimeNodeSelect *get_singleton();
+
+ ~RuntimeNodeSelect();
+};
#endif
#endif // SCENE_DEBUGGER_H
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 7346c9dcd3..9d7e2496a2 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -1481,7 +1481,7 @@ void CodeEdit::_line_number_draw_callback(int p_line, int p_gutter, const Rect2
if (E) {
text_rid = E->value;
} else {
- String fc = String::num(p_line + 1).lpad(line_number_digits, line_number_padding);
+ String fc = String::num_int64(p_line + 1).lpad(line_number_digits, line_number_padding);
if (is_localizing_numeral_system()) {
fc = TS->format_number(fc);
}
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/control.cpp b/scene/gui/control.cpp
index 4b3007543b..4a3f7f2414 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -44,7 +44,7 @@
#ifdef TOOLS_ENABLED
#include "editor/plugins/control_editor_plugin.h"
-#endif
+#endif // TOOLS_ENABLED
// Editor plugin interoperability.
@@ -140,14 +140,6 @@ void Control::_edit_set_rect(const Rect2 &p_edit_rect) {
set_size(p_edit_rect.size.snappedf(1), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled());
}
-Rect2 Control::_edit_get_rect() const {
- return Rect2(Point2(), get_size());
-}
-
-bool Control::_edit_use_rect() const {
- return true;
-}
-
void Control::_edit_set_rotation(real_t p_rotation) {
set_rotation(p_rotation);
}
@@ -178,7 +170,17 @@ bool Control::_edit_use_pivot() const {
Size2 Control::_edit_get_minimum_size() const {
return get_combined_minimum_size();
}
-#endif
+#endif // TOOLS_ENABLED
+
+#ifdef DEBUG_ENABLED
+Rect2 Control::_edit_get_rect() const {
+ return Rect2(Point2(), get_size());
+}
+
+bool Control::_edit_use_rect() const {
+ return true;
+}
+#endif // DEBUG_ENABLED
void Control::reparent(Node *p_parent, bool p_keep_global_transform) {
ERR_MAIN_THREAD_GUARD;
@@ -239,7 +241,7 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List
}
CanvasItem::get_argument_options(p_function, p_idx, r_options);
}
-#endif
+#endif // TOOLS_ENABLED
PackedStringArray Control::get_configuration_warnings() const {
ERR_READ_THREAD_GUARD_V(PackedStringArray());
@@ -663,7 +665,7 @@ Rect2 Control::get_parent_anchorable_rect() const {
#else
parent_rect = get_viewport()->get_visible_rect();
-#endif
+#endif // TOOLS_ENABLED
}
return parent_rect;
@@ -1396,7 +1398,7 @@ void Control::set_position(const Point2 &p_point, bool p_keep_offsets) {
data.pos_cache = p_point;
return;
}
-#endif
+#endif // TOOLS_ENABLED
if (p_keep_offsets) {
_compute_anchors(Rect2(p_point, data.size_cache), data.offset, data.anchor);
@@ -1440,7 +1442,7 @@ void Control::_set_size(const Size2 &p_size) {
if (data.size_warning && (data.anchor[SIDE_LEFT] != data.anchor[SIDE_RIGHT] || data.anchor[SIDE_TOP] != data.anchor[SIDE_BOTTOM])) {
WARN_PRINT("Nodes with non-equal opposite anchors will have their size overridden after _ready(). \nIf you want to set size, change the anchors or consider using set_deferred().");
}
-#endif
+#endif // DEBUG_ENABLED
set_size(p_size);
}
@@ -1462,7 +1464,7 @@ void Control::set_size(const Size2 &p_size, bool p_keep_offsets) {
data.size_cache = new_size;
return;
}
-#endif
+#endif // TOOLS_ENABLED
if (p_keep_offsets) {
_compute_anchors(Rect2(data.pos_cache, new_size), data.offset, data.anchor);
@@ -2745,7 +2747,7 @@ Variant Control::get_theme_item(Theme::DataType p_data_type, const StringName &p
Ref<Texture2D> Control::get_editor_theme_icon(const StringName &p_name) const {
return get_theme_icon(p_name, SNAME("EditorIcons"));
}
-#endif
+#endif // TOOLS_ENABLED
bool Control::has_theme_icon(const StringName &p_name, const StringName &p_theme_type) const {
ERR_READ_THREAD_GUARD_V(false);
@@ -3087,7 +3089,7 @@ bool Control::is_layout_rtl() const {
const_cast<Control *>(this)->data.is_rtl = true;
return data.is_rtl;
}
-#endif
+#endif // TOOLS_ENABLED
Node *parent_node = get_parent();
while (parent_node) {
Control *parent_control = Object::cast_to<Control>(parent_node);
@@ -3162,7 +3164,7 @@ bool Control::is_auto_translating() const {
ERR_READ_THREAD_GUARD_V(false);
return can_auto_translate();
}
-#endif
+#endif // DISABLE_DEPRECATED
void Control::set_tooltip_auto_translate_mode(AutoTranslateMode p_mode) {
ERR_MAIN_THREAD_GUARD;
@@ -3215,7 +3217,7 @@ void Control::_notification(int p_notification) {
case NOTIFICATION_EDITOR_POST_SAVE: {
saving = false;
} break;
-#endif
+#endif // TOOLS_ENABLED
case NOTIFICATION_POSTINITIALIZE: {
data.initialized = true;
@@ -3261,7 +3263,7 @@ void Control::_notification(int p_notification) {
case NOTIFICATION_READY: {
#ifdef DEBUG_ENABLED
connect(SceneStringName(ready), callable_mp(this, &Control::_clear_size_warning), CONNECT_DEFERRED | CONNECT_ONE_SHOT);
-#endif
+#endif // DEBUG_ENABLED
} break;
case NOTIFICATION_ENTER_CANVAS: {
@@ -3573,7 +3575,7 @@ void Control::_bind_methods() {
#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("set_auto_translate", "enable"), &Control::set_auto_translate);
ClassDB::bind_method(D_METHOD("is_auto_translating"), &Control::is_auto_translating);
-#endif
+#endif // DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("set_localize_numeral_system", "enable"), &Control::set_localize_numeral_system);
ClassDB::bind_method(D_METHOD("is_localizing_numeral_system"), &Control::is_localizing_numeral_system);
@@ -3628,7 +3630,7 @@ void Control::_bind_methods() {
#ifndef DISABLE_DEPRECATED
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_auto_translate", "is_auto_translating");
-#endif
+#endif // DISABLE_DEPRECATED
ADD_GROUP("Tooltip", "tooltip_");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "tooltip_text", PROPERTY_HINT_MULTILINE_TEXT), "set_tooltip_text", "get_tooltip_text");
diff --git a/scene/gui/control.h b/scene/gui/control.h
index 6cabf10971..ac386659ec 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -48,7 +48,7 @@ class Control : public CanvasItem {
#ifdef TOOLS_ENABLED
bool saving = false;
-#endif
+#endif //TOOLS_ENABLED
public:
enum Anchor {
@@ -396,8 +396,6 @@ public:
virtual Size2 _edit_get_scale() const override;
virtual void _edit_set_rect(const Rect2 &p_edit_rect) override;
- virtual Rect2 _edit_get_rect() const override;
- virtual bool _edit_use_rect() const override;
virtual void _edit_set_rotation(real_t p_rotation) override;
virtual real_t _edit_get_rotation() const override;
@@ -408,7 +406,13 @@ public:
virtual bool _edit_use_pivot() const override;
virtual Size2 _edit_get_minimum_size() const override;
-#endif
+#endif //TOOLS_ENABLED
+
+#ifdef DEBUG_ENABLED
+ virtual Rect2 _edit_get_rect() const override;
+ virtual bool _edit_use_rect() const override;
+#endif // DEBUG_ENABLED
+
virtual void reparent(Node *p_parent, bool p_keep_global_transform = true) override;
// Editor integration.
@@ -418,7 +422,7 @@ public:
PackedStringArray get_configuration_warnings() const override;
#ifdef TOOLS_ENABLED
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
-#endif
+#endif //TOOLS_ENABLED
virtual bool is_text_field() const;
@@ -605,7 +609,7 @@ public:
Variant get_theme_item(Theme::DataType p_data_type, const StringName &p_name, const StringName &p_theme_type = StringName()) const;
#ifdef TOOLS_ENABLED
Ref<Texture2D> get_editor_theme_icon(const StringName &p_name) const;
-#endif
+#endif //TOOLS_ENABLED
bool has_theme_icon_override(const StringName &p_name) const;
bool has_theme_stylebox_override(const StringName &p_name) const;
@@ -637,7 +641,7 @@ public:
#ifndef DISABLE_DEPRECATED
void set_auto_translate(bool p_enable);
bool is_auto_translating() const;
-#endif
+#endif //DISABLE_DEPRECATED
void set_tooltip_auto_translate_mode(AutoTranslateMode p_mode);
AutoTranslateMode get_tooltip_auto_translate_mode() const;
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 111b8579ec..18864b1289 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -67,7 +67,18 @@ void FileDialog::_native_popup() {
} else if (access == ACCESS_USERDATA) {
root = OS::get_singleton()->get_user_data_dir();
}
- DisplayServer::get_singleton()->file_dialog_with_options_show(get_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), root, file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, _get_options(), callable_mp(this, &FileDialog::_native_dialog_cb));
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE_EXTRA)) {
+ DisplayServer::get_singleton()->file_dialog_with_options_show(get_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), root, file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, _get_options(), callable_mp(this, &FileDialog::_native_dialog_cb_with_options));
+ } else {
+ DisplayServer::get_singleton()->file_dialog_show(get_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, callable_mp(this, &FileDialog::_native_dialog_cb));
+ }
+}
+
+bool FileDialog::_can_use_native_popup() {
+ if (access == ACCESS_RESOURCES || access == ACCESS_USERDATA || options.size() > 0) {
+ return DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE_EXTRA);
+ }
+ return DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE);
}
void FileDialog::popup(const Rect2i &p_rect) {
@@ -80,7 +91,7 @@ void FileDialog::popup(const Rect2i &p_rect) {
}
#endif
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
+ if (_can_use_native_popup() && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
_native_popup();
} else {
ConfirmationDialog::popup(p_rect);
@@ -99,7 +110,7 @@ void FileDialog::set_visible(bool p_visible) {
}
#endif
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
+ if (_can_use_native_popup() && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
if (p_visible) {
_native_popup();
}
@@ -108,7 +119,11 @@ void FileDialog::set_visible(bool p_visible) {
}
}
-void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options) {
+void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter) {
+ _native_dialog_cb_with_options(p_ok, p_files, p_filter, Dictionary());
+}
+
+void FileDialog::_native_dialog_cb_with_options(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options) {
if (!p_ok) {
file->set_text("");
emit_signal(SNAME("canceled"));
@@ -182,7 +197,7 @@ void FileDialog::_notification(int p_what) {
#endif
// Replace the built-in dialog with the native one if it started visible.
- if (is_visible() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
+ if (is_visible() && _can_use_native_popup() && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
ConfirmationDialog::set_visible(false);
_native_popup();
}
@@ -1487,7 +1502,7 @@ void FileDialog::set_use_native_dialog(bool p_native) {
#endif
// Replace the built-in dialog with the native one if it's currently visible.
- if (is_inside_tree() && is_visible() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
+ if (is_inside_tree() && is_visible() && _can_use_native_popup() && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
ConfirmationDialog::set_visible(false);
_native_popup();
}
diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h
index 6ef60a0f4f..28978dbed3 100644
--- a/scene/gui/file_dialog.h
+++ b/scene/gui/file_dialog.h
@@ -188,8 +188,10 @@ private:
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
+ bool _can_use_native_popup();
void _native_popup();
- void _native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options);
+ void _native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter);
+ void _native_dialog_cb_with_options(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options);
bool _is_open_should_be_disabled();
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/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp
index 0b521f926d..878bceccbc 100644
--- a/scene/gui/video_stream_player.cpp
+++ b/scene/gui/video_stream_player.cpp
@@ -178,6 +178,7 @@ void VideoStreamPlayer::_notification(int p_notification) {
draw_texture_rect(texture, Rect2(Point2(), s), false);
} break;
+ case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (is_playing() && !is_paused()) {
paused_from_tree = true;
@@ -189,6 +190,13 @@ void VideoStreamPlayer::_notification(int p_notification) {
}
} break;
+ case NOTIFICATION_UNSUSPENDED: {
+ if (get_tree()->is_paused()) {
+ break;
+ }
+ [[fallthrough]];
+ }
+
case NOTIFICATION_UNPAUSED: {
if (paused_from_tree) {
paused_from_tree = false;
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index 7c8bf9c809..f87dad1889 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -44,7 +44,7 @@
#define ERR_DRAW_GUARD \
ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside this node's `_draw()`, functions connected to its `draw` signal, or when it receives NOTIFICATION_DRAW.")
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
bool CanvasItem::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
if (_edit_use_rect()) {
return _edit_get_rect().has_point(p_point);
@@ -52,11 +52,13 @@ bool CanvasItem::_edit_is_selected_on_click(const Point2 &p_point, double p_tole
return p_point.length() < p_tolerance;
}
}
+#endif // DEBUG_ENABLED
+#ifdef TOOLS_ENABLED
Transform2D CanvasItem::_edit_get_transform() const {
return Transform2D(_edit_get_rotation(), _edit_get_position() + _edit_get_pivot());
}
-#endif
+#endif //TOOLS_ENABLED
bool CanvasItem::is_visible_in_tree() const {
ERR_READ_THREAD_GUARD_V(false);
@@ -1158,7 +1160,7 @@ void CanvasItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("_edit_get_pivot"), &CanvasItem::_edit_get_pivot);
ClassDB::bind_method(D_METHOD("_edit_use_pivot"), &CanvasItem::_edit_use_pivot);
ClassDB::bind_method(D_METHOD("_edit_get_transform"), &CanvasItem::_edit_get_transform);
-#endif
+#endif //TOOLS_ENABLED
ClassDB::bind_method(D_METHOD("get_canvas_item"), &CanvasItem::get_canvas_item);
diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h
index 5c4477234b..c74f8238e3 100644
--- a/scene/main/canvas_item.h
+++ b/scene/main/canvas_item.h
@@ -174,7 +174,7 @@ protected:
void _draw_multiline_bind_compat_84523(const Vector<Point2> &p_points, const Color &p_color, real_t p_width);
void _draw_multiline_colors_bind_compat_84523(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width);
static void _bind_compatibility_methods();
-#endif
+#endif // DISABLE_DEPRECATED
void _validate_property(PropertyInfo &p_property) const;
@@ -193,11 +193,9 @@ public:
NOTIFICATION_WORLD_2D_CHANGED = 36,
};
- /* EDITOR */
-#ifdef TOOLS_ENABLED
- // Select the node
- virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const;
+ /* EDITOR AND DEBUGGING */
+#ifdef TOOLS_ENABLED
// Save and restore a CanvasItem state
virtual void _edit_set_state(const Dictionary &p_state) {}
virtual Dictionary _edit_get_state() const { return Dictionary(); }
@@ -216,9 +214,7 @@ public:
virtual real_t _edit_get_rotation() const { return 0.0; }
// Used to resize/move the node
- virtual bool _edit_use_rect() const { return false; } // MAYBE REPLACE BY A _edit_get_editmode()
virtual void _edit_set_rect(const Rect2 &p_rect) {}
- virtual Rect2 _edit_get_rect() const { return Rect2(0, 0, 0, 0); }
virtual Size2 _edit_get_minimum_size() const { return Size2(-1, -1); } // LOOKS WEIRD
// Used to set a pivot
@@ -227,7 +223,18 @@ public:
virtual Point2 _edit_get_pivot() const { return Point2(); }
virtual Transform2D _edit_get_transform() const;
-#endif
+#endif // TOOLS_ENABLED
+
+#ifdef DEBUG_ENABLED
+ // Those need to be available in debug runtime, to allow for node selection.
+
+ // Select the node.
+ virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const;
+
+ // Used to resize/move the node.
+ virtual bool _edit_use_rect() const { return false; } // Maybe replace with _edit_get_editmode().
+ virtual Rect2 _edit_get_rect() const { return Rect2(0, 0, 0, 0); }
+#endif // DEBUG_ENABLED
void update_draw_order();
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index d921cc5b67..8dc7b4a87c 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -184,6 +184,7 @@ void Node::_notification(int p_notification) {
}
} break;
+ case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (is_physics_interpolated_and_enabled() && is_inside_tree()) {
reset_physics_interpolation();
@@ -695,6 +696,16 @@ void Node::_propagate_pause_notification(bool p_enable) {
data.blocked--;
}
+void Node::_propagate_suspend_notification(bool p_enable) {
+ notification(p_enable ? NOTIFICATION_SUSPENDED : NOTIFICATION_UNSUSPENDED);
+
+ data.blocked++;
+ for (KeyValue<StringName, Node *> &KV : data.children) {
+ KV.value->_propagate_suspend_notification(p_enable);
+ }
+ data.blocked--;
+}
+
Node::ProcessMode Node::get_process_mode() const {
return data.process_mode;
}
@@ -850,7 +861,7 @@ bool Node::can_process_notification(int p_what) const {
bool Node::can_process() const {
ERR_FAIL_COND_V(!is_inside_tree(), false);
- return _can_process(get_tree()->is_paused());
+ return !get_tree()->is_suspended() && _can_process(get_tree()->is_paused());
}
bool Node::_can_process(bool p_paused) const {
diff --git a/scene/main/node.h b/scene/main/node.h
index 799478fa35..e2f3ce9b78 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -300,6 +300,7 @@ private:
void _set_tree(SceneTree *p_tree);
void _propagate_pause_notification(bool p_enable);
+ void _propagate_suspend_notification(bool p_enable);
_FORCE_INLINE_ bool _can_process(bool p_paused) const;
_FORCE_INLINE_ bool _is_enabled() const;
@@ -439,6 +440,8 @@ public:
// Editor specific node notifications
NOTIFICATION_EDITOR_PRE_SAVE = 9001,
NOTIFICATION_EDITOR_POST_SAVE = 9002,
+ NOTIFICATION_SUSPENDED = 9003,
+ NOTIFICATION_UNSUSPENDED = 9004
};
/* NODE/TREE */
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index 71d91b970e..60cecfcfe7 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -954,11 +954,14 @@ Ref<ArrayMesh> SceneTree::get_debug_contact_mesh() {
void SceneTree::set_pause(bool p_enabled) {
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Pause can only be set from the main thread.");
+ ERR_FAIL_COND_MSG(suspended, "Pause state cannot be modified while suspended.");
if (p_enabled == paused) {
return;
}
+
paused = p_enabled;
+
#ifndef _3D_DISABLED
PhysicsServer3D::get_singleton()->set_active(!p_enabled);
#endif // _3D_DISABLED
@@ -972,6 +975,30 @@ bool SceneTree::is_paused() const {
return paused;
}
+void SceneTree::set_suspend(bool p_enabled) {
+ ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Suspend can only be set from the main thread.");
+
+ if (p_enabled == suspended) {
+ return;
+ }
+
+ suspended = p_enabled;
+
+ Engine::get_singleton()->set_freeze_time_scale(p_enabled);
+
+#ifndef _3D_DISABLED
+ PhysicsServer3D::get_singleton()->set_active(!p_enabled && !paused);
+#endif // _3D_DISABLED
+ PhysicsServer2D::get_singleton()->set_active(!p_enabled && !paused);
+ if (get_root()) {
+ get_root()->_propagate_suspend_notification(p_enabled);
+ }
+}
+
+bool SceneTree::is_suspended() const {
+ return suspended;
+}
+
void SceneTree::_process_group(ProcessGroup *p_group, bool p_physics) {
// When reading this function, keep in mind that this code must work in a way where
// if any node is removed, this needs to continue working.
diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h
index 7e44541105..291e4a5a0c 100644
--- a/scene/main/scene_tree.h
+++ b/scene/main/scene_tree.h
@@ -143,6 +143,7 @@ private:
bool debug_navigation_hint = false;
#endif
bool paused = false;
+ bool suspended = false;
HashMap<StringName, Group> group_map;
bool _quit = false;
@@ -343,6 +344,8 @@ public:
void set_pause(bool p_enabled);
bool is_paused() const;
+ void set_suspend(bool p_enabled);
+ bool is_suspended() const;
#ifdef DEBUG_ENABLED
void set_debug_collisions_hint(bool p_enabled);
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 54f66e8d4e..5a90eb8f3e 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -3123,7 +3123,7 @@ void Viewport::push_input(const Ref<InputEvent> &p_event, bool p_local_coords) {
ERR_FAIL_COND(!is_inside_tree());
ERR_FAIL_COND(p_event.is_null());
- if (disable_input) {
+ if (disable_input || disable_input_override) {
return;
}
@@ -3195,7 +3195,7 @@ void Viewport::push_unhandled_input(const Ref<InputEvent> &p_event, bool p_local
local_input_handled = false;
- if (disable_input || !_can_consume_input_events()) {
+ if (disable_input || disable_input_override || !_can_consume_input_events()) {
return;
}
@@ -3298,7 +3298,7 @@ void Viewport::set_disable_input(bool p_disable) {
if (p_disable == disable_input) {
return;
}
- if (p_disable) {
+ if (p_disable && !disable_input_override) {
_drop_mouse_focus();
_mouse_leave_viewport();
_gui_cancel_tooltip();
@@ -3311,6 +3311,19 @@ bool Viewport::is_input_disabled() const {
return disable_input;
}
+void Viewport::set_disable_input_override(bool p_disable) {
+ ERR_MAIN_THREAD_GUARD;
+ if (p_disable == disable_input_override) {
+ return;
+ }
+ if (p_disable && !disable_input) {
+ _drop_mouse_focus();
+ _mouse_leave_viewport();
+ _gui_cancel_tooltip();
+ }
+ disable_input_override = p_disable;
+}
+
Variant Viewport::gui_get_drag_data() const {
ERR_READ_THREAD_GUARD_V(Variant());
return get_section_root_viewport()->gui.drag_data;
@@ -4237,6 +4250,22 @@ void Viewport::set_camera_3d_override_orthogonal(real_t p_size, real_t p_z_near,
}
}
+HashMap<StringName, real_t> Viewport::get_camera_3d_override_properties() const {
+ HashMap<StringName, real_t> props;
+
+ props["size"] = 0;
+ props["fov"] = 0;
+ props["z_near"] = 0;
+ props["z_far"] = 0;
+ ERR_READ_THREAD_GUARD_V(props);
+
+ props["size"] = camera_3d_override.size;
+ props["fov"] = camera_3d_override.fov;
+ props["z_near"] = camera_3d_override.z_near;
+ props["z_far"] = camera_3d_override.z_far;
+ return props;
+}
+
void Viewport::set_disable_3d(bool p_disable) {
ERR_MAIN_THREAD_GUARD;
disable_3d = p_disable;
@@ -4270,6 +4299,54 @@ Transform3D Viewport::get_camera_3d_override_transform() const {
return Transform3D();
}
+Vector3 Viewport::camera_3d_override_project_ray_normal(const Point2 &p_pos) const {
+ ERR_READ_THREAD_GUARD_V(Vector3());
+ Vector3 ray = camera_3d_override_project_local_ray_normal(p_pos);
+ return camera_3d_override.transform.basis.xform(ray).normalized();
+}
+
+Vector3 Viewport::camera_3d_override_project_local_ray_normal(const Point2 &p_pos) const {
+ ERR_READ_THREAD_GUARD_V(Vector3());
+ Size2 viewport_size = get_camera_rect_size();
+ Vector2 cpos = get_camera_coords(p_pos);
+ Vector3 ray;
+
+ if (camera_3d_override.projection == Camera3DOverrideData::PROJECTION_ORTHOGONAL) {
+ ray = Vector3(0, 0, -1);
+ } else {
+ Projection cm;
+ cm.set_perspective(camera_3d_override.fov, get_visible_rect().size.aspect(), camera_3d_override.z_near, camera_3d_override.z_far, false);
+
+ Vector2 screen_he = cm.get_viewport_half_extents();
+ ray = Vector3(((cpos.x / viewport_size.width) * 2.0 - 1.0) * screen_he.x, ((1.0 - (cpos.y / viewport_size.height)) * 2.0 - 1.0) * screen_he.y, -camera_3d_override.z_near).normalized();
+ }
+
+ return ray;
+}
+
+Vector3 Viewport::camera_3d_override_project_ray_origin(const Point2 &p_pos) const {
+ ERR_READ_THREAD_GUARD_V(Vector3());
+ Size2 viewport_size = get_camera_rect_size();
+ Vector2 cpos = get_camera_coords(p_pos);
+ ERR_FAIL_COND_V(viewport_size.y == 0, Vector3());
+
+ if (camera_3d_override.projection == Camera3DOverrideData::PROJECTION_ORTHOGONAL) {
+ Vector2 pos = cpos / viewport_size;
+ real_t vsize, hsize;
+ hsize = camera_3d_override.size * viewport_size.aspect();
+ vsize = camera_3d_override.size;
+
+ Vector3 ray;
+ ray.x = pos.x * (hsize)-hsize / 2;
+ ray.y = (1.0 - pos.y) * (vsize)-vsize / 2;
+ ray.z = -camera_3d_override.z_near;
+ ray = camera_3d_override.transform.xform(ray);
+ return ray;
+ } else {
+ return camera_3d_override.transform.origin;
+ };
+}
+
Ref<World3D> Viewport::get_world_3d() const {
ERR_READ_THREAD_GUARD_V(Ref<World3D>());
return world_3d;
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index a18dc1f6f0..92691ccbec 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -401,6 +401,7 @@ private:
DefaultCanvasItemTextureRepeat default_canvas_item_texture_repeat = DEFAULT_CANVAS_ITEM_TEXTURE_REPEAT_DISABLED;
bool disable_input = false;
+ bool disable_input_override = false;
void _gui_call_input(Control *p_control, const Ref<InputEvent> &p_input);
void _gui_call_notification(Control *p_control, int p_what);
@@ -580,6 +581,8 @@ public:
void set_disable_input(bool p_disable);
bool is_input_disabled() const;
+ void set_disable_input_override(bool p_disable);
+
Vector2 get_mouse_position() const;
void warp_mouse(const Vector2 &p_position);
virtual void update_mouse_cursor_state();
@@ -770,6 +773,11 @@ public:
void set_camera_3d_override_perspective(real_t p_fovy_degrees, real_t p_z_near, real_t p_z_far);
void set_camera_3d_override_orthogonal(real_t p_size, real_t p_z_near, real_t p_z_far);
+ HashMap<StringName, real_t> get_camera_3d_override_properties() const;
+
+ Vector3 camera_3d_override_project_ray_normal(const Point2 &p_pos) const;
+ Vector3 camera_3d_override_project_ray_origin(const Point2 &p_pos) const;
+ Vector3 camera_3d_override_project_local_ray_normal(const Point2 &p_pos) const;
void set_disable_3d(bool p_disable);
bool is_3d_disabled() const;
diff --git a/scene/resources/2d/navigation_polygon.cpp b/scene/resources/2d/navigation_polygon.cpp
index 3dfa906e3b..37240e8038 100644
--- a/scene/resources/2d/navigation_polygon.cpp
+++ b/scene/resources/2d/navigation_polygon.cpp
@@ -36,7 +36,7 @@
#include "thirdparty/misc/polypartition.h"
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 NavigationPolygon::_edit_get_rect() const {
RWLockRead read_lock(rwlock);
if (rect_cache_dirty) {
@@ -79,7 +79,7 @@ bool NavigationPolygon::_edit_is_selected_on_click(const Point2 &p_point, double
}
return false;
}
-#endif
+#endif // DEBUG_ENABLED
void NavigationPolygon::set_vertices(const Vector<Vector2> &p_vertices) {
RWLockWrite write_lock(rwlock);
diff --git a/scene/resources/2d/navigation_polygon.h b/scene/resources/2d/navigation_polygon.h
index ed2c606c55..59e5eeed68 100644
--- a/scene/resources/2d/navigation_polygon.h
+++ b/scene/resources/2d/navigation_polygon.h
@@ -68,10 +68,11 @@ protected:
TypedArray<Vector<Vector2>> _get_outlines() const;
public:
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
Rect2 _edit_get_rect() const;
bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const;
-#endif
+#endif // DEBUG_ENABLED
+
enum SamplePartitionType {
SAMPLE_PARTITION_CONVEX_PARTITION = 0,
SAMPLE_PARTITION_TRIANGULATE,
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/convex_polygon_shape_3d.cpp b/scene/resources/3d/convex_polygon_shape_3d.cpp
index 3bfeeca461..586d5f4678 100644
--- a/scene/resources/3d/convex_polygon_shape_3d.cpp
+++ b/scene/resources/3d/convex_polygon_shape_3d.cpp
@@ -35,7 +35,7 @@
Vector<Vector3> ConvexPolygonShape3D::get_debug_mesh_lines() const {
Vector<Vector3> poly_points = get_points();
- if (poly_points.size() > 3) {
+ if (poly_points.size() > 1) { // Need at least 2 points for a line.
Vector<Vector3> varr = Variant(poly_points);
Geometry3D::MeshData md;
Error err = ConvexHullComputer::convex_hull(varr, md);
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/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..f1bc83eb42 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -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 3c04d66668..6bf87b9163 100644
--- a/scu_builders.py
+++ b/scu_builders.py
@@ -290,11 +290,16 @@ def generate_scu_files(max_includes_per_scu):
process_folder(["drivers/unix"])
process_folder(["drivers/png"])
+ process_folder(["drivers/gles3/effects"])
+ process_folder(["drivers/gles3/storage"])
+
process_folder(["editor"], ["file_system_dock", "editor_resource_preview"], 32)
process_folder(["editor/debugger"])
process_folder(["editor/debugger/debug_adapter"])
process_folder(["editor/export"])
process_folder(["editor/gui"])
+ process_folder(["editor/themes"])
+ process_folder(["editor/project_manager"])
process_folder(["editor/import"])
process_folder(["editor/import/3d"])
process_folder(["editor/plugins"])
@@ -308,12 +313,15 @@ def generate_scu_files(max_includes_per_scu):
process_folder(["platform/web/export"])
process_folder(["platform/windows/export"])
+ process_folder(["modules/lightmapper_rd"])
process_folder(["modules/gltf"])
process_folder(["modules/gltf/structures"])
process_folder(["modules/gltf/editor"])
process_folder(["modules/gltf/extensions"])
process_folder(["modules/gltf/extensions/physics"])
process_folder(["modules/navigation"])
+ process_folder(["modules/navigation/2d"])
+ process_folder(["modules/navigation/3d"])
process_folder(["modules/webrtc"])
process_folder(["modules/websocket"])
process_folder(["modules/gridmap"])
@@ -322,6 +330,8 @@ def generate_scu_files(max_includes_per_scu):
process_folder(["modules/openxr"], ["register_types"])
process_folder(["modules/openxr/action_map"])
process_folder(["modules/openxr/editor"])
+ process_folder(["modules/openxr/extensions"])
+ process_folder(["modules/openxr/scene"])
process_folder(["modules/godot_physics_2d"])
process_folder(["modules/godot_physics_3d"])
process_folder(["modules/godot_physics_3d/joints"])
@@ -340,19 +350,26 @@ def generate_scu_files(max_includes_per_scu):
process_folder(["scene/animation"])
process_folder(["scene/gui"])
process_folder(["scene/main"])
+ process_folder(["scene/theme"])
process_folder(["scene/resources"])
process_folder(["scene/resources/2d"])
+ process_folder(["scene/resources/2d/skeleton"])
process_folder(["scene/resources/3d"])
process_folder(["servers"])
process_folder(["servers/rendering"])
+ process_folder(["servers/rendering/dummy/storage"])
process_folder(["servers/rendering/storage"])
process_folder(["servers/rendering/renderer_rd"])
process_folder(["servers/rendering/renderer_rd/effects"])
process_folder(["servers/rendering/renderer_rd/environment"])
process_folder(["servers/rendering/renderer_rd/storage_rd"])
+ process_folder(["servers/rendering/renderer_rd/forward_clustered"])
+ process_folder(["servers/rendering/renderer_rd/forward_mobile"])
process_folder(["servers/audio"])
process_folder(["servers/audio/effects"])
+ process_folder(["servers/navigation"])
+ process_folder(["servers/xr"])
# Finally change back the path to the calling folder
os.chdir(curr_folder)
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.cpp b/servers/display_server.cpp
index 7059e7ed49..82ac62bc9f 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -1056,6 +1056,7 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(FEATURE_NATIVE_HELP);
BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_INPUT);
BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_FILE);
+ BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_FILE_EXTRA);
BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE);
BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN);
diff --git a/servers/display_server.h b/servers/display_server.h
index 670afb3646..916c006f01 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -152,6 +152,7 @@ public:
FEATURE_NATIVE_HELP,
FEATURE_NATIVE_DIALOG_INPUT,
FEATURE_NATIVE_DIALOG_FILE,
+ FEATURE_NATIVE_DIALOG_FILE_EXTRA,
};
virtual bool has_feature(Feature p_feature) const = 0;
@@ -360,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/core/math/test_aabb.h b/tests/core/math/test_aabb.h
index 741e6af5d4..eb6977fd69 100644
--- a/tests/core/math/test_aabb.h
+++ b/tests/core/math/test_aabb.h
@@ -48,7 +48,7 @@ TEST_CASE("[AABB] Constructor methods") {
TEST_CASE("[AABB] String conversion") {
CHECK_MESSAGE(
- String(AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6))) == "[P: (-1.5, 2, -2.5), S: (4, 5, 6)]",
+ String(AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6))) == "[P: (-1.5, 2.0, -2.5), S: (4.0, 5.0, 6.0)]",
"The string representation should match the expected value.");
}
diff --git a/tests/core/math/test_color.h b/tests/core/math/test_color.h
index bd2d4f40e5..3d36079102 100644
--- a/tests/core/math/test_color.h
+++ b/tests/core/math/test_color.h
@@ -140,7 +140,7 @@ TEST_CASE("[Color] Conversion methods") {
cyan.to_rgba64() == 0x0000'ffff'ffff'ffff,
"The returned 64-bit BGR number should match the expected value.");
CHECK_MESSAGE(
- String(cyan) == "(0, 1, 1, 1)",
+ String(cyan) == "(0.0, 1.0, 1.0, 1.0)",
"The string representation should match the expected value.");
}
diff --git a/tests/core/math/test_rect2.h b/tests/core/math/test_rect2.h
index c4368808a6..a93bfeb71b 100644
--- a/tests/core/math/test_rect2.h
+++ b/tests/core/math/test_rect2.h
@@ -57,7 +57,7 @@ TEST_CASE("[Rect2] Constructor methods") {
TEST_CASE("[Rect2] String conversion") {
// Note: This also depends on the Vector2 string representation.
CHECK_MESSAGE(
- String(Rect2(0, 100, 1280, 720)) == "[P: (0, 100), S: (1280, 720)]",
+ String(Rect2(0, 100, 1280, 720)) == "[P: (0.0, 100.0), S: (1280.0, 720.0)]",
"The string representation should match the expected value.");
}
diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h
index 8d6137cf62..49afc55c64 100644
--- a/tests/core/string/test_string.h
+++ b/tests/core/string/test_string.h
@@ -455,9 +455,9 @@ TEST_CASE("[String] Erasing") {
}
TEST_CASE("[String] Number to string") {
- CHECK(String::num(0) == "0");
- CHECK(String::num(0.0) == "0"); // No trailing zeros.
- CHECK(String::num(-0.0) == "-0"); // Includes sign even for zero.
+ CHECK(String::num(0) == "0.0"); // The method takes double, so always add zeros.
+ CHECK(String::num(0.0) == "0.0");
+ CHECK(String::num(-0.0) == "-0.0"); // Includes sign even for zero.
CHECK(String::num(3.141593) == "3.141593");
CHECK(String::num(3.141593, 3) == "3.142");
CHECK(String::num(42.100023, 4) == "42.1"); // No trailing zeros.
@@ -492,15 +492,15 @@ TEST_CASE("[String] Number to string") {
CHECK(String::num_real(3.141593) == "3.141593");
CHECK(String::num_real(3.141) == "3.141"); // No trailing zeros.
#ifdef REAL_T_IS_DOUBLE
- CHECK_MESSAGE(String::num_real(123.456789) == "123.456789", "Prints the appropriate amount of digits for real_t = double.");
- CHECK_MESSAGE(String::num_real(-123.456789) == "-123.456789", "Prints the appropriate amount of digits for real_t = double.");
- CHECK_MESSAGE(String::num_real(Math_PI) == "3.14159265358979", "Prints the appropriate amount of digits for real_t = double.");
- CHECK_MESSAGE(String::num_real(3.1415f) == "3.1414999961853", "Prints more digits of 32-bit float when real_t = double (ones that would be reliable for double) and no trailing zero.");
+ CHECK_MESSAGE(String::num_real(real_t(123.456789)) == "123.456789", "Prints the appropriate amount of digits for real_t = double.");
+ CHECK_MESSAGE(String::num_real(real_t(-123.456789)) == "-123.456789", "Prints the appropriate amount of digits for real_t = double.");
+ CHECK_MESSAGE(String::num_real(real_t(Math_PI)) == "3.14159265358979", "Prints the appropriate amount of digits for real_t = double.");
+ CHECK_MESSAGE(String::num_real(real_t(3.1415f)) == "3.1414999961853", "Prints more digits of 32-bit float when real_t = double (ones that would be reliable for double) and no trailing zero.");
#else
- CHECK_MESSAGE(String::num_real(123.456789) == "123.4568", "Prints the appropriate amount of digits for real_t = float.");
- CHECK_MESSAGE(String::num_real(-123.456789) == "-123.4568", "Prints the appropriate amount of digits for real_t = float.");
- CHECK_MESSAGE(String::num_real(Math_PI) == "3.141593", "Prints the appropriate amount of digits for real_t = float.");
- CHECK_MESSAGE(String::num_real(3.1415f) == "3.1415", "Prints only reliable digits of 32-bit float when real_t = float.");
+ CHECK_MESSAGE(String::num_real(real_t(123.456789)) == "123.4568", "Prints the appropriate amount of digits for real_t = float.");
+ CHECK_MESSAGE(String::num_real(real_t(-123.456789)) == "-123.4568", "Prints the appropriate amount of digits for real_t = float.");
+ CHECK_MESSAGE(String::num_real(real_t(Math_PI)) == "3.141593", "Prints the appropriate amount of digits for real_t = float.");
+ CHECK_MESSAGE(String::num_real(real_t(3.1415f)) == "3.1415", "Prints only reliable digits of 32-bit float when real_t = float.");
#endif // REAL_T_IS_DOUBLE
// Checks doubles with many decimal places.
@@ -509,7 +509,7 @@ TEST_CASE("[String] Number to string") {
CHECK(String::num(-0.0000012345432123454321) == "-0.00000123454321");
CHECK(String::num(-10000.0000012345432123454321) == "-10000.0000012345");
CHECK(String::num(0.0000000000012345432123454321) == "0.00000000000123");
- CHECK(String::num(0.0000000000012345432123454321, 3) == "0");
+ CHECK(String::num(0.0000000000012345432123454321, 3) == "0.0");
// Note: When relevant (remainder > 0.5), the last digit gets rounded up,
// which can also lead to not include a trailing zero, e.g. "...89" -> "...9".
diff --git a/tests/core/variant/test_variant_utility.h b/tests/core/variant/test_variant_utility.h
index 93458b63f4..34b4880d51 100644
--- a/tests/core/variant/test_variant_utility.h
+++ b/tests/core/variant/test_variant_utility.h
@@ -89,7 +89,7 @@ TEST_CASE("[VariantUtility] Type conversion") {
converted = VariantUtilityFunctions::type_convert(basis, Variant::Type::STRING);
CHECK(converted.get_type() == Variant::Type::STRING);
- CHECK(converted == Variant("[X: (1.2, 0, 0), Y: (0, 3.4, 0), Z: (0, 0, 5.6)]"));
+ CHECK(converted == Variant("[X: (1.2, 0.0, 0.0), Y: (0.0, 3.4, 0.0), Z: (0.0, 0.0, 5.6)]"));
}
{
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)