summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.git-blame-ignore-revs3
-rw-r--r--.pre-commit-config.yaml4
-rw-r--r--SConstruct20
-rw-r--r--core/extension/gdextension_interface.cpp2
-rw-r--r--core/extension/gdextension_interface.h4
-rw-r--r--core/input/input_event.cpp2
-rw-r--r--core/io/dir_access.h4
-rw-r--r--core/io/file_access.h4
-rw-r--r--core/io/image.cpp10
-rw-r--r--core/io/resource.cpp1
-rw-r--r--core/math/aabb.h2
-rw-r--r--core/math/bvh.h2
-rw-r--r--core/math/bvh_abb.h2
-rw-r--r--core/math/bvh_tree.h8
-rw-r--r--core/math/delaunay_3d.h12
-rw-r--r--core/math/disjoint_set.h14
-rw-r--r--core/math/dynamic_bvh.h21
-rw-r--r--core/math/expression.h2
-rw-r--r--core/math/geometry_2d.h6
-rw-r--r--core/math/projection.h2
-rw-r--r--core/math/random_pcg.h2
-rw-r--r--core/math/rect2.h14
-rw-r--r--core/math/rect2i.h14
-rw-r--r--core/math/transform_interpolator.cpp76
-rw-r--r--core/math/transform_interpolator.h46
-rw-r--r--core/object/callable_method_pointer.h20
-rw-r--r--core/object/class_db.h32
-rw-r--r--core/object/message_queue.cpp18
-rw-r--r--core/object/method_bind.h42
-rw-r--r--core/object/object.cpp85
-rw-r--r--core/object/object.h4
-rw-r--r--core/object/ref_counted.h20
-rw-r--r--core/object/script_language.cpp19
-rw-r--r--core/object/script_language.h2
-rw-r--r--core/object/script_language_extension.h9
-rw-r--r--core/object/worker_thread_pool.h8
-rw-r--r--core/os/condition_variable.h4
-rw-r--r--core/os/main_loop.h1
-rw-r--r--core/os/memory.h10
-rw-r--r--core/os/mutex.h8
-rw-r--r--core/os/os.h3
-rw-r--r--core/string/ustring.cpp129
-rw-r--r--core/string/ustring.h15
-rw-r--r--core/templates/bin_sorted_array.h2
-rw-r--r--core/templates/command_queue_mt.h68
-rw-r--r--core/templates/cowdata.h28
-rw-r--r--core/templates/hash_map.h10
-rw-r--r--core/templates/hash_set.h6
-rw-r--r--core/templates/hashfuncs.h12
-rw-r--r--core/templates/list.h12
-rw-r--r--core/templates/local_vector.h22
-rw-r--r--core/templates/lru.h2
-rw-r--r--core/templates/oa_hash_map.h6
-rw-r--r--core/templates/paged_allocator.h6
-rw-r--r--core/templates/paged_array.h4
-rw-r--r--core/templates/pair.h18
-rw-r--r--core/templates/pooled_list.h4
-rw-r--r--core/templates/rb_map.h2
-rw-r--r--core/templates/rb_set.h2
-rw-r--r--core/templates/rid_owner.h6
-rw-r--r--core/templates/safe_list.h2
-rw-r--r--core/templates/safe_refcount.h2
-rw-r--r--core/templates/search_array.h2
-rw-r--r--core/templates/self_list.h4
-rw-r--r--core/templates/simple_type.h8
-rw-r--r--core/templates/sort_array.h4
-rw-r--r--core/templates/vector.h16
-rw-r--r--core/templates/vmap.h2
-rw-r--r--core/templates/vset.h2
-rw-r--r--core/typedefs.h6
-rw-r--r--core/variant/binder_common.h142
-rw-r--r--core/variant/method_ptrcall.h6
-rw-r--r--core/variant/native_ptr.h12
-rw-r--r--core/variant/type_info.h4
-rw-r--r--core/variant/typed_array.h14
-rw-r--r--core/variant/variant.cpp6
-rw-r--r--core/variant/variant.h2
-rw-r--r--core/variant/variant_call.cpp140
-rw-r--r--core/variant/variant_construct.cpp2
-rw-r--r--core/variant/variant_construct.h12
-rw-r--r--core/variant/variant_destruct.cpp2
-rw-r--r--core/variant/variant_destruct.h2
-rw-r--r--core/variant/variant_internal.h20
-rw-r--r--core/variant/variant_op.cpp2
-rw-r--r--core/variant/variant_op.h80
-rw-r--r--core/variant/variant_parser.cpp2
-rw-r--r--core/variant/variant_parser.h2
-rw-r--r--core/variant/variant_setget.cpp6
-rw-r--r--core/variant/variant_utility.cpp38
-rw-r--r--doc/classes/AnimationNode.xml11
-rw-r--r--doc/classes/AnimationNodeAnimation.xml18
-rw-r--r--doc/classes/AnimationNodeOneShot.xml5
-rw-r--r--doc/classes/AnimationNodeStateMachineTransition.xml4
-rw-r--r--doc/classes/AnimationNodeTransition.xml16
-rw-r--r--doc/classes/Control.xml1
-rw-r--r--doc/classes/EditorSettings.xml6
-rw-r--r--doc/classes/InputEvent.xml7
-rw-r--r--doc/classes/InputEventScreenDrag.xml2
-rw-r--r--doc/classes/InputEventScreenTouch.xml2
-rw-r--r--doc/classes/NativeMenu.xml142
-rw-r--r--doc/classes/NavigationMeshSourceGeometryData2D.xml35
-rw-r--r--doc/classes/NavigationMeshSourceGeometryData3D.xml41
-rw-r--r--doc/classes/NavigationObstacle2D.xml17
-rw-r--r--doc/classes/NavigationObstacle3D.xml17
-rw-r--r--doc/classes/Node.xml40
-rw-r--r--doc/classes/Parallax2D.xml1
-rw-r--r--doc/classes/ParallaxLayer.xml1
-rw-r--r--doc/classes/PopupMenu.xml3
-rw-r--r--doc/classes/ProjectSettings.xml8
-rw-r--r--doc/classes/RayCast2D.xml5
-rw-r--r--doc/classes/RayCast3D.xml5
-rw-r--r--doc/classes/RenderingServer.xml75
-rw-r--r--doc/classes/SceneTree.xml4
-rw-r--r--doc/classes/ScriptExtension.xml1
-rw-r--r--doc/classes/String.xml24
-rw-r--r--doc/classes/StringName.xml24
-rw-r--r--doc/classes/Time.xml4
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.cpp13
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.h2
-rw-r--r--drivers/gles3/storage/texture_storage.cpp3
-rw-r--r--drivers/vulkan/rendering_device_driver_vulkan.cpp3
-rw-r--r--editor/animation_bezier_editor.cpp205
-rw-r--r--editor/animation_bezier_editor.h9
-rw-r--r--editor/animation_track_editor.cpp193
-rw-r--r--editor/animation_track_editor.h17
-rw-r--r--editor/code_editor.cpp6
-rw-r--r--editor/code_editor.h2
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_parser.cpp4
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_parser.h3
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_protocol.cpp8
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_protocol.h4
-rw-r--r--editor/debugger/script_editor_debugger.cpp4
-rw-r--r--editor/editor_atlas_packer.cpp3
-rw-r--r--editor/editor_data.cpp8
-rw-r--r--editor/editor_data.h2
-rw-r--r--editor/editor_dock_manager.cpp1171
-rw-r--r--editor/editor_dock_manager.h123
-rw-r--r--editor/editor_file_system.cpp18
-rw-r--r--editor/editor_file_system.h8
-rw-r--r--editor/editor_help_search.h2
-rw-r--r--editor/editor_inspector.cpp6
-rw-r--r--editor/editor_log.cpp3
-rw-r--r--editor/editor_node.cpp52
-rw-r--r--editor/editor_plugin.h4
-rw-r--r--editor/editor_properties_array_dict.cpp510
-rw-r--r--editor/editor_properties_array_dict.h43
-rw-r--r--editor/editor_resource_picker.cpp2
-rw-r--r--editor/editor_settings.cpp2
-rw-r--r--editor/filesystem_dock.cpp67
-rw-r--r--editor/filesystem_dock.h7
-rw-r--r--editor/gui/editor_bottom_panel.cpp6
-rw-r--r--editor/gui/editor_file_dialog.cpp4
-rw-r--r--editor/gui/editor_spin_slider.cpp40
-rw-r--r--editor/gui/scene_tree_editor.cpp8
-rw-r--r--editor/icons/AnimationAutoFit.svg2
-rw-r--r--editor/icons/AnimationAutoFitBezier.svg2
-rw-r--r--editor/import/3d/resource_importer_scene.h8
-rw-r--r--editor/plugins/animation_blend_tree_editor_plugin.cpp9
-rw-r--r--editor/plugins/animation_state_machine_editor.cpp14
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp14
-rw-r--r--editor/plugins/editor_preview_plugins.cpp2
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp27
-rw-r--r--editor/plugins/script_editor_plugin.cpp2
-rw-r--r--editor/plugins/script_text_editor.cpp5
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.cpp6
-rw-r--r--editor/plugins/tiles/tile_atlas_view.cpp3
-rw-r--r--editor/plugins/version_control_editor_plugin.cpp3
-rw-r--r--editor/themes/editor_theme_manager.cpp41
-rw-r--r--main/main.cpp101
-rw-r--r--main/main.h4
-rw-r--r--main/main_timer_sync.cpp11
-rwxr-xr-xmisc/scripts/copyright_headers.py94
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml6
-rw-r--r--modules/gdscript/gdscript_editor.cpp10
-rw-r--r--modules/gdscript/gdscript_parser.cpp2
-rw-r--r--modules/gdscript/gdscript_parser.h8
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/emit_after_await.gd12
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/emit_after_await.out1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/emit_one_shot_is_non_recursive.gd22
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/emit_one_shot_is_non_recursive.out5
-rw-r--r--modules/gltf/gltf_document.cpp444
-rw-r--r--modules/gltf/gltf_document.h26
-rw-r--r--modules/gltf/gltf_template_convert.h12
-rw-r--r--modules/gltf/structures/gltf_animation.h2
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.cpp3
-rw-r--r--modules/multiplayer/scene_replication_interface.h2
-rw-r--r--modules/navigation/2d/nav_mesh_generator_2d.cpp105
-rw-r--r--modules/navigation/2d/nav_mesh_generator_2d.h1
-rw-r--r--modules/navigation/3d/nav_mesh_generator_3d.cpp91
-rw-r--r--modules/navigation/3d/nav_mesh_generator_3d.h1
-rw-r--r--modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml19
-rw-r--r--modules/openxr/extensions/openxr_composition_layer_depth_extension.cpp11
-rw-r--r--modules/openxr/extensions/openxr_composition_layer_depth_extension.h4
-rw-r--r--modules/openxr/extensions/openxr_composition_layer_provider.h4
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper_extension.cpp20
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper_extension.h8
-rw-r--r--modules/openxr/openxr_api.cpp32
-rw-r--r--modules/openxr/openxr_api.h9
-rw-r--r--modules/regex/SCsub3
-rw-r--r--modules/websocket/packet_buffer.h2
-rw-r--r--platform/ios/detect.py1
-rw-r--r--platform/ios/godot_ios.mm11
-rw-r--r--platform/linuxbsd/godot_linuxbsd.cpp13
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp9
-rw-r--r--platform/macos/display_server_macos.mm9
-rw-r--r--platform/macos/godot_main_macos.mm19
-rw-r--r--platform/macos/native_menu_macos.h1
-rw-r--r--platform/macos/native_menu_macos.mm10
-rw-r--r--platform/web/web_main.cpp16
-rw-r--r--platform/windows/SCsub1
-rw-r--r--platform/windows/display_server_windows.cpp40
-rw-r--r--platform/windows/display_server_windows.h16
-rw-r--r--platform/windows/export/export_plugin.cpp6
-rw-r--r--platform/windows/godot_windows.cpp8
-rw-r--r--platform/windows/native_menu_windows.cpp1165
-rw-r--r--platform/windows/native_menu_windows.h151
-rw-r--r--platform/windows/os_windows.h2
-rw-r--r--scene/2d/camera_2d.cpp107
-rw-r--r--scene/2d/camera_2d.h8
-rw-r--r--scene/2d/light_2d.cpp15
-rw-r--r--scene/2d/light_2d.h1
-rw-r--r--scene/2d/light_occluder_2d.cpp15
-rw-r--r--scene/2d/light_occluder_2d.h2
-rw-r--r--scene/2d/navigation_obstacle_2d.cpp30
-rw-r--r--scene/2d/navigation_obstacle_2d.h9
-rw-r--r--scene/2d/parallax_2d.cpp3
-rw-r--r--scene/2d/parallax_layer.cpp3
-rw-r--r--scene/2d/physics/rigid_body_2d.cpp23
-rw-r--r--scene/2d/sprite_2d.cpp3
-rw-r--r--scene/2d/tile_map_layer.cpp17
-rw-r--r--scene/3d/decal.cpp2
-rw-r--r--scene/3d/fog_volume.cpp4
-rw-r--r--scene/3d/gpu_particles_collision_3d.cpp10
-rw-r--r--scene/3d/navigation_obstacle_3d.cpp31
-rw-r--r--scene/3d/navigation_obstacle_3d.h9
-rw-r--r--scene/3d/occluder_instance_3d.cpp2
-rw-r--r--scene/3d/physics/rigid_body_3d.cpp21
-rw-r--r--scene/3d/voxel_gi.cpp2
-rw-r--r--scene/animation/animation_blend_space_1d.cpp39
-rw-r--r--scene/animation/animation_blend_space_1d.h3
-rw-r--r--scene/animation/animation_blend_space_2d.cpp34
-rw-r--r--scene/animation/animation_blend_space_2d.h3
-rw-r--r--scene/animation/animation_blend_tree.cpp504
-rw-r--r--scene/animation/animation_blend_tree.h61
-rw-r--r--scene/animation/animation_node_state_machine.cpp138
-rw-r--r--scene/animation/animation_node_state_machine.h19
-rw-r--r--scene/animation/animation_tree.cpp127
-rw-r--r--scene/animation/animation_tree.h54
-rw-r--r--scene/gui/aspect_ratio_container.cpp6
-rw-r--r--scene/gui/center_container.cpp3
-rw-r--r--scene/gui/check_box.cpp16
-rw-r--r--scene/gui/check_button.cpp4
-rw-r--r--scene/gui/code_edit.cpp20
-rw-r--r--scene/gui/control.cpp19
-rw-r--r--scene/gui/dialogs.cpp11
-rw-r--r--scene/gui/file_dialog.cpp4
-rw-r--r--scene/gui/graph_edit.cpp3
-rw-r--r--scene/gui/graph_element.cpp3
-rw-r--r--scene/gui/label.cpp126
-rw-r--r--scene/gui/label.h7
-rw-r--r--scene/gui/menu_bar.cpp10
-rw-r--r--scene/gui/menu_bar.h2
-rw-r--r--scene/gui/panel_container.cpp3
-rw-r--r--scene/gui/popup.cpp3
-rw-r--r--scene/gui/popup_menu.cpp63
-rw-r--r--scene/gui/popup_menu.h5
-rw-r--r--scene/gui/progress_bar.cpp6
-rw-r--r--scene/gui/rich_text_label.cpp2
-rw-r--r--scene/gui/scroll_container.cpp3
-rw-r--r--scene/gui/subviewport_container.cpp3
-rw-r--r--scene/gui/tab_container.cpp3
-rw-r--r--scene/gui/texture_button.cpp4
-rw-r--r--scene/gui/texture_progress_bar.cpp3
-rw-r--r--scene/main/canvas_item.cpp23
-rw-r--r--scene/main/canvas_item.h2
-rw-r--r--scene/main/node.cpp181
-rw-r--r--scene/main/node.h76
-rw-r--r--scene/main/scene_tree.cpp30
-rw-r--r--scene/main/scene_tree.h7
-rw-r--r--scene/main/viewport.cpp6
-rw-r--r--scene/main/window.cpp8
-rw-r--r--scene/resources/2d/polygon_path_finder.cpp3
-rw-r--r--scene/resources/2d/tile_set.cpp67
-rw-r--r--scene/resources/2d/tile_set.h8
-rw-r--r--scene/resources/3d/primitive_meshes.cpp6
-rw-r--r--scene/resources/animation.cpp10
-rw-r--r--scene/resources/animation.h16
-rw-r--r--scene/resources/audio_stream_wav.cpp2
-rw-r--r--scene/resources/audio_stream_wav.h2
-rw-r--r--scene/resources/material.cpp989
-rw-r--r--scene/resources/navigation_mesh_source_geometry_data_2d.cpp115
-rw-r--r--scene/resources/navigation_mesh_source_geometry_data_2d.h24
-rw-r--r--scene/resources/navigation_mesh_source_geometry_data_3d.cpp127
-rw-r--r--scene/resources/navigation_mesh_source_geometry_data_3d.h26
-rw-r--r--scene/resources/navigation_polygon.cpp3
-rw-r--r--scene/resources/visual_shader.cpp97
-rw-r--r--scene/resources/visual_shader.h4
-rw-r--r--scu_builders.py2
-rw-r--r--servers/camera_server.h4
-rw-r--r--servers/native_menu.cpp8
-rw-r--r--servers/native_menu.h5
-rw-r--r--servers/physics_2d/godot_broad_phase_2d_bvh.h4
-rw-r--r--servers/physics_2d/godot_collision_solver_2d_sat.cpp2
-rw-r--r--servers/physics_3d/godot_broad_phase_3d_bvh.h4
-rw-r--r--servers/physics_3d/godot_collision_solver_3d_sat.cpp2
-rw-r--r--servers/physics_3d/godot_shape_3d.cpp4
-rw-r--r--servers/physics_3d/godot_space_3d.cpp2
-rw-r--r--servers/rendering/renderer_canvas_cull.cpp205
-rw-r--r--servers/rendering/renderer_canvas_cull.h40
-rw-r--r--servers/rendering/renderer_canvas_render.h38
-rw-r--r--servers/rendering/renderer_rd/cluster_builder_rd.cpp6
-rw-r--r--servers/rendering/renderer_rd/environment/fog.cpp8
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp13
-rw-r--r--servers/rendering/renderer_rd/storage_rd/texture_storage.cpp3
-rw-r--r--servers/rendering/renderer_scene_occlusion_cull.h2
-rw-r--r--servers/rendering/renderer_viewport.cpp45
-rw-r--r--servers/rendering/rendering_device.cpp2
-rw-r--r--servers/rendering/rendering_device.h2
-rw-r--r--servers/rendering/rendering_device_driver.h8
-rw-r--r--servers/rendering/rendering_server_default.cpp10
-rw-r--r--servers/rendering/rendering_server_default.h17
-rw-r--r--servers/rendering/shader_language.h2
-rw-r--r--servers/rendering/storage/variant_converters.h4
-rw-r--r--servers/rendering_server.cpp9
-rw-r--r--servers/rendering_server.h17
-rw-r--r--tests/core/string/test_string.h8
-rw-r--r--thirdparty/README.md3
-rw-r--r--thirdparty/pcre2/patches/sljit-macos11-conditional.patch31
-rw-r--r--thirdparty/pcre2/src/sljit/sljitExecAllocator.c414
-rw-r--r--thirdparty/pcre2/src/sljit/sljitProtExecAllocator.c474
-rw-r--r--thirdparty/pcre2/src/sljit/sljitWXExecAllocator.c204
331 files changed, 7874 insertions, 4424 deletions
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index eed5b661b8..ebfd3b515a 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -51,3 +51,6 @@ bd282ff43f23fe845f29a3e25c8efc01bd65ffb0
# Add "Godot Engine contributors" copyright line
df61dc4b2bd54a5a40c515493c76f5a458e5b541
+
+# Enforce template syntax `typename` over `class`
+9903e6779b70fc03aae70a37b9cf053f4f355b91
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 9e9da655cc..7122363f77 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -39,10 +39,10 @@ repos:
entry: python3 misc/scripts/copyright_headers.py
exclude: |
(?x)^(
- tests/python_build.*|
.*thirdparty.*|
- .*platform/android/java/lib/src/com.*|
.*-so_wrap.*|
+ core/math/bvh_.*\.inc$|
+ platform/android/java/lib/src/com.*|
platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.*|
platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper.*|
platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.*
diff --git a/SConstruct b/SConstruct
index c11fd50497..753cea40e3 100644
--- a/SConstruct
+++ b/SConstruct
@@ -956,25 +956,25 @@ if selected_platform in platform_list:
env.vs_incs = []
env.vs_srcs = []
- if env["compiledb"]:
- # Generating the compilation DB (`compile_commands.json`) requires SCons 4.0.0 or later.
- from SCons import __version__ as scons_raw_version
-
- scons_ver = env._get_major_minor_revision(scons_raw_version)
-
- if scons_ver < (4, 0, 0):
- print("The `compiledb=yes` option requires SCons 4.0 or later, but your version is %s." % scons_raw_version)
- Exit(255)
+ # CompileDB
+ from SCons import __version__ as scons_raw_version
+ scons_ver = env._get_major_minor_revision(scons_raw_version)
+ if env["compiledb"] and scons_ver < (4, 0, 0):
+ # Generating the compilation DB (`compile_commands.json`) requires SCons 4.0.0 or later.
+ print("The `compiledb=yes` option requires SCons 4.0 or later, but your version is %s." % scons_raw_version)
+ Exit(255)
+ if scons_ver >= (4, 0, 0):
env.Tool("compilation_db")
env.Alias("compiledb", env.CompilationDatabase())
+ # Threads
if env["threads"]:
env.Append(CPPDEFINES=["THREADS_ENABLED"])
+ # Build subdirs, the build order is dependent on link order.
Export("env")
- # Build subdirs, the build order is dependent on link order.
SConscript("core/SCsub")
SConscript("servers/SCsub")
SConscript("scene/SCsub")
diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp
index ca58d589bd..9b4aa98357 100644
--- a/core/extension/gdextension_interface.cpp
+++ b/core/extension/gdextension_interface.cpp
@@ -1302,6 +1302,7 @@ static GDExtensionScriptInstancePtr gdextension_script_instance_create(const GDE
info_3->get_property_type_func = p_info->get_property_type_func;
info_3->validate_property_func = nullptr;
info_3->has_method_func = p_info->has_method_func;
+ info_3->get_method_argument_count_func = nullptr;
info_3->call_func = p_info->call_func;
info_3->notification_func = nullptr;
info_3->to_string_func = p_info->to_string_func;
@@ -1341,6 +1342,7 @@ static GDExtensionScriptInstancePtr gdextension_script_instance_create2(const GD
info_3->get_property_type_func = p_info->get_property_type_func;
info_3->validate_property_func = nullptr;
info_3->has_method_func = p_info->has_method_func;
+ info_3->get_method_argument_count_func = nullptr;
info_3->call_func = p_info->call_func;
info_3->notification_func = p_info->notification_func;
info_3->to_string_func = p_info->to_string_func;
diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h
index c863507019..e9c570e994 100644
--- a/core/extension/gdextension_interface.h
+++ b/core/extension/gdextension_interface.h
@@ -535,6 +535,8 @@ typedef void (*GDExtensionScriptInstanceFreeMethodList2)(GDExtensionScriptInstan
typedef GDExtensionBool (*GDExtensionScriptInstanceHasMethod)(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionConstStringNamePtr p_name);
+typedef GDExtensionInt (*GDExtensionScriptInstanceGetMethodArgumentCount)(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionBool *r_is_valid);
+
typedef void (*GDExtensionScriptInstanceCall)(GDExtensionScriptInstanceDataPtr p_self, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error);
typedef void (*GDExtensionScriptInstanceNotification)(GDExtensionScriptInstanceDataPtr p_instance, int32_t p_what); // Deprecated. Use GDExtensionScriptInstanceNotification2 instead.
typedef void (*GDExtensionScriptInstanceNotification2)(GDExtensionScriptInstanceDataPtr p_instance, int32_t p_what, GDExtensionBool p_reversed);
@@ -654,6 +656,8 @@ typedef struct {
GDExtensionScriptInstanceHasMethod has_method_func;
+ GDExtensionScriptInstanceGetMethodArgumentCount get_method_argument_count_func;
+
GDExtensionScriptInstanceCall call_func;
GDExtensionScriptInstanceNotification2 notification_func;
diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp
index bd1fde5a85..bf1de8d3b2 100644
--- a/core/input/input_event.cpp
+++ b/core/input/input_event.cpp
@@ -132,6 +132,8 @@ void InputEvent::_bind_methods() {
ClassDB::bind_method(D_METHOD("xformed_by", "xform", "local_ofs"), &InputEvent::xformed_by, DEFVAL(Vector2()));
ADD_PROPERTY(PropertyInfo(Variant::INT, "device"), "set_device", "get_device");
+
+ BIND_CONSTANT(DEVICE_ID_EMULATION);
}
///////////////////////////////////
diff --git a/core/io/dir_access.h b/core/io/dir_access.h
index d175235b98..e9c864c56b 100644
--- a/core/io/dir_access.h
+++ b/core/io/dir_access.h
@@ -70,7 +70,7 @@ protected:
AccessType get_access_type() const;
virtual String fix_path(const String &p_path) const;
- template <class T>
+ template <typename T>
static Ref<DirAccess> _create_builtin() {
return memnew(T);
}
@@ -130,7 +130,7 @@ public:
static Ref<DirAccess> create(AccessType p_access);
static Error get_open_error();
- template <class T>
+ template <typename T>
static void make_default(AccessType p_access) {
create_func[p_access] = _create_builtin<T>;
}
diff --git a/core/io/file_access.h b/core/io/file_access.h
index 7d346ca2f4..122ae3b190 100644
--- a/core/io/file_access.h
+++ b/core/io/file_access.h
@@ -114,7 +114,7 @@ private:
AccessType _access_type = ACCESS_FILESYSTEM;
static CreateFunc create_func[ACCESS_MAX]; /** default file access creation function for a platform */
- template <class T>
+ template <typename T>
static Ref<FileAccess> _create_builtin() {
return memnew(T);
}
@@ -226,7 +226,7 @@ public:
static PackedByteArray _get_file_as_bytes(const String &p_path) { return get_file_as_bytes(p_path, &last_file_open_error); }
static String _get_file_as_string(const String &p_path) { return get_file_as_string(p_path, &last_file_open_error); }
- template <class T>
+ template <typename T>
static void make_default(AccessType p_access) {
create_func[p_access] = _create_builtin<T>;
}
diff --git a/core/io/image.cpp b/core/io/image.cpp
index b094290ac8..c454f06d67 100644
--- a/core/io/image.cpp
+++ b/core/io/image.cpp
@@ -679,7 +679,7 @@ static double _bicubic_interp_kernel(double x) {
return bc;
}
-template <int CC, class T>
+template <int CC, typename T>
static void _scale_cubic(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) {
// get source image size
int width = p_src_width;
@@ -766,7 +766,7 @@ static void _scale_cubic(const uint8_t *__restrict p_src, uint8_t *__restrict p_
}
}
-template <int CC, class T>
+template <int CC, typename T>
static void _scale_bilinear(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) {
enum {
FRAC_BITS = 8,
@@ -856,7 +856,7 @@ static void _scale_bilinear(const uint8_t *__restrict p_src, uint8_t *__restrict
}
}
-template <int CC, class T>
+template <int CC, typename T>
static void _scale_nearest(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) {
for (uint32_t i = 0; i < p_dst_height; i++) {
uint32_t src_yofs = i * p_src_height / p_dst_height;
@@ -883,7 +883,7 @@ static float _lanczos(float p_x) {
return Math::abs(p_x) >= LANCZOS_TYPE ? 0 : Math::sincn(p_x) * Math::sincn(p_x / LANCZOS_TYPE);
}
-template <int CC, class T>
+template <int CC, typename T>
static void _scale_lanczos(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) {
int32_t src_width = p_src_width;
int32_t src_height = p_src_height;
@@ -1665,7 +1665,7 @@ bool Image::_can_modify(Format p_format) const {
return p_format <= FORMAT_RGBE9995;
}
-template <class Component, int CC, bool renormalize,
+template <typename Component, int CC, bool renormalize,
void (*average_func)(Component &, const Component &, const Component &, const Component &, const Component &),
void (*renormalize_func)(Component *)>
static void _generate_po2_mipmap(const Component *p_src, Component *p_dst, uint32_t p_width, uint32_t p_height) {
diff --git a/core/io/resource.cpp b/core/io/resource.cpp
index 5edb045760..dc974a545a 100644
--- a/core/io/resource.cpp
+++ b/core/io/resource.cpp
@@ -214,6 +214,7 @@ Error Resource::copy_from(const Ref<Resource> &p_resource) {
}
return OK;
}
+
void Resource::reload_from_file() {
String path = get_path();
if (!path.is_resource_file()) {
diff --git a/core/math/aabb.h b/core/math/aabb.h
index 7927c431eb..48a883e64c 100644
--- a/core/math/aabb.h
+++ b/core/math/aabb.h
@@ -101,7 +101,7 @@ struct _NO_DISCARD_ AABB {
_FORCE_INLINE_ void expand_to(const Vector3 &p_vector); /** expand to contain a point if necessary */
_FORCE_INLINE_ AABB abs() const {
- return AABB(Vector3(position.x + MIN(size.x, (real_t)0), position.y + MIN(size.y, (real_t)0), position.z + MIN(size.z, (real_t)0)), size.abs());
+ return AABB(position + size.min(Vector3()), size.abs());
}
Variant intersects_segment_bind(const Vector3 &p_from, const Vector3 &p_to) const;
diff --git a/core/math/bvh.h b/core/math/bvh.h
index 8fbbd8e7fd..4815466e89 100644
--- a/core/math/bvh.h
+++ b/core/math/bvh.h
@@ -57,7 +57,7 @@
#define BVHTREE_CLASS BVH_Tree<T, NUM_TREES, 2, MAX_ITEMS, USER_PAIR_TEST_FUNCTION, USER_CULL_TEST_FUNCTION, USE_PAIRS, BOUNDS, POINT>
#define BVH_LOCKED_FUNCTION BVHLockedFunction _lock_guard(&_mutex, BVH_THREAD_SAFE &&_thread_safe);
-template <class T, int NUM_TREES = 1, bool USE_PAIRS = false, int MAX_ITEMS = 32, class USER_PAIR_TEST_FUNCTION = BVH_DummyPairTestFunction<T>, class USER_CULL_TEST_FUNCTION = BVH_DummyCullTestFunction<T>, class BOUNDS = AABB, class POINT = Vector3, bool BVH_THREAD_SAFE = true>
+template <typename T, int NUM_TREES = 1, bool USE_PAIRS = false, int MAX_ITEMS = 32, typename USER_PAIR_TEST_FUNCTION = BVH_DummyPairTestFunction<T>, typename USER_CULL_TEST_FUNCTION = BVH_DummyCullTestFunction<T>, typename BOUNDS = AABB, typename POINT = Vector3, bool BVH_THREAD_SAFE = true>
class BVH_Manager {
public:
// note we are using uint32_t instead of BVHHandle, losing type safety, but this
diff --git a/core/math/bvh_abb.h b/core/math/bvh_abb.h
index fb0207e0bd..cec3dc90db 100644
--- a/core/math/bvh_abb.h
+++ b/core/math/bvh_abb.h
@@ -32,7 +32,7 @@
#define BVH_ABB_H
// special optimized version of axis aligned bounding box
-template <class BOUNDS = AABB, class POINT = Vector3>
+template <typename BOUNDS = AABB, typename POINT = Vector3>
struct BVH_ABB {
struct ConvexHull {
// convex hulls (optional)
diff --git a/core/math/bvh_tree.h b/core/math/bvh_tree.h
index ce296668db..0faa50555f 100644
--- a/core/math/bvh_tree.h
+++ b/core/math/bvh_tree.h
@@ -106,7 +106,7 @@ struct BVHHandle {
};
// helper class to make iterative versions of recursive functions
-template <class T>
+template <typename T>
class BVH_IterativeInfo {
public:
enum {
@@ -152,7 +152,7 @@ public:
}
};
-template <class T>
+template <typename T>
class BVH_DummyPairTestFunction {
public:
static bool user_collision_check(T *p_a, T *p_b) {
@@ -161,7 +161,7 @@ public:
}
};
-template <class T>
+template <typename T>
class BVH_DummyCullTestFunction {
public:
static bool user_cull_check(T *p_a, T *p_b) {
@@ -170,7 +170,7 @@ public:
}
};
-template <class T, int NUM_TREES, int MAX_CHILDREN, int MAX_ITEMS, class USER_PAIR_TEST_FUNCTION = BVH_DummyPairTestFunction<T>, class USER_CULL_TEST_FUNCTION = BVH_DummyCullTestFunction<T>, bool USE_PAIRS = false, class BOUNDS = AABB, class POINT = Vector3>
+template <typename T, int NUM_TREES, int MAX_CHILDREN, int MAX_ITEMS, typename USER_PAIR_TEST_FUNCTION = BVH_DummyPairTestFunction<T>, typename USER_CULL_TEST_FUNCTION = BVH_DummyCullTestFunction<T>, bool USE_PAIRS = false, typename BOUNDS = AABB, typename POINT = Vector3>
class BVH_Tree {
friend class BVH;
diff --git a/core/math/delaunay_3d.h b/core/math/delaunay_3d.h
index 7df8c37e3c..846acdecc3 100644
--- a/core/math/delaunay_3d.h
+++ b/core/math/delaunay_3d.h
@@ -281,9 +281,7 @@ public:
}
Vector3i grid_pos = Vector3i(points[i] * ACCEL_GRID_SIZE);
- grid_pos.x = CLAMP(grid_pos.x, 0, ACCEL_GRID_SIZE - 1);
- grid_pos.y = CLAMP(grid_pos.y, 0, ACCEL_GRID_SIZE - 1);
- grid_pos.z = CLAMP(grid_pos.z, 0, ACCEL_GRID_SIZE - 1);
+ grid_pos = grid_pos.clamp(Vector3i(), Vector3i(ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1));
for (List<Simplex *>::Element *E = acceleration_grid[grid_pos.x][grid_pos.y][grid_pos.z].front(); E;) {
List<Simplex *>::Element *N = E->next(); //may be deleted
@@ -339,12 +337,8 @@ public:
Vector3 extents = Vector3(radius2, radius2, radius2);
Vector3i from = Vector3i((center - extents) * ACCEL_GRID_SIZE);
Vector3i to = Vector3i((center + extents) * ACCEL_GRID_SIZE);
- from.x = CLAMP(from.x, 0, ACCEL_GRID_SIZE - 1);
- from.y = CLAMP(from.y, 0, ACCEL_GRID_SIZE - 1);
- from.z = CLAMP(from.z, 0, ACCEL_GRID_SIZE - 1);
- to.x = CLAMP(to.x, 0, ACCEL_GRID_SIZE - 1);
- to.y = CLAMP(to.y, 0, ACCEL_GRID_SIZE - 1);
- to.z = CLAMP(to.z, 0, ACCEL_GRID_SIZE - 1);
+ from = from.clamp(Vector3i(), Vector3i(ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1));
+ to = to.clamp(Vector3i(), Vector3i(ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1));
for (int32_t x = from.x; x <= to.x; x++) {
for (int32_t y = from.y; y <= to.y; y++) {
diff --git a/core/math/disjoint_set.h b/core/math/disjoint_set.h
index 2ece991fea..4348da992d 100644
--- a/core/math/disjoint_set.h
+++ b/core/math/disjoint_set.h
@@ -35,7 +35,7 @@
#include "core/templates/vector.h"
/* This DisjointSet class uses Find with path compression and Union by rank */
-template <typename T, class H = HashMapHasherDefault, class C = HashMapComparatorDefault<T>, class AL = DefaultAllocator>
+template <typename T, typename H = HashMapHasherDefault, typename C = HashMapComparatorDefault<T>, typename AL = DefaultAllocator>
class DisjointSet {
struct Element {
T object;
@@ -65,14 +65,14 @@ public:
/* FUNCTIONS */
-template <typename T, class H, class C, class AL>
+template <typename T, typename H, typename C, typename AL>
DisjointSet<T, H, C, AL>::~DisjointSet() {
for (KeyValue<T, Element *> &E : elements) {
memdelete_allocator<Element, AL>(E.value);
}
}
-template <typename T, class H, class C, class AL>
+template <typename T, typename H, typename C, typename AL>
typename DisjointSet<T, H, C, AL>::Element *DisjointSet<T, H, C, AL>::get_parent(Element *element) {
if (element->parent != element) {
element->parent = get_parent(element->parent);
@@ -81,7 +81,7 @@ typename DisjointSet<T, H, C, AL>::Element *DisjointSet<T, H, C, AL>::get_parent
return element->parent;
}
-template <typename T, class H, class C, class AL>
+template <typename T, typename H, typename C, typename AL>
typename DisjointSet<T, H, C, AL>::Element *DisjointSet<T, H, C, AL>::insert_or_get(T object) {
typename MapT::Iterator itr = elements.find(object);
if (itr != nullptr) {
@@ -96,7 +96,7 @@ typename DisjointSet<T, H, C, AL>::Element *DisjointSet<T, H, C, AL>::insert_or_
return new_element;
}
-template <typename T, class H, class C, class AL>
+template <typename T, typename H, typename C, typename AL>
void DisjointSet<T, H, C, AL>::create_union(T a, T b) {
Element *x = insert_or_get(a);
Element *y = insert_or_get(b);
@@ -121,7 +121,7 @@ void DisjointSet<T, H, C, AL>::create_union(T a, T b) {
}
}
-template <typename T, class H, class C, class AL>
+template <typename T, typename H, typename C, typename AL>
void DisjointSet<T, H, C, AL>::get_representatives(Vector<T> &out_representatives) {
for (KeyValue<T, Element *> &E : elements) {
Element *element = E.value;
@@ -131,7 +131,7 @@ void DisjointSet<T, H, C, AL>::get_representatives(Vector<T> &out_representative
}
}
-template <typename T, class H, class C, class AL>
+template <typename T, typename H, typename C, typename AL>
void DisjointSet<T, H, C, AL>::get_members(Vector<T> &out_members, T representative) {
typename MapT::Iterator rep_itr = elements.find(representative);
ERR_FAIL_NULL(rep_itr);
diff --git a/core/math/dynamic_bvh.h b/core/math/dynamic_bvh.h
index 9b49fcc3c8..26fc517f7f 100644
--- a/core/math/dynamic_bvh.h
+++ b/core/math/dynamic_bvh.h
@@ -305,11 +305,11 @@ public:
virtual ~DefaultQueryResult() {}
};
- template <class QueryResult>
+ template <typename QueryResult>
_FORCE_INLINE_ void aabb_query(const AABB &p_aabb, QueryResult &r_result);
- template <class QueryResult>
+ template <typename QueryResult>
_FORCE_INLINE_ void convex_query(const Plane *p_planes, int p_plane_count, const Vector3 *p_points, int p_point_count, QueryResult &r_result);
- template <class QueryResult>
+ template <typename QueryResult>
_FORCE_INLINE_ void ray_query(const Vector3 &p_from, const Vector3 &p_to, QueryResult &r_result);
void set_index(uint32_t p_index);
@@ -318,7 +318,7 @@ public:
~DynamicBVH();
};
-template <class QueryResult>
+template <typename QueryResult>
void DynamicBVH::aabb_query(const AABB &p_box, QueryResult &r_result) {
if (!bvh_root) {
return;
@@ -363,7 +363,7 @@ void DynamicBVH::aabb_query(const AABB &p_box, QueryResult &r_result) {
} while (depth > 0);
}
-template <class QueryResult>
+template <typename QueryResult>
void DynamicBVH::convex_query(const Plane *p_planes, int p_plane_count, const Vector3 *p_points, int p_point_count, QueryResult &r_result) {
if (!bvh_root) {
return;
@@ -376,13 +376,8 @@ void DynamicBVH::convex_query(const Plane *p_planes, int p_plane_count, const Ve
volume.min = p_points[0];
volume.max = p_points[0];
} else {
- volume.min.x = MIN(volume.min.x, p_points[i].x);
- volume.min.y = MIN(volume.min.y, p_points[i].y);
- volume.min.z = MIN(volume.min.z, p_points[i].z);
-
- volume.max.x = MAX(volume.max.x, p_points[i].x);
- volume.max.y = MAX(volume.max.y, p_points[i].y);
- volume.max.z = MAX(volume.max.z, p_points[i].z);
+ volume.min = volume.min.min(p_points[i]);
+ volume.max = volume.max.max(p_points[i]);
}
}
@@ -420,7 +415,7 @@ void DynamicBVH::convex_query(const Plane *p_planes, int p_plane_count, const Ve
}
} while (depth > 0);
}
-template <class QueryResult>
+template <typename QueryResult>
void DynamicBVH::ray_query(const Vector3 &p_from, const Vector3 &p_to, QueryResult &r_result) {
if (!bvh_root) {
return;
diff --git a/core/math/expression.h b/core/math/expression.h
index c6ad1bd634..46bc3618df 100644
--- a/core/math/expression.h
+++ b/core/math/expression.h
@@ -243,7 +243,7 @@ private:
}
};
- template <class T>
+ template <typename T>
T *alloc_node() {
T *node = memnew(T);
node->next = nodes;
diff --git a/core/math/geometry_2d.h b/core/math/geometry_2d.h
index fbcaa018a8..1502b2807c 100644
--- a/core/math/geometry_2d.h
+++ b/core/math/geometry_2d.h
@@ -377,10 +377,8 @@ public:
Vector2 further_away_opposite(1e20, 1e20);
for (int i = 0; i < c; i++) {
- further_away.x = MAX(p[i].x, further_away.x);
- further_away.y = MAX(p[i].y, further_away.y);
- further_away_opposite.x = MIN(p[i].x, further_away_opposite.x);
- further_away_opposite.y = MIN(p[i].y, further_away_opposite.y);
+ further_away = further_away.max(p[i]);
+ further_away_opposite = further_away_opposite.min(p[i]);
}
// Make point outside that won't intersect with points in segment from p_point.
diff --git a/core/math/projection.h b/core/math/projection.h
index a7adc9017e..b98f636344 100644
--- a/core/math/projection.h
+++ b/core/math/projection.h
@@ -34,7 +34,7 @@
#include "core/math/vector3.h"
#include "core/math/vector4.h"
-template <class T>
+template <typename T>
class Vector;
struct AABB;
diff --git a/core/math/random_pcg.h b/core/math/random_pcg.h
index fd0934b24a..6bad70059f 100644
--- a/core/math/random_pcg.h
+++ b/core/math/random_pcg.h
@@ -59,7 +59,7 @@ static int __bsr_clz32(uint32_t x) {
#define LDEXPF(s, e) ldexp(s, e)
#endif
-template <class T>
+template <typename T>
class Vector;
class RandomPCG {
diff --git a/core/math/rect2.h b/core/math/rect2.h
index 0f874d4857..497ed8cf04 100644
--- a/core/math/rect2.h
+++ b/core/math/rect2.h
@@ -152,14 +152,12 @@ struct _NO_DISCARD_ Rect2 {
return Rect2();
}
- new_rect.position.x = MAX(p_rect.position.x, position.x);
- new_rect.position.y = MAX(p_rect.position.y, position.y);
+ new_rect.position = p_rect.position.max(position);
Point2 p_rect_end = p_rect.position + p_rect.size;
Point2 end = position + size;
- new_rect.size.x = MIN(p_rect_end.x, end.x) - new_rect.position.x;
- new_rect.size.y = MIN(p_rect_end.y, end.y) - new_rect.position.y;
+ new_rect.size = p_rect_end.min(end) - new_rect.position;
return new_rect;
}
@@ -172,11 +170,9 @@ struct _NO_DISCARD_ Rect2 {
#endif
Rect2 new_rect;
- new_rect.position.x = MIN(p_rect.position.x, position.x);
- new_rect.position.y = MIN(p_rect.position.y, position.y);
+ new_rect.position = p_rect.position.min(position);
- new_rect.size.x = MAX(p_rect.position.x + p_rect.size.x, position.x + size.x);
- new_rect.size.y = MAX(p_rect.position.y + p_rect.size.y, position.y + size.y);
+ new_rect.size = (p_rect.position + p_rect.size).max(position + size);
new_rect.size = new_rect.size - new_rect.position; // Make relative again.
@@ -282,7 +278,7 @@ struct _NO_DISCARD_ Rect2 {
}
_FORCE_INLINE_ Rect2 abs() const {
- return Rect2(Point2(position.x + MIN(size.x, (real_t)0), position.y + MIN(size.y, (real_t)0)), size.abs());
+ return Rect2(position + size.min(Point2()), size.abs());
}
_FORCE_INLINE_ Rect2 round() const {
diff --git a/core/math/rect2i.h b/core/math/rect2i.h
index 205b2c7198..64806414c7 100644
--- a/core/math/rect2i.h
+++ b/core/math/rect2i.h
@@ -95,14 +95,12 @@ struct _NO_DISCARD_ Rect2i {
return Rect2i();
}
- new_rect.position.x = MAX(p_rect.position.x, position.x);
- new_rect.position.y = MAX(p_rect.position.y, position.y);
+ new_rect.position = p_rect.position.max(position);
Point2i p_rect_end = p_rect.position + p_rect.size;
Point2i end = position + size;
- new_rect.size.x = MIN(p_rect_end.x, end.x) - new_rect.position.x;
- new_rect.size.y = MIN(p_rect_end.y, end.y) - new_rect.position.y;
+ new_rect.size = p_rect_end.min(end) - new_rect.position;
return new_rect;
}
@@ -115,11 +113,9 @@ struct _NO_DISCARD_ Rect2i {
#endif
Rect2i new_rect;
- new_rect.position.x = MIN(p_rect.position.x, position.x);
- new_rect.position.y = MIN(p_rect.position.y, position.y);
+ new_rect.position = p_rect.position.min(position);
- new_rect.size.x = MAX(p_rect.position.x + p_rect.size.x, position.x + size.x);
- new_rect.size.y = MAX(p_rect.position.y + p_rect.size.y, position.y + size.y);
+ new_rect.size = (p_rect.position + p_rect.size).max(position + size);
new_rect.size = new_rect.size - new_rect.position; // Make relative again.
@@ -217,7 +213,7 @@ struct _NO_DISCARD_ Rect2i {
}
_FORCE_INLINE_ Rect2i abs() const {
- return Rect2i(Point2i(position.x + MIN(size.x, 0), position.y + MIN(size.y, 0)), size.abs());
+ return Rect2i(position + size.min(Point2i()), size.abs());
}
_FORCE_INLINE_ void set_end(const Vector2i &p_end) {
diff --git a/core/math/transform_interpolator.cpp b/core/math/transform_interpolator.cpp
new file mode 100644
index 0000000000..7cfe880b5a
--- /dev/null
+++ b/core/math/transform_interpolator.cpp
@@ -0,0 +1,76 @@
+/**************************************************************************/
+/* transform_interpolator.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 "transform_interpolator.h"
+
+#include "core/math/transform_2d.h"
+
+void TransformInterpolator::interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction) {
+ // Extract parameters.
+ Vector2 p1 = p_prev.get_origin();
+ Vector2 p2 = p_curr.get_origin();
+
+ // Special case for physics interpolation, if flipping, don't interpolate basis.
+ // If the determinant polarity changes, the handedness of the coordinate system changes.
+ if (_sign(p_prev.determinant()) != _sign(p_curr.determinant())) {
+ r_result.columns[0] = p_curr.columns[0];
+ r_result.columns[1] = p_curr.columns[1];
+ r_result.set_origin(p1.lerp(p2, p_fraction));
+ return;
+ }
+
+ real_t r1 = p_prev.get_rotation();
+ real_t r2 = p_curr.get_rotation();
+
+ Size2 s1 = p_prev.get_scale();
+ Size2 s2 = p_curr.get_scale();
+
+ // Slerp rotation.
+ Vector2 v1(Math::cos(r1), Math::sin(r1));
+ Vector2 v2(Math::cos(r2), Math::sin(r2));
+
+ real_t dot = v1.dot(v2);
+
+ dot = CLAMP(dot, -1, 1);
+
+ Vector2 v;
+
+ if (dot > 0.9995f) {
+ v = v1.lerp(v2, p_fraction).normalized(); // Linearly interpolate to avoid numerical precision issues.
+ } else {
+ real_t angle = p_fraction * Math::acos(dot);
+ Vector2 v3 = (v2 - v1 * dot).normalized();
+ v = v1 * Math::cos(angle) + v3 * Math::sin(angle);
+ }
+
+ // Construct matrix.
+ r_result = Transform2D(Math::atan2(v.y, v.x), p1.lerp(p2, p_fraction));
+ r_result.scale_basis(s1.lerp(s2, p_fraction));
+}
diff --git a/core/math/transform_interpolator.h b/core/math/transform_interpolator.h
new file mode 100644
index 0000000000..a9bce2bd7f
--- /dev/null
+++ b/core/math/transform_interpolator.h
@@ -0,0 +1,46 @@
+/**************************************************************************/
+/* transform_interpolator.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 TRANSFORM_INTERPOLATOR_H
+#define TRANSFORM_INTERPOLATOR_H
+
+#include "core/math/math_defs.h"
+
+struct Transform2D;
+
+class TransformInterpolator {
+private:
+ static bool _sign(real_t p_val) { return p_val >= 0; }
+
+public:
+ static void interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction);
+};
+
+#endif // TRANSFORM_INTERPOLATOR_H
diff --git a/core/object/callable_method_pointer.h b/core/object/callable_method_pointer.h
index 09fe9679f7..1b29e1778a 100644
--- a/core/object/callable_method_pointer.h
+++ b/core/object/callable_method_pointer.h
@@ -77,7 +77,7 @@ public:
virtual uint32_t hash() const;
};
-template <class T, class... P>
+template <typename T, typename... P>
class CallableCustomMethodPointer : public CallableCustomMethodPointerBase {
struct Data {
T *instance;
@@ -112,7 +112,7 @@ public:
}
};
-template <class T, class... P>
+template <typename T, typename... P>
Callable create_custom_callable_function_pointer(T *p_instance,
#ifdef DEBUG_METHODS_ENABLED
const char *p_func_text,
@@ -128,7 +128,7 @@ Callable create_custom_callable_function_pointer(T *p_instance,
// VERSION WITH RETURN
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
class CallableCustomMethodPointerRet : public CallableCustomMethodPointerBase {
struct Data {
T *instance;
@@ -164,7 +164,7 @@ public:
}
};
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
Callable create_custom_callable_function_pointer(T *p_instance,
#ifdef DEBUG_METHODS_ENABLED
const char *p_func_text,
@@ -180,7 +180,7 @@ Callable create_custom_callable_function_pointer(T *p_instance,
// CONST VERSION WITH RETURN
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
class CallableCustomMethodPointerRetC : public CallableCustomMethodPointerBase {
struct Data {
T *instance;
@@ -216,7 +216,7 @@ public:
}
};
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
Callable create_custom_callable_function_pointer(T *p_instance,
#ifdef DEBUG_METHODS_ENABLED
const char *p_func_text,
@@ -238,7 +238,7 @@ Callable create_custom_callable_function_pointer(T *p_instance,
// STATIC VERSIONS
-template <class... P>
+template <typename... P>
class CallableCustomStaticMethodPointer : public CallableCustomMethodPointerBase {
struct Data {
void (*method)(P...);
@@ -270,7 +270,7 @@ public:
}
};
-template <class T, class... P>
+template <typename T, typename... P>
Callable create_custom_callable_static_function_pointer(
#ifdef DEBUG_METHODS_ENABLED
const char *p_func_text,
@@ -284,7 +284,7 @@ Callable create_custom_callable_static_function_pointer(
return Callable(ccmp);
}
-template <class R, class... P>
+template <typename R, typename... P>
class CallableCustomStaticMethodPointerRet : public CallableCustomMethodPointerBase {
struct Data {
R(*method)
@@ -316,7 +316,7 @@ public:
}
};
-template <class R, class... P>
+template <typename R, typename... P>
Callable create_custom_callable_static_function_pointer(
#ifdef DEBUG_METHODS_ENABLED
const char *p_func_text,
diff --git a/core/object/class_db.h b/core/object/class_db.h
index 3b146dd06e..adb525cbe8 100644
--- a/core/object/class_db.h
+++ b/core/object/class_db.h
@@ -140,7 +140,7 @@ public:
~ClassInfo() {}
};
- template <class T>
+ template <typename T>
static Object *creator() {
return memnew(T);
}
@@ -187,12 +187,12 @@ private:
public:
// DO NOT USE THIS!!!!!! NEEDS TO BE PUBLIC BUT DO NOT USE NO MATTER WHAT!!!
- template <class T>
+ template <typename T>
static void _add_class() {
_add_class2(T::get_class_static(), T::get_parent_class_static());
}
- template <class T>
+ template <typename T>
static void register_class(bool p_virtual = false) {
GLOBAL_LOCK_FUNCTION;
static_assert(types_are_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
@@ -207,7 +207,7 @@ public:
T::register_custom_data_to_otdb();
}
- template <class T>
+ template <typename T>
static void register_abstract_class() {
GLOBAL_LOCK_FUNCTION;
static_assert(types_are_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
@@ -220,7 +220,7 @@ public:
//nothing
}
- template <class T>
+ template <typename T>
static void register_internal_class() {
GLOBAL_LOCK_FUNCTION;
static_assert(types_are_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
@@ -235,7 +235,7 @@ public:
T::register_custom_data_to_otdb();
}
- template <class T>
+ template <typename T>
static void register_runtime_class() {
GLOBAL_LOCK_FUNCTION;
static_assert(types_are_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
@@ -255,12 +255,12 @@ public:
static void register_extension_class(ObjectGDExtension *p_extension);
static void unregister_extension_class(const StringName &p_class, bool p_free_method_binds = true);
- template <class T>
+ template <typename T>
static Object *_create_ptr_func() {
return T::create();
}
- template <class T>
+ template <typename T>
static void register_custom_instance_class() {
GLOBAL_LOCK_FUNCTION;
static_assert(types_are_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
@@ -314,7 +314,7 @@ public:
using return_type = R;
};
- template <class N, class M, typename... VarArgs>
+ template <typename N, typename M, typename... VarArgs>
static MethodBind *bind_method(N p_method_name, M p_method, VarArgs... p_args) {
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
const Variant *argptrs[sizeof...(p_args) + 1];
@@ -328,7 +328,7 @@ public:
return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, false, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
- template <class N, class M, typename... VarArgs>
+ template <typename N, typename M, typename... VarArgs>
static MethodBind *bind_static_method(const StringName &p_class, N p_method_name, M p_method, VarArgs... p_args) {
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
const Variant *argptrs[sizeof...(p_args) + 1];
@@ -343,7 +343,7 @@ public:
return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, false, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
- template <class N, class M, typename... VarArgs>
+ template <typename N, typename M, typename... VarArgs>
static MethodBind *bind_compatibility_method(N p_method_name, M p_method, VarArgs... p_args) {
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
const Variant *argptrs[sizeof...(p_args) + 1];
@@ -357,7 +357,7 @@ public:
return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, true, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
- template <class N, class M, typename... VarArgs>
+ template <typename N, typename M, typename... VarArgs>
static MethodBind *bind_compatibility_static_method(const StringName &p_class, N p_method_name, M p_method, VarArgs... p_args) {
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
const Variant *argptrs[sizeof...(p_args) + 1];
@@ -372,7 +372,7 @@ public:
return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, true, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
- template <class M>
+ template <typename M>
static MethodBind *bind_vararg_method(uint32_t p_flags, const StringName &p_name, M p_method, const MethodInfo &p_info = MethodInfo(), const Vector<Variant> &p_default_args = Vector<Variant>(), bool p_return_nil_is_variant = true) {
GLOBAL_LOCK_FUNCTION;
@@ -385,7 +385,7 @@ public:
return _bind_vararg_method(bind, p_name, p_default_args, false);
}
- template <class M>
+ template <typename M>
static MethodBind *bind_compatibility_vararg_method(uint32_t p_flags, const StringName &p_name, M p_method, const MethodInfo &p_info = MethodInfo(), const Vector<Variant> &p_default_args = Vector<Variant>(), bool p_return_nil_is_variant = true) {
GLOBAL_LOCK_FUNCTION;
@@ -498,13 +498,13 @@ _FORCE_INLINE_ void errarray_add_str(Vector<Error> &arr, const Error &p_err) {
arr.push_back(p_err);
}
-template <class... P>
+template <typename... P>
_FORCE_INLINE_ void errarray_add_str(Vector<Error> &arr, const Error &p_err, P... p_args) {
arr.push_back(p_err);
errarray_add_str(arr, p_args...);
}
-template <class... P>
+template <typename... P>
_FORCE_INLINE_ Vector<Error> errarray(P... p_args) {
Vector<Error> arr;
errarray_add_str(arr, p_args...);
diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp
index 83a19554dc..90536e58ff 100644
--- a/core/object/message_queue.cpp
+++ b/core/object/message_queue.cpp
@@ -183,7 +183,7 @@ Error CallQueue::push_notification(ObjectID p_id, int p_notification) {
if ((page_bytes[pages_used - 1] + room_needed) > uint32_t(PAGE_SIZE_BYTES)) {
if (pages_used == max_pages) {
- fprintf(stderr, "Failed notification: %s target ID: %s. Message queue out of memory. %s\n", itos(p_notification).utf8().get_data(), itos(p_id).utf8().get_data(), error_text.utf8().get_data());
+ fprintf(stderr, "Failed notification: %d target ID: %s. Message queue out of memory. %s\n", p_notification, itos(p_id).utf8().get_data(), error_text.utf8().get_data());
statistics();
UNLOCK_MUTEX;
return ERR_OUT_OF_MEMORY;
@@ -256,7 +256,7 @@ Error CallQueue::_transfer_messages_to_main_queue() {
// Any other possibly existing source page needs to be added.
if (mq->pages_used + (pages_used - src_page) > mq->max_pages) {
- ERR_PRINT("Failed appending thread queue. Message queue out of memory. " + mq->error_text);
+ fprintf(stderr, "Failed appending thread queue. Message queue out of memory. %s\n", mq->error_text.utf8().get_data());
mq->statistics();
mq->mutex.unlock();
return ERR_OUT_OF_MEMORY;
@@ -462,8 +462,8 @@ void CallQueue::statistics() {
} break;
}
if (null_target) {
- //object was deleted
- print_line("Object was deleted while awaiting a callback");
+ // Object was deleted.
+ fprintf(stdout, "Object was deleted while awaiting a callback.\n");
null_count++;
}
@@ -481,19 +481,19 @@ void CallQueue::statistics() {
}
}
- print_line("TOTAL PAGES: " + itos(pages_used) + " (" + itos(pages_used * PAGE_SIZE_BYTES) + " bytes).");
- print_line("NULL count: " + itos(null_count));
+ fprintf(stdout, "TOTAL PAGES: %d (%d bytes).\n", pages_used, pages_used * PAGE_SIZE_BYTES);
+ fprintf(stdout, "NULL count: %d.\n", null_count);
for (const KeyValue<StringName, int> &E : set_count) {
- print_line("SET " + E.key + ": " + itos(E.value));
+ fprintf(stdout, "SET %s: %d.\n", String(E.key).utf8().get_data(), E.value);
}
for (const KeyValue<Callable, int> &E : call_count) {
- print_line("CALL " + E.key + ": " + itos(E.value));
+ fprintf(stdout, "CALL %s: %d.\n", String(E.key).utf8().get_data(), E.value);
}
for (const KeyValue<int, int> &E : notify_count) {
- print_line("NOTIFY " + itos(E.key) + ": " + itos(E.value));
+ fprintf(stdout, "NOTIFY %d: %d.\n", E.key, E.value);
}
UNLOCK_MUTEX;
diff --git a/core/object/method_bind.h b/core/object/method_bind.h
index 88b867a1ca..e97f4abc6a 100644
--- a/core/object/method_bind.h
+++ b/core/object/method_bind.h
@@ -140,7 +140,7 @@ public:
};
// MethodBindVarArg base CRTP
-template <class Derived, class T, class R, bool should_returns>
+template <typename Derived, typename T, typename R, bool should_returns>
class MethodBindVarArgBase : public MethodBind {
protected:
R(T::*method)
@@ -219,7 +219,7 @@ private:
};
// variadic, no return
-template <class T>
+template <typename T>
class MethodBindVarArgT : public MethodBindVarArgBase<MethodBindVarArgT<T>, T, void, false> {
friend class MethodBindVarArgBase<MethodBindVarArgT<T>, T, void, false>;
@@ -245,7 +245,7 @@ private:
}
};
-template <class T>
+template <typename T>
MethodBind *create_vararg_method_bind(void (T::*p_method)(const Variant **, int, Callable::CallError &), const MethodInfo &p_info, bool p_return_nil_is_variant) {
MethodBind *a = memnew((MethodBindVarArgT<T>)(p_method, p_info, p_return_nil_is_variant));
a->set_instance_class(T::get_class_static());
@@ -253,7 +253,7 @@ MethodBind *create_vararg_method_bind(void (T::*p_method)(const Variant **, int,
}
// variadic, return
-template <class T, class R>
+template <typename T, typename R>
class MethodBindVarArgTR : public MethodBindVarArgBase<MethodBindVarArgTR<T, R>, T, R, true> {
friend class MethodBindVarArgBase<MethodBindVarArgTR<T, R>, T, R, true>;
@@ -287,7 +287,7 @@ private:
}
};
-template <class T, class R>
+template <typename T, typename R>
MethodBind *create_vararg_method_bind(R (T::*p_method)(const Variant **, int, Callable::CallError &), const MethodInfo &p_info, bool p_return_nil_is_variant) {
MethodBind *a = memnew((MethodBindVarArgTR<T, R>)(p_method, p_info, p_return_nil_is_variant));
a->set_instance_class(T::get_class_static());
@@ -305,9 +305,9 @@ class __UnexistingClass;
// no return, not const
#ifdef TYPED_METHOD_BIND
-template <class T, class... P>
+template <typename T, typename... P>
#else
-template <class... P>
+template <typename... P>
#endif
class MethodBindT : public MethodBind {
void (MB_T::*method)(P...);
@@ -375,7 +375,7 @@ public:
}
};
-template <class T, class... P>
+template <typename T, typename... P>
MethodBind *create_method_bind(void (T::*p_method)(P...)) {
#ifdef TYPED_METHOD_BIND
MethodBind *a = memnew((MethodBindT<T, P...>)(p_method));
@@ -389,9 +389,9 @@ MethodBind *create_method_bind(void (T::*p_method)(P...)) {
// no return, const
#ifdef TYPED_METHOD_BIND
-template <class T, class... P>
+template <typename T, typename... P>
#else
-template <class... P>
+template <typename... P>
#endif
class MethodBindTC : public MethodBind {
void (MB_T::*method)(P...) const;
@@ -460,7 +460,7 @@ public:
}
};
-template <class T, class... P>
+template <typename T, typename... P>
MethodBind *create_method_bind(void (T::*p_method)(P...) const) {
#ifdef TYPED_METHOD_BIND
MethodBind *a = memnew((MethodBindTC<T, P...>)(p_method));
@@ -474,9 +474,9 @@ MethodBind *create_method_bind(void (T::*p_method)(P...) const) {
// return, not const
#ifdef TYPED_METHOD_BIND
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
#else
-template <class R, class... P>
+template <typename R, typename... P>
#endif
class MethodBindTR : public MethodBind {
R(MB_T::*method)
@@ -555,7 +555,7 @@ public:
}
};
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
MethodBind *create_method_bind(R (T::*p_method)(P...)) {
#ifdef TYPED_METHOD_BIND
MethodBind *a = memnew((MethodBindTR<T, R, P...>)(p_method));
@@ -570,9 +570,9 @@ MethodBind *create_method_bind(R (T::*p_method)(P...)) {
// return, const
#ifdef TYPED_METHOD_BIND
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
#else
-template <class R, class... P>
+template <typename R, typename... P>
#endif
class MethodBindTRC : public MethodBind {
R(MB_T::*method)
@@ -652,7 +652,7 @@ public:
}
};
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
MethodBind *create_method_bind(R (T::*p_method)(P...) const) {
#ifdef TYPED_METHOD_BIND
MethodBind *a = memnew((MethodBindTRC<T, R, P...>)(p_method));
@@ -667,7 +667,7 @@ MethodBind *create_method_bind(R (T::*p_method)(P...) const) {
// no return
-template <class... P>
+template <typename... P>
class MethodBindTS : public MethodBind {
void (*function)(P...);
@@ -717,7 +717,7 @@ public:
}
};
-template <class... P>
+template <typename... P>
MethodBind *create_static_method_bind(void (*p_method)(P...)) {
MethodBind *a = memnew((MethodBindTS<P...>)(p_method));
return a;
@@ -725,7 +725,7 @@ MethodBind *create_static_method_bind(void (*p_method)(P...)) {
// return
-template <class R, class... P>
+template <typename R, typename... P>
class MethodBindTRS : public MethodBind {
R(*function)
(P...);
@@ -784,7 +784,7 @@ public:
}
};
-template <class R, class... P>
+template <typename R, typename... P>
MethodBind *create_static_method_bind(R (*p_method)(P...)) {
MethodBind *a = memnew((MethodBindTRS<R, P...>)(p_method));
return a;
diff --git a/core/object/object.cpp b/core/object/object.cpp
index e5d771844b..8b6fd587e0 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -1100,11 +1100,6 @@ bool Object::_has_user_signal(const StringName &p_name) const {
return signal_map[p_name].user.name.length() > 0;
}
-struct _ObjectSignalDisconnectData {
- StringName signal;
- Callable callable;
-};
-
Error Object::_emit_signal(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
if (unlikely(p_argcount < 1)) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
@@ -1153,26 +1148,43 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int
// which is needed in certain edge cases; e.g., https://github.com/godotengine/godot/issues/73889.
Ref<RefCounted> rc = Ref<RefCounted>(Object::cast_to<RefCounted>(this));
- List<_ObjectSignalDisconnectData> disconnect_data;
-
// Ensure that disconnecting the signal or even deleting the object
// will not affect the signal calling.
- LocalVector<Connection> slot_conns;
- slot_conns.resize(s->slot_map.size());
- {
- uint32_t idx = 0;
- for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) {
- slot_conns[idx++] = slot_kv.value.conn;
+ Callable *slot_callables = (Callable *)alloca(sizeof(Callable) * s->slot_map.size());
+ uint32_t *slot_flags = (uint32_t *)alloca(sizeof(uint32_t) * s->slot_map.size());
+ uint32_t slot_count = 0;
+
+ for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) {
+ memnew_placement(&slot_callables[slot_count], Callable(slot_kv.value.conn.callable));
+ slot_flags[slot_count] = slot_kv.value.conn.flags;
+ ++slot_count;
+ }
+
+ DEV_ASSERT(slot_count == s->slot_map.size());
+
+ // Disconnect all one-shot connections before emitting to prevent recursion.
+ for (uint32_t i = 0; i < slot_count; ++i) {
+ bool disconnect = slot_flags[i] & CONNECT_ONE_SHOT;
+#ifdef TOOLS_ENABLED
+ if (disconnect && (slot_flags[i] & CONNECT_PERSIST) && Engine::get_singleton()->is_editor_hint()) {
+ // This signal was connected from the editor, and is being edited. Just don't disconnect for now.
+ disconnect = false;
+ }
+#endif
+ if (disconnect) {
+ _disconnect(p_name, slot_callables[i]);
}
- DEV_ASSERT(idx == s->slot_map.size());
}
OBJ_DEBUG_LOCK
Error err = OK;
- for (const Connection &c : slot_conns) {
- if (!c.callable.is_valid()) {
+ for (uint32_t i = 0; i < slot_count; ++i) {
+ const Callable &callable = slot_callables[i];
+ const uint32_t &flags = slot_flags[i];
+
+ if (!callable.is_valid()) {
// Target might have been deleted during signal callback, this is expected and OK.
continue;
}
@@ -1180,51 +1192,34 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int
const Variant **args = p_args;
int argc = p_argcount;
- if (c.flags & CONNECT_DEFERRED) {
- MessageQueue::get_singleton()->push_callablep(c.callable, args, argc, true);
+ if (flags & CONNECT_DEFERRED) {
+ MessageQueue::get_singleton()->push_callablep(callable, args, argc, true);
} else {
Callable::CallError ce;
_emitting = true;
Variant ret;
- c.callable.callp(args, argc, ret, ce);
+ callable.callp(args, argc, ret, ce);
_emitting = false;
if (ce.error != Callable::CallError::CALL_OK) {
#ifdef DEBUG_ENABLED
- if (c.flags & CONNECT_PERSIST && Engine::get_singleton()->is_editor_hint() && (script.is_null() || !Ref<Script>(script)->is_tool())) {
+ if (flags & CONNECT_PERSIST && Engine::get_singleton()->is_editor_hint() && (script.is_null() || !Ref<Script>(script)->is_tool())) {
continue;
}
#endif
- Object *target = c.callable.get_object();
+ Object *target = callable.get_object();
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(c.callable, args, argc, ce) + ".");
+ ERR_PRINT("Error calling from signal '" + String(p_name) + "' to callable: " + Variant::get_callable_error_text(callable, args, argc, ce) + ".");
err = ERR_METHOD_NOT_FOUND;
}
}
}
-
- bool disconnect = c.flags & CONNECT_ONE_SHOT;
-#ifdef TOOLS_ENABLED
- if (disconnect && (c.flags & CONNECT_PERSIST) && Engine::get_singleton()->is_editor_hint()) {
- //this signal was connected from the editor, and is being edited. just don't disconnect for now
- disconnect = false;
- }
-#endif
- if (disconnect) {
- _ObjectSignalDisconnectData dd;
- dd.signal = p_name;
- dd.callable = c.callable;
- disconnect_data.push_back(dd);
- }
}
- while (!disconnect_data.is_empty()) {
- const _ObjectSignalDisconnectData &dd = disconnect_data.front()->get();
-
- _disconnect(dd.signal, dd.callable);
- disconnect_data.pop_front();
+ for (uint32_t i = 0; i < slot_count; ++i) {
+ slot_callables[i].~Callable();
}
return err;
@@ -1535,9 +1530,9 @@ String Object::tr(const StringName &p_message, const StringName &p_context) cons
return p_message;
}
- if (Engine::get_singleton()->is_editor_hint()) {
+ if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {
String tr_msg = TranslationServer::get_singleton()->extractable_translate(p_message, p_context);
- if (!tr_msg.is_empty()) {
+ if (!tr_msg.is_empty() && tr_msg != p_message) {
return tr_msg;
}
@@ -1556,9 +1551,9 @@ String Object::tr_n(const StringName &p_message, const StringName &p_message_plu
return p_message_plural;
}
- if (Engine::get_singleton()->is_editor_hint()) {
+ if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {
String tr_msg = TranslationServer::get_singleton()->extractable_translate_plural(p_message, p_message_plural, p_n, p_context);
- if (!tr_msg.is_empty()) {
+ if (!tr_msg.is_empty() && tr_msg != p_message && tr_msg != p_message_plural) {
return tr_msg;
}
diff --git a/core/object/object.h b/core/object/object.h
index 2efcf70670..d9551ecd01 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -797,12 +797,12 @@ public:
void detach_from_objectdb();
_FORCE_INLINE_ ObjectID get_instance_id() const { return _instance_id; }
- template <class T>
+ template <typename T>
static T *cast_to(Object *p_object) {
return p_object ? dynamic_cast<T *>(p_object) : nullptr;
}
- template <class T>
+ template <typename T>
static const T *cast_to(const Object *p_object) {
return p_object ? dynamic_cast<const T *>(p_object) : nullptr;
}
diff --git a/core/object/ref_counted.h b/core/object/ref_counted.h
index 10be27b879..5b358135c4 100644
--- a/core/object/ref_counted.h
+++ b/core/object/ref_counted.h
@@ -53,7 +53,7 @@ public:
~RefCounted() {}
};
-template <class T>
+template <typename T>
class Ref {
T *reference = nullptr;
@@ -117,7 +117,7 @@ public:
ref(p_from);
}
- template <class T_Other>
+ template <typename T_Other>
void operator=(const Ref<T_Other> &p_from) {
RefCounted *refb = const_cast<RefCounted *>(static_cast<const RefCounted *>(p_from.ptr()));
if (!refb) {
@@ -149,7 +149,7 @@ public:
}
}
- template <class T_Other>
+ template <typename T_Other>
void reference_ptr(T_Other *p_ptr) {
if (reference == p_ptr) {
return;
@@ -166,7 +166,7 @@ public:
ref(p_from);
}
- template <class T_Other>
+ template <typename T_Other>
Ref(const Ref<T_Other> &p_from) {
RefCounted *refb = const_cast<RefCounted *>(static_cast<const RefCounted *>(p_from.ptr()));
if (!refb) {
@@ -240,7 +240,7 @@ public:
WeakRef() {}
};
-template <class T>
+template <typename T>
struct PtrToArg<Ref<T>> {
_FORCE_INLINE_ static Ref<T> convert(const void *p_ptr) {
if (p_ptr == nullptr) {
@@ -258,7 +258,7 @@ struct PtrToArg<Ref<T>> {
}
};
-template <class T>
+template <typename T>
struct PtrToArg<const Ref<T> &> {
typedef Ref<T> EncodeT;
@@ -271,7 +271,7 @@ struct PtrToArg<const Ref<T> &> {
}
};
-template <class T>
+template <typename T>
struct GetTypeInfo<Ref<T>> {
static const Variant::Type VARIANT_TYPE = Variant::OBJECT;
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
@@ -281,7 +281,7 @@ struct GetTypeInfo<Ref<T>> {
}
};
-template <class T>
+template <typename T>
struct GetTypeInfo<const Ref<T> &> {
static const Variant::Type VARIANT_TYPE = Variant::OBJECT;
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
@@ -291,13 +291,13 @@ struct GetTypeInfo<const Ref<T> &> {
}
};
-template <class T>
+template <typename T>
struct VariantInternalAccessor<Ref<T>> {
static _FORCE_INLINE_ Ref<T> get(const Variant *v) { return Ref<T>(*VariantInternal::get_object(v)); }
static _FORCE_INLINE_ void set(Variant *v, const Ref<T> &p_ref) { VariantInternal::refcounted_object_assign(v, p_ref.ptr()); }
};
-template <class T>
+template <typename T>
struct VariantInternalAccessor<const Ref<T> &> {
static _FORCE_INLINE_ Ref<T> get(const Variant *v) { return Ref<T>(*VariantInternal::get_object(v)); }
static _FORCE_INLINE_ void set(Variant *v, const Ref<T> &p_ref) { VariantInternal::refcounted_object_assign(v, p_ref.ptr()); }
diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp
index 1196c2f787..73da0ba2af 100644
--- a/core/object/script_language.cpp
+++ b/core/object/script_language.cpp
@@ -34,6 +34,7 @@
#include "core/core_string_names.h"
#include "core/debugger/engine_debugger.h"
#include "core/debugger/script_debugger.h"
+#include "core/io/resource_loader.h"
#include <stdint.h>
@@ -170,6 +171,24 @@ void Script::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "source_code", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_source_code", "get_source_code");
}
+void Script::reload_from_file() {
+#ifdef TOOLS_ENABLED
+ // Replicates how the ScriptEditor reloads script resources, which generally handles it.
+ // However, when scripts are to be reloaded but aren't open in the internal editor, we go through here instead.
+ const Ref<Script> rel = ResourceLoader::load(ResourceLoader::path_remap(get_path()), get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
+ if (rel.is_null()) {
+ return;
+ }
+
+ set_source_code(rel->get_source_code());
+ set_last_modified_time(rel->get_last_modified_time());
+
+ reload();
+#else
+ Resource::reload_from_file();
+#endif
+}
+
void ScriptServer::set_scripting_enabled(bool p_enabled) {
scripting_enabled = p_enabled;
}
diff --git a/core/object/script_language.h b/core/object/script_language.h
index be50e58d79..c6c6f3de9f 100644
--- a/core/object/script_language.h
+++ b/core/object/script_language.h
@@ -125,6 +125,8 @@ protected:
Dictionary _get_script_constant_map();
public:
+ virtual void reload_from_file() override;
+
virtual bool can_instantiate() const = 0;
virtual Ref<Script> get_base_script() const = 0; //for script inheritance
diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h
index efb317b839..1db322526d 100644
--- a/core/object/script_language_extension.h
+++ b/core/object/script_language_extension.h
@@ -821,6 +821,14 @@ public:
}
virtual int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override {
+ if (native_info->get_method_argument_count_func) {
+ GDExtensionBool is_valid = 0;
+ GDExtensionInt ret = native_info->get_method_argument_count_func(instance, (GDExtensionStringNamePtr)&p_method, &is_valid);
+ if (r_is_valid) {
+ *r_is_valid = is_valid != 0;
+ }
+ return ret;
+ }
// Fallback to default.
return ScriptInstance::get_method_argument_count(p_method, r_is_valid);
}
@@ -912,7 +920,6 @@ public:
return reinterpret_cast<ScriptLanguage *>(lang);
}
return nullptr;
- ;
}
virtual ~ScriptInstanceExtension() {
if (native_info->free_func) {
diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h
index c9921c808d..fdddc9a647 100644
--- a/core/object/worker_thread_pool.h
+++ b/core/object/worker_thread_pool.h
@@ -157,7 +157,7 @@ private:
TaskID _add_task(const Callable &p_callable, void (*p_func)(void *), void *p_userdata, BaseTemplateUserdata *p_template_userdata, bool p_high_priority, const String &p_description);
GroupID _add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description);
- template <class C, class M, class U>
+ template <typename C, typename M, typename U>
struct TaskUserData : public BaseTemplateUserdata {
C *instance;
M method;
@@ -167,7 +167,7 @@ private:
}
};
- template <class C, class M, class U>
+ template <typename C, typename M, typename U>
struct GroupUserData : public BaseTemplateUserdata {
C *instance;
M method;
@@ -181,7 +181,7 @@ protected:
static void _bind_methods();
public:
- template <class C, class M, class U>
+ template <typename C, typename M, typename U>
TaskID add_template_task(C *p_instance, M p_method, U p_userdata, bool p_high_priority = false, const String &p_description = String()) {
typedef TaskUserData<C, M, U> TUD;
TUD *ud = memnew(TUD);
@@ -196,7 +196,7 @@ public:
bool is_task_completed(TaskID p_task_id) const;
Error wait_for_task_completion(TaskID p_task_id);
- template <class C, class M, class U>
+ template <typename C, typename M, typename U>
GroupID add_template_group_task(C *p_instance, M p_method, U p_userdata, int p_elements, int p_tasks = -1, bool p_high_priority = false, const String &p_description = String()) {
typedef GroupUserData<C, M, U> GroupUD;
GroupUD *ud = memnew(GroupUD);
diff --git a/core/os/condition_variable.h b/core/os/condition_variable.h
index 2b6b272e18..fa1355e98c 100644
--- a/core/os/condition_variable.h
+++ b/core/os/condition_variable.h
@@ -54,7 +54,7 @@ class ConditionVariable {
mutable THREADING_NAMESPACE::condition_variable condition;
public:
- template <class BinaryMutexT>
+ template <typename BinaryMutexT>
_ALWAYS_INLINE_ void wait(const MutexLock<BinaryMutexT> &p_lock) const {
condition.wait(const_cast<THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> &>(p_lock.lock));
}
@@ -72,7 +72,7 @@ public:
class ConditionVariable {
public:
- template <class BinaryMutexT>
+ template <typename BinaryMutexT>
void wait(const MutexLock<BinaryMutexT> &p_lock) const {}
void notify_one() const {}
void notify_all() const {}
diff --git a/core/os/main_loop.h b/core/os/main_loop.h
index b45eb38aeb..e48541d074 100644
--- a/core/os/main_loop.h
+++ b/core/os/main_loop.h
@@ -62,6 +62,7 @@ public:
};
virtual void initialize();
+ virtual void iteration_prepare() {}
virtual bool physics_process(double p_time);
virtual bool process(double p_time);
virtual void finalize();
diff --git a/core/os/memory.h b/core/os/memory.h
index 6f3f6fed39..d03e08d785 100644
--- a/core/os/memory.h
+++ b/core/os/memory.h
@@ -92,7 +92,7 @@ void operator delete(void *p_mem, void *p_pointer, size_t check, const char *p_d
_ALWAYS_INLINE_ void postinitialize_handler(void *) {}
-template <class T>
+template <typename T>
_ALWAYS_INLINE_ T *_post_initialize(T *p_obj) {
postinitialize_handler(p_obj);
return p_obj;
@@ -107,7 +107,7 @@ _ALWAYS_INLINE_ bool predelete_handler(void *) {
return true;
}
-template <class T>
+template <typename T>
void memdelete(T *p_class) {
if (!predelete_handler(p_class)) {
return; // doesn't want to be deleted
@@ -119,7 +119,7 @@ void memdelete(T *p_class) {
Memory::free_static(p_class, false);
}
-template <class T, class A>
+template <typename T, typename A>
void memdelete_allocator(T *p_class) {
if (!predelete_handler(p_class)) {
return; // doesn't want to be deleted
@@ -213,10 +213,10 @@ struct _GlobalNilClass {
static _GlobalNil _nil;
};
-template <class T>
+template <typename T>
class DefaultTypedAllocator {
public:
- template <class... Args>
+ template <typename... Args>
_FORCE_INLINE_ T *new_allocation(const Args &&...p_args) { return memnew(T(p_args...)); }
_FORCE_INLINE_ void delete_allocation(T *p_allocation) { memdelete(p_allocation); }
};
diff --git a/core/os/mutex.h b/core/os/mutex.h
index a4eab0cd86..3e7aa81bc1 100644
--- a/core/os/mutex.h
+++ b/core/os/mutex.h
@@ -45,10 +45,10 @@
#ifdef THREADS_ENABLED
-template <class MutexT>
+template <typename MutexT>
class MutexLock;
-template <class StdMutexT>
+template <typename StdMutexT>
class MutexImpl {
friend class MutexLock<MutexImpl<StdMutexT>>;
@@ -70,7 +70,7 @@ public:
}
};
-template <class MutexT>
+template <typename MutexT>
class MutexLock {
friend class ConditionVariable;
@@ -100,7 +100,7 @@ public:
bool try_lock() const { return true; }
};
-template <class MutexT>
+template <typename MutexT>
class MutexLock {
public:
MutexLock(const MutexT &p_mutex) {}
diff --git a/core/os/os.h b/core/os/os.h
index e0dda0b155..4f0df1543f 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -56,7 +56,8 @@ class OS {
bool _verbose_stdout = false;
bool _debug_stdout = false;
String _local_clipboard;
- int _exit_code = EXIT_FAILURE; // unexpected exit is marked as failure
+ // Assume success by default, all failure cases need to set EXIT_FAILURE explicitly.
+ int _exit_code = EXIT_SUCCESS;
bool _allow_hidpi = false;
bool _allow_layered = false;
bool _stdout_enabled = true;
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index 1d27933016..dbc283946e 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -927,52 +927,49 @@ static _FORCE_INLINE_ signed char natural_cmp_common(const char32_t *&r_this_str
return 0;
}
-signed char String::naturalcasecmp_to(const String &p_str) const {
- const char32_t *this_str = get_data();
- const char32_t *that_str = p_str.get_data();
-
- if (this_str && that_str) {
- while (*this_str == '.' || *that_str == '.') {
- if (*this_str++ != '.') {
+static _FORCE_INLINE_ signed char naturalcasecmp_to_base(const char32_t *p_this_str, const char32_t *p_that_str) {
+ if (p_this_str && p_that_str) {
+ while (*p_this_str == '.' || *p_that_str == '.') {
+ if (*p_this_str++ != '.') {
return 1;
}
- if (*that_str++ != '.') {
+ if (*p_that_str++ != '.') {
return -1;
}
- if (!*that_str) {
+ if (!*p_that_str) {
return 1;
}
- if (!*this_str) {
+ if (!*p_this_str) {
return -1;
}
}
- while (*this_str) {
- if (!*that_str) {
+ while (*p_this_str) {
+ if (!*p_that_str) {
return 1;
- } else if (is_digit(*this_str)) {
- if (!is_digit(*that_str)) {
+ } else if (is_digit(*p_this_str)) {
+ if (!is_digit(*p_that_str)) {
return -1;
}
- signed char ret = natural_cmp_common(this_str, that_str);
+ signed char ret = natural_cmp_common(p_this_str, p_that_str);
if (ret) {
return ret;
}
- } else if (is_digit(*that_str)) {
+ } else if (is_digit(*p_that_str)) {
return 1;
} else {
- if (*this_str < *that_str) { // If current character in this is less, we are less.
+ if (*p_this_str < *p_that_str) { // If current character in this is less, we are less.
return -1;
- } else if (*this_str > *that_str) { // If current character in this is greater, we are greater.
+ } else if (*p_this_str > *p_that_str) { // If current character in this is greater, we are greater.
return 1;
}
- this_str++;
- that_str++;
+ p_this_str++;
+ p_that_str++;
}
}
- if (*that_str) {
+ if (*p_that_str) {
return -1;
}
}
@@ -980,52 +977,56 @@ signed char String::naturalcasecmp_to(const String &p_str) const {
return 0;
}
-signed char String::naturalnocasecmp_to(const String &p_str) const {
+signed char String::naturalcasecmp_to(const String &p_str) const {
const char32_t *this_str = get_data();
const char32_t *that_str = p_str.get_data();
- if (this_str && that_str) {
- while (*this_str == '.' || *that_str == '.') {
- if (*this_str++ != '.') {
+ return naturalcasecmp_to_base(this_str, that_str);
+}
+
+static _FORCE_INLINE_ signed char naturalnocasecmp_to_base(const char32_t *p_this_str, const char32_t *p_that_str) {
+ if (p_this_str && p_that_str) {
+ while (*p_this_str == '.' || *p_that_str == '.') {
+ if (*p_this_str++ != '.') {
return 1;
}
- if (*that_str++ != '.') {
+ if (*p_that_str++ != '.') {
return -1;
}
- if (!*that_str) {
+ if (!*p_that_str) {
return 1;
}
- if (!*this_str) {
+ if (!*p_this_str) {
return -1;
}
}
- while (*this_str) {
- if (!*that_str) {
+ while (*p_this_str) {
+ if (!*p_that_str) {
return 1;
- } else if (is_digit(*this_str)) {
- if (!is_digit(*that_str)) {
+ } else if (is_digit(*p_this_str)) {
+ if (!is_digit(*p_that_str)) {
return -1;
}
- signed char ret = natural_cmp_common(this_str, that_str);
+ signed char ret = natural_cmp_common(p_this_str, p_that_str);
if (ret) {
return ret;
}
- } else if (is_digit(*that_str)) {
+ } else if (is_digit(*p_that_str)) {
return 1;
} else {
- if (_find_upper(*this_str) < _find_upper(*that_str)) { // If current character in this is less, we are less.
+ if (_find_upper(*p_this_str) < _find_upper(*p_that_str)) { // If current character in this is less, we are less.
return -1;
- } else if (_find_upper(*this_str) > _find_upper(*that_str)) { // If current character in this is greater, we are greater.
+ } else if (_find_upper(*p_this_str) > _find_upper(*p_that_str)) { // If current character in this is greater, we are greater.
return 1;
}
- this_str++;
- that_str++;
+ p_this_str++;
+ p_that_str++;
}
}
- if (*that_str) {
+ if (*p_that_str) {
return -1;
}
}
@@ -1033,6 +1034,54 @@ signed char String::naturalnocasecmp_to(const String &p_str) const {
return 0;
}
+signed char String::naturalnocasecmp_to(const String &p_str) const {
+ const char32_t *this_str = get_data();
+ const char32_t *that_str = p_str.get_data();
+
+ return naturalnocasecmp_to_base(this_str, that_str);
+}
+
+static _FORCE_INLINE_ signed char file_cmp_common(const char32_t *&r_this_str, const char32_t *&r_that_str) {
+ // Compare leading `_` sequences.
+ while (*r_this_str && *r_that_str) {
+ // Sort `_` lower than everything except `.`
+ if (*r_this_str != '_' && *r_that_str == '_') {
+ return *r_this_str == '.' ? -1 : 1;
+ }
+ if (*r_this_str == '_' && *r_that_str != '_') {
+ return *r_that_str == '.' ? 1 : -1;
+ }
+ r_this_str++;
+ r_that_str++;
+ }
+
+ return 0;
+}
+
+signed char String::filecasecmp_to(const String &p_str) const {
+ const char32_t *this_str = get_data();
+ const char32_t *that_str = p_str.get_data();
+
+ signed char ret = file_cmp_common(this_str, that_str);
+ if (ret) {
+ return ret;
+ }
+
+ return naturalcasecmp_to_base(this_str, that_str);
+}
+
+signed char String::filenocasecmp_to(const String &p_str) const {
+ const char32_t *this_str = get_data();
+ const char32_t *that_str = p_str.get_data();
+
+ signed char ret = file_cmp_common(this_str, that_str);
+ if (ret) {
+ return ret;
+ }
+
+ return naturalnocasecmp_to_base(this_str, that_str);
+}
+
const char32_t *String::get_data() const {
static const char32_t zero = 0;
return size() ? &operator[](0) : &zero;
@@ -2459,7 +2508,7 @@ bool String::is_numeric() const {
return true; // TODO: Use the parser below for this instead
}
-template <class C>
+template <typename C>
static double built_in_strtod(
/* A decimal ASCII floating-point number,
* optionally preceded by white space. Must
diff --git a/core/string/ustring.h b/core/string/ustring.h
index 468a015302..fa904c8200 100644
--- a/core/string/ustring.h
+++ b/core/string/ustring.h
@@ -43,7 +43,7 @@
/* CharProxy */
/*************************************************************************/
-template <class T>
+template <typename T>
class CharProxy {
friend class Char16String;
friend class CharString;
@@ -265,6 +265,9 @@ public:
signed char nocasecmp_to(const String &p_str) const;
signed char naturalcasecmp_to(const String &p_str) const;
signed char naturalnocasecmp_to(const String &p_str) const;
+ // Special sorting for file names. Names starting with `_` are put before all others except those starting with `.`, otherwise natural comparison is used.
+ signed char filecasecmp_to(const String &p_str) const;
+ signed char filenocasecmp_to(const String &p_str) const;
const char32_t *get_data() const;
/* standard size stuff */
@@ -499,6 +502,12 @@ struct NaturalNoCaseComparator {
}
};
+struct FileNoCaseComparator {
+ bool operator()(const String &p_a, const String &p_b) const {
+ return p_a.filenocasecmp_to(p_b) < 0;
+ }
+};
+
template <typename L, typename R>
_FORCE_INLINE_ bool is_str_less(const L *l_ptr, const R *r_ptr) {
while (true) {
@@ -602,13 +611,13 @@ _FORCE_INLINE_ void sarray_add_str(Vector<String> &arr, const String &p_str) {
arr.push_back(p_str);
}
-template <class... P>
+template <typename... P>
_FORCE_INLINE_ void sarray_add_str(Vector<String> &arr, const String &p_str, P... p_args) {
arr.push_back(p_str);
sarray_add_str(arr, p_args...);
}
-template <class... P>
+template <typename... P>
_FORCE_INLINE_ Vector<String> sarray(P... p_args) {
Vector<String> arr;
sarray_add_str(arr, p_args...);
diff --git a/core/templates/bin_sorted_array.h b/core/templates/bin_sorted_array.h
index 500d1f1377..30c1fefbbd 100644
--- a/core/templates/bin_sorted_array.h
+++ b/core/templates/bin_sorted_array.h
@@ -34,7 +34,7 @@
#include "core/templates/local_vector.h"
#include "core/templates/paged_array.h"
-template <class T>
+template <typename T>
class BinSortedArray {
PagedArray<T> array;
LocalVector<uint64_t> bin_limits;
diff --git a/core/templates/command_queue_mt.h b/core/templates/command_queue_mt.h
index b1010f7f43..4056119851 100644
--- a/core/templates/command_queue_mt.h
+++ b/core/templates/command_queue_mt.h
@@ -207,41 +207,41 @@
#define ARG(N) p##N
#define PARAM(N) P##N p##N
-#define TYPE_PARAM(N) class P##N
+#define TYPE_PARAM(N) typename P##N
#define PARAM_DECL(N) typename GetSimpleTypeT<P##N>::type_t p##N
-#define DECL_CMD(N) \
- template <class T, class M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
- struct Command##N : public CommandBase { \
- T *instance; \
- M method; \
- SEMIC_SEP_LIST(PARAM_DECL, N); \
- virtual void call() override { \
- (instance->*method)(COMMA_SEP_LIST(ARG, N)); \
- } \
+#define DECL_CMD(N) \
+ template <typename T, typename M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
+ struct Command##N : public CommandBase { \
+ T *instance; \
+ M method; \
+ SEMIC_SEP_LIST(PARAM_DECL, N); \
+ virtual void call() override { \
+ (instance->*method)(COMMA_SEP_LIST(ARG, N)); \
+ } \
};
-#define DECL_CMD_RET(N) \
- template <class T, class M, COMMA_SEP_LIST(TYPE_PARAM, N) COMMA(N) class R> \
- struct CommandRet##N : public SyncCommand { \
- R *ret; \
- T *instance; \
- M method; \
- SEMIC_SEP_LIST(PARAM_DECL, N); \
- virtual void call() override { \
- *ret = (instance->*method)(COMMA_SEP_LIST(ARG, N)); \
- } \
+#define DECL_CMD_RET(N) \
+ template <typename T, typename M, COMMA_SEP_LIST(TYPE_PARAM, N) COMMA(N) typename R> \
+ struct CommandRet##N : public SyncCommand { \
+ R *ret; \
+ T *instance; \
+ M method; \
+ SEMIC_SEP_LIST(PARAM_DECL, N); \
+ virtual void call() override { \
+ *ret = (instance->*method)(COMMA_SEP_LIST(ARG, N)); \
+ } \
};
-#define DECL_CMD_SYNC(N) \
- template <class T, class M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
- struct CommandSync##N : public SyncCommand { \
- T *instance; \
- M method; \
- SEMIC_SEP_LIST(PARAM_DECL, N); \
- virtual void call() override { \
- (instance->*method)(COMMA_SEP_LIST(ARG, N)); \
- } \
+#define DECL_CMD_SYNC(N) \
+ template <typename T, typename M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
+ struct CommandSync##N : public SyncCommand { \
+ T *instance; \
+ M method; \
+ SEMIC_SEP_LIST(PARAM_DECL, N); \
+ virtual void call() override { \
+ (instance->*method)(COMMA_SEP_LIST(ARG, N)); \
+ } \
};
#define TYPE_ARG(N) P##N
@@ -249,7 +249,7 @@
#define CMD_ASSIGN_PARAM(N) cmd->p##N = p##N
#define DECL_PUSH(N) \
- template <class T, class M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
+ template <typename T, typename M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
void push(T *p_instance, M p_method COMMA(N) COMMA_SEP_LIST(PARAM, N)) { \
CMD_TYPE(N) *cmd = allocate_and_lock<CMD_TYPE(N)>(); \
cmd->instance = p_instance; \
@@ -263,7 +263,7 @@
#define CMD_RET_TYPE(N) CommandRet##N<T, M, COMMA_SEP_LIST(TYPE_ARG, N) COMMA(N) R>
#define DECL_PUSH_AND_RET(N) \
- template <class T, class M, COMMA_SEP_LIST(TYPE_PARAM, N) COMMA(N) class R> \
+ template <typename T, typename M, COMMA_SEP_LIST(TYPE_PARAM, N) COMMA(N) typename R> \
void push_and_ret(T *p_instance, M p_method, COMMA_SEP_LIST(PARAM, N) COMMA(N) R *r_ret) { \
SyncSemaphore *ss = _alloc_sync_sem(); \
CMD_RET_TYPE(N) *cmd = allocate_and_lock<CMD_RET_TYPE(N)>(); \
@@ -282,7 +282,7 @@
#define CMD_SYNC_TYPE(N) CommandSync##N<T, M COMMA(N) COMMA_SEP_LIST(TYPE_ARG, N)>
#define DECL_PUSH_AND_SYNC(N) \
- template <class T, class M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
+ template <typename T, typename M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
void push_and_sync(T *p_instance, M p_method COMMA(N) COMMA_SEP_LIST(PARAM, N)) { \
SyncSemaphore *ss = _alloc_sync_sem(); \
CMD_SYNC_TYPE(N) *cmd = allocate_and_lock<CMD_SYNC_TYPE(N)>(); \
@@ -343,7 +343,7 @@ class CommandQueueMT {
Semaphore *sync = nullptr;
uint64_t flush_read_ptr = 0;
- template <class T>
+ template <typename T>
T *allocate() {
// alloc size is size+T+safeguard
uint32_t alloc_size = ((sizeof(T) + 8 - 1) & ~(8 - 1));
@@ -354,7 +354,7 @@ class CommandQueueMT {
return cmd;
}
- template <class T>
+ template <typename T>
T *allocate_and_lock() {
lock();
T *ret = allocate<T>();
diff --git a/core/templates/cowdata.h b/core/templates/cowdata.h
index 466658951e..f22ae1f1d3 100644
--- a/core/templates/cowdata.h
+++ b/core/templates/cowdata.h
@@ -38,12 +38,12 @@
#include <string.h>
#include <type_traits>
-template <class T>
+template <typename T>
class Vector;
class String;
class Char16String;
class CharString;
-template <class T, class V>
+template <typename T, typename V>
class VMap;
static_assert(std::is_trivially_destructible_v<std::atomic<uint64_t>>);
@@ -54,14 +54,14 @@ static_assert(std::is_trivially_destructible_v<std::atomic<uint64_t>>);
#pragma GCC diagnostic ignored "-Wplacement-new"
#endif
-template <class T>
+template <typename T>
class CowData {
- template <class TV>
+ template <typename TV>
friend class Vector;
friend class String;
friend class Char16String;
friend class CharString;
- template <class TV, class VV>
+ template <typename TV, typename VV>
friend class VMap;
public:
@@ -241,7 +241,7 @@ public:
_FORCE_INLINE_ CowData(CowData<T> &p_from) { _ref(p_from); };
};
-template <class T>
+template <typename T>
void CowData<T>::_unref(void *p_data) {
if (!p_data) {
return;
@@ -268,7 +268,7 @@ void CowData<T>::_unref(void *p_data) {
Memory::free_static(((uint8_t *)p_data) - DATA_OFFSET, false);
}
-template <class T>
+template <typename T>
typename CowData<T>::USize CowData<T>::_copy_on_write() {
if (!_ptr) {
return 0;
@@ -308,7 +308,7 @@ typename CowData<T>::USize CowData<T>::_copy_on_write() {
return rc;
}
-template <class T>
+template <typename T>
template <bool p_ensure_zero>
Error CowData<T>::resize(Size p_size) {
ERR_FAIL_COND_V(p_size < 0, ERR_INVALID_PARAMETER);
@@ -401,7 +401,7 @@ Error CowData<T>::resize(Size p_size) {
return OK;
}
-template <class T>
+template <typename T>
typename CowData<T>::Size CowData<T>::find(const T &p_val, Size p_from) const {
Size ret = -1;
@@ -419,7 +419,7 @@ typename CowData<T>::Size CowData<T>::find(const T &p_val, Size p_from) const {
return ret;
}
-template <class T>
+template <typename T>
typename CowData<T>::Size CowData<T>::rfind(const T &p_val, Size p_from) const {
const Size s = size();
@@ -438,7 +438,7 @@ typename CowData<T>::Size CowData<T>::rfind(const T &p_val, Size p_from) const {
return -1;
}
-template <class T>
+template <typename T>
typename CowData<T>::Size CowData<T>::count(const T &p_val) const {
Size amount = 0;
for (Size i = 0; i < size(); i++) {
@@ -449,12 +449,12 @@ typename CowData<T>::Size CowData<T>::count(const T &p_val) const {
return amount;
}
-template <class T>
+template <typename T>
void CowData<T>::_ref(const CowData *p_from) {
_ref(*p_from);
}
-template <class T>
+template <typename T>
void CowData<T>::_ref(const CowData &p_from) {
if (_ptr == p_from._ptr) {
return; // self assign, do nothing.
@@ -472,7 +472,7 @@ void CowData<T>::_ref(const CowData &p_from) {
}
}
-template <class T>
+template <typename T>
CowData<T>::~CowData() {
_unref(_ptr);
}
diff --git a/core/templates/hash_map.h b/core/templates/hash_map.h
index e1745110d7..a3e8c2c788 100644
--- a/core/templates/hash_map.h
+++ b/core/templates/hash_map.h
@@ -51,7 +51,7 @@
* The assignment operator copy the pairs from one map to the other.
*/
-template <class TKey, class TValue>
+template <typename TKey, typename TValue>
struct HashMapElement {
HashMapElement *next = nullptr;
HashMapElement *prev = nullptr;
@@ -61,10 +61,10 @@ struct HashMapElement {
data(p_key, p_value) {}
};
-template <class TKey, class TValue,
- class Hasher = HashMapHasherDefault,
- class Comparator = HashMapComparatorDefault<TKey>,
- class Allocator = DefaultTypedAllocator<HashMapElement<TKey, TValue>>>
+template <typename TKey, typename TValue,
+ typename Hasher = HashMapHasherDefault,
+ typename Comparator = HashMapComparatorDefault<TKey>,
+ typename Allocator = DefaultTypedAllocator<HashMapElement<TKey, TValue>>>
class HashMap {
public:
static constexpr uint32_t MIN_CAPACITY_INDEX = 2; // Use a prime.
diff --git a/core/templates/hash_set.h b/core/templates/hash_set.h
index 00f4acbc9c..295b07e5e7 100644
--- a/core/templates/hash_set.h
+++ b/core/templates/hash_set.h
@@ -46,9 +46,9 @@
*
*/
-template <class TKey,
- class Hasher = HashMapHasherDefault,
- class Comparator = HashMapComparatorDefault<TKey>>
+template <typename TKey,
+ typename Hasher = HashMapHasherDefault,
+ typename Comparator = HashMapComparatorDefault<TKey>>
class HashSet {
public:
static constexpr uint32_t MIN_CAPACITY_INDEX = 2; // Use a prime.
diff --git a/core/templates/hashfuncs.h b/core/templates/hashfuncs.h
index a16f655524..fc7a78bcf5 100644
--- a/core/templates/hashfuncs.h
+++ b/core/templates/hashfuncs.h
@@ -248,7 +248,7 @@ static _FORCE_INLINE_ uint32_t hash_djb2_one_float(double p_in, uint32_t p_prev
return ((p_prev << 5) + p_prev) + hash_one_uint64(u.i);
}
-template <class T>
+template <typename T>
static _FORCE_INLINE_ uint32_t hash_make_uint32_t(T p_in) {
union {
T t;
@@ -281,7 +281,7 @@ static _FORCE_INLINE_ uint64_t hash_djb2_one_64(uint64_t p_in, uint64_t p_prev =
return ((p_prev << 5) + p_prev) ^ p_in;
}
-template <class T>
+template <typename T>
static _FORCE_INLINE_ uint64_t hash_make_uint64_t(T p_in) {
union {
T t;
@@ -293,15 +293,15 @@ static _FORCE_INLINE_ uint64_t hash_make_uint64_t(T p_in) {
return _u._u64;
}
-template <class T>
+template <typename T>
class Ref;
struct HashMapHasherDefault {
// Generic hash function for any type.
- template <class T>
+ template <typename T>
static _FORCE_INLINE_ uint32_t hash(const T *p_pointer) { return hash_one_uint64((uint64_t)p_pointer); }
- template <class T>
+ template <typename T>
static _FORCE_INLINE_ uint32_t hash(const Ref<T> &p_ref) { return hash_one_uint64((uint64_t)p_ref.operator->()); }
static _FORCE_INLINE_ uint32_t hash(const String &p_string) { return p_string.hash(); }
@@ -387,7 +387,7 @@ struct HashMapHasherDefault {
};
// TODO: Fold this into HashMapHasherDefault once C++20 concepts are allowed
-template <class T>
+template <typename T>
struct HashableHasher {
static _FORCE_INLINE_ uint32_t hash(const T &hashable) { return hashable.hash(); }
};
diff --git a/core/templates/list.h b/core/templates/list.h
index 354e826a43..b4d4beb930 100644
--- a/core/templates/list.h
+++ b/core/templates/list.h
@@ -43,7 +43,7 @@
* from the iterator.
*/
-template <class T, class A = DefaultAllocator>
+template <typename T, typename A = DefaultAllocator>
class List {
struct _Data;
@@ -410,7 +410,7 @@ public:
/**
* find an element in the list,
*/
- template <class T_v>
+ template <typename T_v>
Element *find(const T_v &p_val) {
Element *it = front();
while (it) {
@@ -646,7 +646,7 @@ public:
sort_custom<Comparator<T>>();
}
- template <class C>
+ template <typename C>
void sort_custom_inplace() {
if (size() < 2) {
return;
@@ -693,7 +693,7 @@ public:
_data->last = to;
}
- template <class C>
+ template <typename C>
struct AuxiliaryComparator {
C compare;
_FORCE_INLINE_ bool operator()(const Element *a, const Element *b) const {
@@ -701,7 +701,7 @@ public:
}
};
- template <class C>
+ template <typename C>
void sort_custom() {
//this version uses auxiliary memory for speed.
//if you don't want to use auxiliary memory, use the in_place version
@@ -764,7 +764,7 @@ public:
}
};
-template <class T, class A>
+template <typename T, typename A>
void List<T, A>::Element::transfer_to_back(List<T, A> *p_dst_list) {
// Detach from current.
diff --git a/core/templates/local_vector.h b/core/templates/local_vector.h
index 17ddbf6161..e0047e0782 100644
--- a/core/templates/local_vector.h
+++ b/core/templates/local_vector.h
@@ -41,7 +41,7 @@
// If tight, it grows strictly as much as needed.
// Otherwise, it grows exponentially (the default and what you want in most cases).
-template <class T, class U = uint32_t, bool force_trivial = false, bool tight = false>
+template <typename T, typename U = uint32_t, bool force_trivial = false, bool tight = false>
class LocalVector {
private:
U count = 0;
@@ -104,6 +104,22 @@ public:
return false;
}
+ U erase_multiple_unordered(const T &p_val) {
+ U from = 0;
+ U occurrences = 0;
+ while (true) {
+ int64_t idx = find(p_val, from);
+
+ if (idx == -1) {
+ break;
+ }
+ remove_at_unordered(idx);
+ from = idx;
+ occurrences++;
+ }
+ return occurrences;
+ }
+
void invert() {
for (U i = 0; i < count / 2; i++) {
SWAP(data[i], data[count - i - 1]);
@@ -248,7 +264,7 @@ public:
return -1;
}
- template <class C>
+ template <typename C>
void sort_custom() {
U len = count;
if (len == 0) {
@@ -322,7 +338,7 @@ public:
}
};
-template <class T, class U = uint32_t, bool force_trivial = false>
+template <typename T, typename U = uint32_t, bool force_trivial = false>
using TightLocalVector = LocalVector<T, U, force_trivial, true>;
#endif // LOCAL_VECTOR_H
diff --git a/core/templates/lru.h b/core/templates/lru.h
index aecb735c48..919c5605aa 100644
--- a/core/templates/lru.h
+++ b/core/templates/lru.h
@@ -35,7 +35,7 @@
#include "hash_map.h"
#include "list.h"
-template <class TKey, class TData, class Hasher = HashMapHasherDefault, class Comparator = HashMapComparatorDefault<TKey>>
+template <typename TKey, typename TData, typename Hasher = HashMapHasherDefault, typename Comparator = HashMapComparatorDefault<TKey>>
class LRUCache {
private:
struct Pair {
diff --git a/core/templates/oa_hash_map.h b/core/templates/oa_hash_map.h
index 752e264a49..7afb58b7c2 100644
--- a/core/templates/oa_hash_map.h
+++ b/core/templates/oa_hash_map.h
@@ -50,9 +50,9 @@
*
* The assignment operator copy the pairs from one map to the other.
*/
-template <class TKey, class TValue,
- class Hasher = HashMapHasherDefault,
- class Comparator = HashMapComparatorDefault<TKey>>
+template <typename TKey, typename TValue,
+ typename Hasher = HashMapHasherDefault,
+ typename Comparator = HashMapComparatorDefault<TKey>>
class OAHashMap {
private:
TValue *values = nullptr;
diff --git a/core/templates/paged_allocator.h b/core/templates/paged_allocator.h
index 48110d37e5..4854e1b866 100644
--- a/core/templates/paged_allocator.h
+++ b/core/templates/paged_allocator.h
@@ -40,7 +40,7 @@
#include <type_traits>
#include <typeinfo>
-template <class T, bool thread_safe = false, uint32_t DEFAULT_PAGE_SIZE = 4096>
+template <typename T, bool thread_safe = false, uint32_t DEFAULT_PAGE_SIZE = 4096>
class PagedAllocator {
T **page_pool = nullptr;
T ***available_pool = nullptr;
@@ -53,7 +53,7 @@ class PagedAllocator {
SpinLock spin_lock;
public:
- template <class... Args>
+ template <typename... Args>
T *alloc(Args &&...p_args) {
if (thread_safe) {
spin_lock.lock();
@@ -95,7 +95,7 @@ public:
}
}
- template <class... Args>
+ template <typename... Args>
T *new_allocation(Args &&...p_args) { return alloc(p_args...); }
void delete_allocation(T *p_mem) { free(p_mem); }
diff --git a/core/templates/paged_array.h b/core/templates/paged_array.h
index 21053dd033..1aa8fa2485 100644
--- a/core/templates/paged_array.h
+++ b/core/templates/paged_array.h
@@ -41,7 +41,7 @@
// PageArrayPool manages central page allocation in a thread safe matter
-template <class T>
+template <typename T>
class PagedArrayPool {
T **page_pool = nullptr;
uint32_t pages_allocated = 0;
@@ -134,7 +134,7 @@ public:
// It does so by allocating pages from a PagedArrayPool.
// It is safe to use multiple PagedArrays from different threads, sharing a single PagedArrayPool
-template <class T>
+template <typename T>
class PagedArray {
PagedArrayPool<T> *page_pool = nullptr;
diff --git a/core/templates/pair.h b/core/templates/pair.h
index ff093a58e6..fd774641c2 100644
--- a/core/templates/pair.h
+++ b/core/templates/pair.h
@@ -33,7 +33,7 @@
#include "core/templates/hashfuncs.h"
#include "core/typedefs.h"
-template <class F, class S>
+template <typename F, typename S>
struct Pair {
F first;
S second;
@@ -49,17 +49,17 @@ struct Pair {
}
};
-template <class F, class S>
+template <typename F, typename S>
bool operator==(const Pair<F, S> &pair, const Pair<F, S> &other) {
return (pair.first == other.first) && (pair.second == other.second);
}
-template <class F, class S>
+template <typename F, typename S>
bool operator!=(const Pair<F, S> &pair, const Pair<F, S> &other) {
return (pair.first != other.first) || (pair.second != other.second);
}
-template <class F, class S>
+template <typename F, typename S>
struct PairSort {
bool operator()(const Pair<F, S> &A, const Pair<F, S> &B) const {
if (A.first != B.first) {
@@ -69,7 +69,7 @@ struct PairSort {
}
};
-template <class F, class S>
+template <typename F, typename S>
struct PairHash {
static uint32_t hash(const Pair<F, S> &P) {
uint64_t h1 = HashMapHasherDefault::hash(P.first);
@@ -78,7 +78,7 @@ struct PairHash {
}
};
-template <class K, class V>
+template <typename K, typename V>
struct KeyValue {
const K key;
V value;
@@ -94,17 +94,17 @@ struct KeyValue {
}
};
-template <class K, class V>
+template <typename K, typename V>
bool operator==(const KeyValue<K, V> &pair, const KeyValue<K, V> &other) {
return (pair.key == other.key) && (pair.value == other.value);
}
-template <class K, class V>
+template <typename K, typename V>
bool operator!=(const KeyValue<K, V> &pair, const KeyValue<K, V> &other) {
return (pair.key != other.key) || (pair.value != other.value);
}
-template <class K, class V>
+template <typename K, typename V>
struct KeyValueSort {
bool operator()(const KeyValue<K, V> &A, const KeyValue<K, V> &B) const {
return A.key < B.key;
diff --git a/core/templates/pooled_list.h b/core/templates/pooled_list.h
index 0d96bbb239..0e7048732e 100644
--- a/core/templates/pooled_list.h
+++ b/core/templates/pooled_list.h
@@ -55,7 +55,7 @@
#include "core/templates/local_vector.h"
-template <class T, class U = uint32_t, bool force_trivial = false, bool zero_on_first_request = false>
+template <typename T, typename U = uint32_t, bool force_trivial = false, bool zero_on_first_request = false>
class PooledList {
LocalVector<T, U, force_trivial> list;
LocalVector<U, U, true> freelist;
@@ -128,7 +128,7 @@ public:
};
// a pooled list which automatically keeps a list of the active members
-template <class T, class U = uint32_t, bool force_trivial = false, bool zero_on_first_request = false>
+template <typename T, typename U = uint32_t, bool force_trivial = false, bool zero_on_first_request = false>
class TrackedPooledList {
public:
U pool_used_size() const { return _pool.used_size(); }
diff --git a/core/templates/rb_map.h b/core/templates/rb_map.h
index 152fddd011..ef555e4a16 100644
--- a/core/templates/rb_map.h
+++ b/core/templates/rb_map.h
@@ -38,7 +38,7 @@
// based on the very nice implementation of rb-trees by:
// https://web.archive.org/web/20120507164830/https://web.mit.edu/~emin/www/source_code/red_black_tree/index.html
-template <class K, class V, class C = Comparator<K>, class A = DefaultAllocator>
+template <typename K, typename V, typename C = Comparator<K>, typename A = DefaultAllocator>
class RBMap {
enum Color {
RED,
diff --git a/core/templates/rb_set.h b/core/templates/rb_set.h
index 0fc88709e4..ac7a8df36a 100644
--- a/core/templates/rb_set.h
+++ b/core/templates/rb_set.h
@@ -37,7 +37,7 @@
// based on the very nice implementation of rb-trees by:
// https://web.archive.org/web/20120507164830/https://web.mit.edu/~emin/www/source_code/red_black_tree/index.html
-template <class T, class C = Comparator<T>, class A = DefaultAllocator>
+template <typename T, typename C = Comparator<T>, typename A = DefaultAllocator>
class RBSet {
enum Color {
RED,
diff --git a/core/templates/rid_owner.h b/core/templates/rid_owner.h
index f92e0f4162..86304d3c73 100644
--- a/core/templates/rid_owner.h
+++ b/core/templates/rid_owner.h
@@ -67,7 +67,7 @@ public:
virtual ~RID_AllocBase() {}
};
-template <class T, bool THREAD_SAFE = false>
+template <typename T, bool THREAD_SAFE = false>
class RID_Alloc : public RID_AllocBase {
T **chunks = nullptr;
uint32_t **free_list_chunks = nullptr;
@@ -364,7 +364,7 @@ public:
}
};
-template <class T, bool THREAD_SAFE = false>
+template <typename T, bool THREAD_SAFE = false>
class RID_PtrOwner {
RID_Alloc<T *, THREAD_SAFE> alloc;
@@ -423,7 +423,7 @@ public:
alloc(p_target_chunk_byte_size) {}
};
-template <class T, bool THREAD_SAFE = false>
+template <typename T, bool THREAD_SAFE = false>
class RID_Owner {
RID_Alloc<T, THREAD_SAFE> alloc;
diff --git a/core/templates/safe_list.h b/core/templates/safe_list.h
index 79457db24c..60ccdd9423 100644
--- a/core/templates/safe_list.h
+++ b/core/templates/safe_list.h
@@ -48,7 +48,7 @@
// This is used in very specific areas of the engine where it's critical that these guarantees are held.
-template <class T, class A = DefaultAllocator>
+template <typename T, typename A = DefaultAllocator>
class SafeList {
struct SafeListNode {
std::atomic<SafeListNode *> next = nullptr;
diff --git a/core/templates/safe_refcount.h b/core/templates/safe_refcount.h
index 7bbceadc8d..637b068da9 100644
--- a/core/templates/safe_refcount.h
+++ b/core/templates/safe_refcount.h
@@ -59,7 +59,7 @@
static_assert(sizeof(SafeFlag) == sizeof(bool)); \
static_assert(alignof(SafeFlag) == alignof(bool));
-template <class T>
+template <typename T>
class SafeNumeric {
std::atomic<T> value;
diff --git a/core/templates/search_array.h b/core/templates/search_array.h
index 9c3f527323..835bcd63a6 100644
--- a/core/templates/search_array.h
+++ b/core/templates/search_array.h
@@ -33,7 +33,7 @@
#include <core/templates/sort_array.h>
-template <class T, class Comparator = _DefaultComparator<T>>
+template <typename T, typename Comparator = _DefaultComparator<T>>
class SearchArray {
public:
Comparator compare;
diff --git a/core/templates/self_list.h b/core/templates/self_list.h
index afa0501c75..a04972594e 100644
--- a/core/templates/self_list.h
+++ b/core/templates/self_list.h
@@ -34,7 +34,7 @@
#include "core/error/error_macros.h"
#include "core/typedefs.h"
-template <class T>
+template <typename T>
class SelfList {
public:
class List {
@@ -109,7 +109,7 @@ public:
sort_custom<Comparator<T>>();
}
- template <class C>
+ template <typename C>
void sort_custom() {
if (_first == _last) {
return;
diff --git a/core/templates/simple_type.h b/core/templates/simple_type.h
index 3950158c5a..b2ae0110e2 100644
--- a/core/templates/simple_type.h
+++ b/core/templates/simple_type.h
@@ -33,22 +33,22 @@
/* Batch of specializations to obtain the actual simple type */
-template <class T>
+template <typename T>
struct GetSimpleTypeT {
typedef T type_t;
};
-template <class T>
+template <typename T>
struct GetSimpleTypeT<T &> {
typedef T type_t;
};
-template <class T>
+template <typename T>
struct GetSimpleTypeT<T const> {
typedef T type_t;
};
-template <class T>
+template <typename T>
struct GetSimpleTypeT<T const &> {
typedef T type_t;
};
diff --git a/core/templates/sort_array.h b/core/templates/sort_array.h
index 45aeaf1579..e7eaf8ee81 100644
--- a/core/templates/sort_array.h
+++ b/core/templates/sort_array.h
@@ -40,7 +40,7 @@
break; \
}
-template <class T>
+template <typename T>
struct _DefaultComparator {
_FORCE_INLINE_ bool operator()(const T &a, const T &b) const { return (a < b); }
};
@@ -51,7 +51,7 @@ struct _DefaultComparator {
#define SORT_ARRAY_VALIDATE_ENABLED false
#endif
-template <class T, class Comparator = _DefaultComparator<T>, bool Validate = SORT_ARRAY_VALIDATE_ENABLED>
+template <typename T, typename Comparator = _DefaultComparator<T>, bool Validate = SORT_ARRAY_VALIDATE_ENABLED>
class SortArray {
enum {
INTROSORT_THRESHOLD = 16
diff --git a/core/templates/vector.h b/core/templates/vector.h
index 0de6a34ced..52c10eea68 100644
--- a/core/templates/vector.h
+++ b/core/templates/vector.h
@@ -45,7 +45,7 @@
#include <climits>
#include <initializer_list>
-template <class T>
+template <typename T>
class VectorWriteProxy {
public:
_FORCE_INLINE_ T &operator[](typename CowData<T>::Size p_index) {
@@ -55,7 +55,7 @@ public:
}
};
-template <class T>
+template <typename T>
class Vector {
friend class VectorWriteProxy<T>;
@@ -108,7 +108,7 @@ public:
sort_custom<_DefaultComparator<T>>();
}
- template <class Comparator, bool Validate = SORT_ARRAY_VALIDATE_ENABLED, class... Args>
+ template <typename Comparator, bool Validate = SORT_ARRAY_VALIDATE_ENABLED, typename... Args>
void sort_custom(Args &&...args) {
Size len = _cowdata.size();
if (len == 0) {
@@ -124,7 +124,7 @@ public:
return bsearch_custom<_DefaultComparator<T>>(p_value, p_before);
}
- template <class Comparator, class Value, class... Args>
+ template <typename Comparator, typename Value, typename... Args>
Size bsearch_custom(const Value &p_value, bool p_before, Args &&...args) {
SearchArray<T, Comparator> search{ args... };
return search.bisect(ptrw(), size(), p_value, p_before);
@@ -291,7 +291,7 @@ public:
_FORCE_INLINE_ ~Vector() {}
};
-template <class T>
+template <typename T>
void Vector<T>::reverse() {
for (Size i = 0; i < size() / 2; i++) {
T *p = ptrw();
@@ -299,7 +299,7 @@ void Vector<T>::reverse() {
}
}
-template <class T>
+template <typename T>
void Vector<T>::append_array(const Vector<T> &p_other) {
const Size ds = p_other.size();
if (ds == 0) {
@@ -312,7 +312,7 @@ void Vector<T>::append_array(const Vector<T> &p_other) {
}
}
-template <class T>
+template <typename T>
bool Vector<T>::push_back(T p_elem) {
Error err = resize(size() + 1);
ERR_FAIL_COND_V(err, true);
@@ -321,7 +321,7 @@ bool Vector<T>::push_back(T p_elem) {
return false;
}
-template <class T>
+template <typename T>
void Vector<T>::fill(T p_elem) {
T *p = ptrw();
for (Size i = 0; i < size(); i++) {
diff --git a/core/templates/vmap.h b/core/templates/vmap.h
index 5620bd3772..2ad074942b 100644
--- a/core/templates/vmap.h
+++ b/core/templates/vmap.h
@@ -34,7 +34,7 @@
#include "core/templates/cowdata.h"
#include "core/typedefs.h"
-template <class T, class V>
+template <typename T, typename V>
class VMap {
public:
struct Pair {
diff --git a/core/templates/vset.h b/core/templates/vset.h
index a4886ca965..614d0e1e5f 100644
--- a/core/templates/vset.h
+++ b/core/templates/vset.h
@@ -34,7 +34,7 @@
#include "core/templates/vector.h"
#include "core/typedefs.h"
-template <class T>
+template <typename T>
class VSet {
Vector<T> _data;
diff --git a/core/typedefs.h b/core/typedefs.h
index 8807ee3c99..2b90a911cd 100644
--- a/core/typedefs.h
+++ b/core/typedefs.h
@@ -132,7 +132,7 @@ constexpr auto CLAMP(const T m_a, const T2 m_min, const T3 m_max) {
// Generic swap template.
#ifndef SWAP
#define SWAP(m_x, m_y) __swap_tmpl((m_x), (m_y))
-template <class T>
+template <typename T>
inline void __swap_tmpl(T &x, T &y) {
T aux = x;
x = y;
@@ -186,7 +186,7 @@ static inline int get_shift_from_power_of_2(unsigned int p_bits) {
return -1;
}
-template <class T>
+template <typename T>
static _FORCE_INLINE_ T nearest_power_of_2_templated(T x) {
--x;
@@ -256,7 +256,7 @@ static inline uint64_t BSWAP64(uint64_t x) {
#endif
// Generic comparator used in Map, List, etc.
-template <class T>
+template <typename T>
struct Comparator {
_ALWAYS_INLINE_ bool operator()(const T &p_a, const T &p_b) const { return (p_a < p_b); }
};
diff --git a/core/variant/binder_common.h b/core/variant/binder_common.h
index a44b938395..0fe4518b0f 100644
--- a/core/variant/binder_common.h
+++ b/core/variant/binder_common.h
@@ -47,7 +47,7 @@
// Variant cannot define an implicit cast operator for every Object subclass, so the
// casting is done here, to allow binding methods with parameters more specific than Object *
-template <class T>
+template <typename T>
struct VariantCaster {
static _FORCE_INLINE_ T cast(const Variant &p_variant) {
using TStripped = std::remove_pointer_t<T>;
@@ -59,7 +59,7 @@ struct VariantCaster {
}
};
-template <class T>
+template <typename T>
struct VariantCaster<T &> {
static _FORCE_INLINE_ T cast(const Variant &p_variant) {
using TStripped = std::remove_pointer_t<T>;
@@ -71,7 +71,7 @@ struct VariantCaster<T &> {
}
};
-template <class T>
+template <typename T>
struct VariantCaster<const T &> {
static _FORCE_INLINE_ T cast(const Variant &p_variant) {
using TStripped = std::remove_pointer_t<T>;
@@ -249,7 +249,7 @@ struct VariantObjectClassChecker<const Ref<T> &> {
#ifdef DEBUG_METHODS_ENABLED
-template <class T>
+template <typename T>
struct VariantCasterAndValidate {
static _FORCE_INLINE_ T cast(const Variant **p_args, uint32_t p_arg_idx, Callable::CallError &r_error) {
Variant::Type argtype = GetTypeInfo<T>::VARIANT_TYPE;
@@ -264,7 +264,7 @@ struct VariantCasterAndValidate {
}
};
-template <class T>
+template <typename T>
struct VariantCasterAndValidate<T &> {
static _FORCE_INLINE_ T cast(const Variant **p_args, uint32_t p_arg_idx, Callable::CallError &r_error) {
Variant::Type argtype = GetTypeInfo<T>::VARIANT_TYPE;
@@ -279,7 +279,7 @@ struct VariantCasterAndValidate<T &> {
}
};
-template <class T>
+template <typename T>
struct VariantCasterAndValidate<const T &> {
static _FORCE_INLINE_ T cast(const Variant **p_args, uint32_t p_arg_idx, Callable::CallError &r_error) {
Variant::Type argtype = GetTypeInfo<T>::VARIANT_TYPE;
@@ -296,7 +296,7 @@ struct VariantCasterAndValidate<const T &> {
#endif // DEBUG_METHODS_ENABLED
-template <class T, class... P, size_t... Is>
+template <typename T, typename... P, size_t... Is>
void call_with_variant_args_helper(T *p_instance, void (T::*p_method)(P...), const Variant **p_args, Callable::CallError &r_error, IndexSequence<Is...>) {
r_error.error = Callable::CallError::CALL_OK;
@@ -308,7 +308,7 @@ void call_with_variant_args_helper(T *p_instance, void (T::*p_method)(P...), con
(void)(p_args); //avoid warning
}
-template <class T, class... P, size_t... Is>
+template <typename T, typename... P, size_t... Is>
void call_with_variant_argsc_helper(T *p_instance, void (T::*p_method)(P...) const, const Variant **p_args, Callable::CallError &r_error, IndexSequence<Is...>) {
r_error.error = Callable::CallError::CALL_OK;
@@ -320,87 +320,87 @@ void call_with_variant_argsc_helper(T *p_instance, void (T::*p_method)(P...) con
(void)(p_args); //avoid warning
}
-template <class T, class... P, size_t... Is>
+template <typename T, typename... P, size_t... Is>
void call_with_ptr_args_helper(T *p_instance, void (T::*p_method)(P...), const void **p_args, IndexSequence<Is...>) {
(p_instance->*p_method)(PtrToArg<P>::convert(p_args[Is])...);
}
-template <class T, class... P, size_t... Is>
+template <typename T, typename... P, size_t... Is>
void call_with_ptr_argsc_helper(T *p_instance, void (T::*p_method)(P...) const, const void **p_args, IndexSequence<Is...>) {
(p_instance->*p_method)(PtrToArg<P>::convert(p_args[Is])...);
}
-template <class T, class R, class... P, size_t... Is>
+template <typename T, typename R, typename... P, size_t... Is>
void call_with_ptr_args_ret_helper(T *p_instance, R (T::*p_method)(P...), const void **p_args, void *r_ret, IndexSequence<Is...>) {
PtrToArg<R>::encode((p_instance->*p_method)(PtrToArg<P>::convert(p_args[Is])...), r_ret);
}
-template <class T, class R, class... P, size_t... Is>
+template <typename T, typename R, typename... P, size_t... Is>
void call_with_ptr_args_retc_helper(T *p_instance, R (T::*p_method)(P...) const, const void **p_args, void *r_ret, IndexSequence<Is...>) {
PtrToArg<R>::encode((p_instance->*p_method)(PtrToArg<P>::convert(p_args[Is])...), r_ret);
}
-template <class T, class... P, size_t... Is>
+template <typename T, typename... P, size_t... Is>
void call_with_ptr_args_static_helper(T *p_instance, void (*p_method)(T *, P...), const void **p_args, IndexSequence<Is...>) {
p_method(p_instance, PtrToArg<P>::convert(p_args[Is])...);
}
-template <class T, class R, class... P, size_t... Is>
+template <typename T, typename R, typename... P, size_t... Is>
void call_with_ptr_args_static_retc_helper(T *p_instance, R (*p_method)(T *, P...), const void **p_args, void *r_ret, IndexSequence<Is...>) {
PtrToArg<R>::encode(p_method(p_instance, PtrToArg<P>::convert(p_args[Is])...), r_ret);
}
-template <class R, class... P, size_t... Is>
+template <typename R, typename... P, size_t... Is>
void call_with_ptr_args_static_method_ret_helper(R (*p_method)(P...), const void **p_args, void *r_ret, IndexSequence<Is...>) {
PtrToArg<R>::encode(p_method(PtrToArg<P>::convert(p_args[Is])...), r_ret);
}
-template <class... P, size_t... Is>
+template <typename... P, size_t... Is>
void call_with_ptr_args_static_method_helper(void (*p_method)(P...), const void **p_args, IndexSequence<Is...>) {
p_method(PtrToArg<P>::convert(p_args[Is])...);
}
-template <class T, class... P, size_t... Is>
+template <typename T, typename... P, size_t... Is>
void call_with_validated_variant_args_helper(T *p_instance, void (T::*p_method)(P...), const Variant **p_args, IndexSequence<Is...>) {
(p_instance->*p_method)((VariantInternalAccessor<typename GetSimpleTypeT<P>::type_t>::get(p_args[Is]))...);
}
-template <class T, class... P, size_t... Is>
+template <typename T, typename... P, size_t... Is>
void call_with_validated_variant_argsc_helper(T *p_instance, void (T::*p_method)(P...) const, const Variant **p_args, IndexSequence<Is...>) {
(p_instance->*p_method)((VariantInternalAccessor<typename GetSimpleTypeT<P>::type_t>::get(p_args[Is]))...);
}
-template <class T, class R, class... P, size_t... Is>
+template <typename T, typename R, typename... P, size_t... Is>
void call_with_validated_variant_args_ret_helper(T *p_instance, R (T::*p_method)(P...), const Variant **p_args, Variant *r_ret, IndexSequence<Is...>) {
VariantInternalAccessor<typename GetSimpleTypeT<R>::type_t>::set(r_ret, (p_instance->*p_method)((VariantInternalAccessor<typename GetSimpleTypeT<P>::type_t>::get(p_args[Is]))...));
}
-template <class T, class R, class... P, size_t... Is>
+template <typename T, typename R, typename... P, size_t... Is>
void call_with_validated_variant_args_retc_helper(T *p_instance, R (T::*p_method)(P...) const, const Variant **p_args, Variant *r_ret, IndexSequence<Is...>) {
VariantInternalAccessor<typename GetSimpleTypeT<R>::type_t>::set(r_ret, (p_instance->*p_method)((VariantInternalAccessor<typename GetSimpleTypeT<P>::type_t>::get(p_args[Is]))...));
}
-template <class T, class R, class... P, size_t... Is>
+template <typename T, typename R, typename... P, size_t... Is>
void call_with_validated_variant_args_static_retc_helper(T *p_instance, R (*p_method)(T *, P...), const Variant **p_args, Variant *r_ret, IndexSequence<Is...>) {
VariantInternalAccessor<typename GetSimpleTypeT<R>::type_t>::set(r_ret, p_method(p_instance, (VariantInternalAccessor<typename GetSimpleTypeT<P>::type_t>::get(p_args[Is]))...));
}
-template <class T, class... P, size_t... Is>
+template <typename T, typename... P, size_t... Is>
void call_with_validated_variant_args_static_helper(T *p_instance, void (*p_method)(T *, P...), const Variant **p_args, IndexSequence<Is...>) {
p_method(p_instance, (VariantInternalAccessor<typename GetSimpleTypeT<P>::type_t>::get(p_args[Is]))...);
}
-template <class R, class... P, size_t... Is>
+template <typename R, typename... P, size_t... Is>
void call_with_validated_variant_args_static_method_ret_helper(R (*p_method)(P...), const Variant **p_args, Variant *r_ret, IndexSequence<Is...>) {
VariantInternalAccessor<typename GetSimpleTypeT<R>::type_t>::set(r_ret, p_method((VariantInternalAccessor<typename GetSimpleTypeT<P>::type_t>::get(p_args[Is]))...));
}
-template <class... P, size_t... Is>
+template <typename... P, size_t... Is>
void call_with_validated_variant_args_static_method_helper(void (*p_method)(P...), const Variant **p_args, IndexSequence<Is...>) {
p_method((VariantInternalAccessor<typename GetSimpleTypeT<P>::type_t>::get(p_args[Is]))...);
}
-template <class T, class... P>
+template <typename T, typename... P>
void call_with_variant_args(T *p_instance, void (T::*p_method)(P...), const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
#ifdef DEBUG_METHODS_ENABLED
if ((size_t)p_argcount > sizeof...(P)) {
@@ -418,7 +418,7 @@ void call_with_variant_args(T *p_instance, void (T::*p_method)(P...), const Vari
call_with_variant_args_helper<T, P...>(p_instance, p_method, p_args, r_error, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class... P>
+template <typename T, typename... P>
void call_with_variant_args_dv(T *p_instance, void (T::*p_method)(P...), const Variant **p_args, int p_argcount, Callable::CallError &r_error, const Vector<Variant> &default_values) {
#ifdef DEBUG_ENABLED
if ((size_t)p_argcount > sizeof...(P)) {
@@ -451,7 +451,7 @@ void call_with_variant_args_dv(T *p_instance, void (T::*p_method)(P...), const V
call_with_variant_args_helper(p_instance, p_method, args, r_error, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class... P>
+template <typename T, typename... P>
void call_with_variant_argsc(T *p_instance, void (T::*p_method)(P...) const, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
#ifdef DEBUG_METHODS_ENABLED
if ((size_t)p_argcount > sizeof...(P)) {
@@ -469,7 +469,7 @@ void call_with_variant_argsc(T *p_instance, void (T::*p_method)(P...) const, con
call_with_variant_args_helper<T, P...>(p_instance, p_method, p_args, r_error, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class... P>
+template <typename T, typename... P>
void call_with_variant_argsc_dv(T *p_instance, void (T::*p_method)(P...) const, const Variant **p_args, int p_argcount, Callable::CallError &r_error, const Vector<Variant> &default_values) {
#ifdef DEBUG_ENABLED
if ((size_t)p_argcount > sizeof...(P)) {
@@ -502,7 +502,7 @@ void call_with_variant_argsc_dv(T *p_instance, void (T::*p_method)(P...) const,
call_with_variant_argsc_helper(p_instance, p_method, args, r_error, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
void call_with_variant_args_ret_dv(T *p_instance, R (T::*p_method)(P...), const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error, const Vector<Variant> &default_values) {
#ifdef DEBUG_ENABLED
if ((size_t)p_argcount > sizeof...(P)) {
@@ -535,7 +535,7 @@ void call_with_variant_args_ret_dv(T *p_instance, R (T::*p_method)(P...), const
call_with_variant_args_ret_helper(p_instance, p_method, args, r_ret, r_error, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
void call_with_variant_args_retc_dv(T *p_instance, R (T::*p_method)(P...) const, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error, const Vector<Variant> &default_values) {
#ifdef DEBUG_ENABLED
if ((size_t)p_argcount > sizeof...(P)) {
@@ -568,111 +568,111 @@ void call_with_variant_args_retc_dv(T *p_instance, R (T::*p_method)(P...) const,
call_with_variant_args_retc_helper(p_instance, p_method, args, r_ret, r_error, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class... P>
+template <typename T, typename... P>
void call_with_ptr_args(T *p_instance, void (T::*p_method)(P...), const void **p_args) {
call_with_ptr_args_helper<T, P...>(p_instance, p_method, p_args, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class... P>
+template <typename T, typename... P>
void call_with_ptr_argsc(T *p_instance, void (T::*p_method)(P...) const, const void **p_args) {
call_with_ptr_argsc_helper<T, P...>(p_instance, p_method, p_args, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
void call_with_ptr_args_ret(T *p_instance, R (T::*p_method)(P...), const void **p_args, void *r_ret) {
call_with_ptr_args_ret_helper<T, R, P...>(p_instance, p_method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
void call_with_ptr_args_retc(T *p_instance, R (T::*p_method)(P...) const, const void **p_args, void *r_ret) {
call_with_ptr_args_retc_helper<T, R, P...>(p_instance, p_method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class... P>
+template <typename T, typename... P>
void call_with_ptr_args_static(T *p_instance, void (*p_method)(T *, P...), const void **p_args) {
call_with_ptr_args_static_helper<T, P...>(p_instance, p_method, p_args, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
void call_with_ptr_args_static_retc(T *p_instance, R (*p_method)(T *, P...), const void **p_args, void *r_ret) {
call_with_ptr_args_static_retc_helper<T, R, P...>(p_instance, p_method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
}
-template <class R, class... P>
+template <typename R, typename... P>
void call_with_ptr_args_static_method_ret(R (*p_method)(P...), const void **p_args, void *r_ret) {
call_with_ptr_args_static_method_ret_helper<R, P...>(p_method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
}
-template <class... P>
+template <typename... P>
void call_with_ptr_args_static_method(void (*p_method)(P...), const void **p_args) {
call_with_ptr_args_static_method_helper<P...>(p_method, p_args, BuildIndexSequence<sizeof...(P)>{});
}
// Validated
-template <class T, class... P>
+template <typename T, typename... P>
void call_with_validated_variant_args(Variant *base, void (T::*p_method)(P...), const Variant **p_args) {
call_with_validated_variant_args_helper<T, P...>(VariantGetInternalPtr<T>::get_ptr(base), p_method, p_args, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
void call_with_validated_variant_args_ret(Variant *base, R (T::*p_method)(P...), const Variant **p_args, Variant *r_ret) {
call_with_validated_variant_args_ret_helper<T, R, P...>(VariantGetInternalPtr<T>::get_ptr(base), p_method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
void call_with_validated_variant_args_retc(Variant *base, R (T::*p_method)(P...) const, const Variant **p_args, Variant *r_ret) {
call_with_validated_variant_args_retc_helper<T, R, P...>(VariantGetInternalPtr<T>::get_ptr(base), p_method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class... P>
+template <typename T, typename... P>
void call_with_validated_variant_args_static(Variant *base, void (*p_method)(T *, P...), const Variant **p_args) {
call_with_validated_variant_args_static_helper<T, P...>(VariantGetInternalPtr<T>::get_ptr(base), p_method, p_args, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
void call_with_validated_variant_args_static_retc(Variant *base, R (*p_method)(T *, P...), const Variant **p_args, Variant *r_ret) {
call_with_validated_variant_args_static_retc_helper<T, R, P...>(VariantGetInternalPtr<T>::get_ptr(base), p_method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
}
-template <class... P>
+template <typename... P>
void call_with_validated_variant_args_static_method(void (*p_method)(P...), const Variant **p_args) {
call_with_validated_variant_args_static_method_helper<P...>(p_method, p_args, BuildIndexSequence<sizeof...(P)>{});
}
-template <class R, class... P>
+template <typename R, typename... P>
void call_with_validated_variant_args_static_method_ret(R (*p_method)(P...), const Variant **p_args, Variant *r_ret) {
call_with_validated_variant_args_static_method_ret_helper<R, P...>(p_method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
}
// Validated Object
-template <class T, class... P>
+template <typename T, typename... P>
void call_with_validated_object_instance_args(T *base, void (T::*p_method)(P...), const Variant **p_args) {
call_with_validated_variant_args_helper<T, P...>(base, p_method, p_args, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class... P>
+template <typename T, typename... P>
void call_with_validated_object_instance_argsc(T *base, void (T::*p_method)(P...) const, const Variant **p_args) {
call_with_validated_variant_argsc_helper<T, P...>(base, p_method, p_args, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
void call_with_validated_object_instance_args_ret(T *base, R (T::*p_method)(P...), const Variant **p_args, Variant *r_ret) {
call_with_validated_variant_args_ret_helper<T, R, P...>(base, p_method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
void call_with_validated_object_instance_args_retc(T *base, R (T::*p_method)(P...) const, const Variant **p_args, Variant *r_ret) {
call_with_validated_variant_args_retc_helper<T, R, P...>(base, p_method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class... P>
+template <typename T, typename... P>
void call_with_validated_object_instance_args_static(T *base, void (*p_method)(T *, P...), const Variant **p_args) {
call_with_validated_variant_args_static_helper<T, P...>(base, p_method, p_args, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
void call_with_validated_object_instance_args_static_retc(T *base, R (*p_method)(T *, P...), const Variant **p_args, Variant *r_ret) {
call_with_validated_variant_args_static_retc_helper<T, R, P...>(base, p_method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
}
@@ -684,7 +684,7 @@ void call_with_validated_object_instance_args_static_retc(T *base, R (*p_method)
#pragma GCC diagnostic ignored "-Wunused-but-set-parameter"
#endif
-template <class Q>
+template <typename Q>
void call_get_argument_type_helper(int p_arg, int &index, Variant::Type &type) {
if (p_arg == index) {
type = GetTypeInfo<Q>::VARIANT_TYPE;
@@ -692,7 +692,7 @@ void call_get_argument_type_helper(int p_arg, int &index, Variant::Type &type) {
index++;
}
-template <class... P>
+template <typename... P>
Variant::Type call_get_argument_type(int p_arg) {
Variant::Type type = Variant::NIL;
int index = 0;
@@ -704,7 +704,7 @@ Variant::Type call_get_argument_type(int p_arg) {
return type;
}
-template <class Q>
+template <typename Q>
void call_get_argument_type_info_helper(int p_arg, int &index, PropertyInfo &info) {
if (p_arg == index) {
info = GetTypeInfo<Q>::get_class_info();
@@ -712,7 +712,7 @@ void call_get_argument_type_info_helper(int p_arg, int &index, PropertyInfo &inf
index++;
}
-template <class... P>
+template <typename... P>
void call_get_argument_type_info(int p_arg, PropertyInfo &info) {
int index = 0;
// I think rocket science is simpler than modern C++.
@@ -723,7 +723,7 @@ void call_get_argument_type_info(int p_arg, PropertyInfo &info) {
}
#ifdef DEBUG_METHODS_ENABLED
-template <class Q>
+template <typename Q>
void call_get_argument_metadata_helper(int p_arg, int &index, GodotTypeInfo::Metadata &md) {
if (p_arg == index) {
md = GetTypeInfo<Q>::METADATA;
@@ -731,7 +731,7 @@ void call_get_argument_metadata_helper(int p_arg, int &index, GodotTypeInfo::Met
index++;
}
-template <class... P>
+template <typename... P>
GodotTypeInfo::Metadata call_get_argument_metadata(int p_arg) {
GodotTypeInfo::Metadata md = GodotTypeInfo::METADATA_NONE;
@@ -748,7 +748,7 @@ GodotTypeInfo::Metadata call_get_argument_metadata(int p_arg) {
//////////////////////
-template <class T, class R, class... P, size_t... Is>
+template <typename T, typename R, typename... P, size_t... Is>
void call_with_variant_args_ret_helper(T *p_instance, R (T::*p_method)(P...), const Variant **p_args, Variant &r_ret, Callable::CallError &r_error, IndexSequence<Is...>) {
r_error.error = Callable::CallError::CALL_OK;
@@ -759,7 +759,7 @@ void call_with_variant_args_ret_helper(T *p_instance, R (T::*p_method)(P...), co
#endif
}
-template <class R, class... P, size_t... Is>
+template <typename R, typename... P, size_t... Is>
void call_with_variant_args_static_ret(R (*p_method)(P...), const Variant **p_args, Variant &r_ret, Callable::CallError &r_error, IndexSequence<Is...>) {
r_error.error = Callable::CallError::CALL_OK;
@@ -770,7 +770,7 @@ void call_with_variant_args_static_ret(R (*p_method)(P...), const Variant **p_ar
#endif
}
-template <class... P, size_t... Is>
+template <typename... P, size_t... Is>
void call_with_variant_args_static(void (*p_method)(P...), const Variant **p_args, Callable::CallError &r_error, IndexSequence<Is...>) {
r_error.error = Callable::CallError::CALL_OK;
@@ -781,7 +781,7 @@ void call_with_variant_args_static(void (*p_method)(P...), const Variant **p_arg
#endif
}
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
void call_with_variant_args_ret(T *p_instance, R (T::*p_method)(P...), const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) {
#ifdef DEBUG_METHODS_ENABLED
if ((size_t)p_argcount > sizeof...(P)) {
@@ -799,7 +799,7 @@ void call_with_variant_args_ret(T *p_instance, R (T::*p_method)(P...), const Var
call_with_variant_args_ret_helper<T, R, P...>(p_instance, p_method, p_args, r_ret, r_error, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class R, class... P, size_t... Is>
+template <typename T, typename R, typename... P, size_t... Is>
void call_with_variant_args_retc_helper(T *p_instance, R (T::*p_method)(P...) const, const Variant **p_args, Variant &r_ret, Callable::CallError &r_error, IndexSequence<Is...>) {
r_error.error = Callable::CallError::CALL_OK;
@@ -811,7 +811,7 @@ void call_with_variant_args_retc_helper(T *p_instance, R (T::*p_method)(P...) co
(void)p_args;
}
-template <class R, class... P>
+template <typename R, typename... P>
void call_with_variant_args_static_ret(R (*p_method)(P...), const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) {
#ifdef DEBUG_METHODS_ENABLED
if ((size_t)p_argcount > sizeof...(P)) {
@@ -829,7 +829,7 @@ void call_with_variant_args_static_ret(R (*p_method)(P...), const Variant **p_ar
call_with_variant_args_static_ret<R, P...>(p_method, p_args, r_ret, r_error, BuildIndexSequence<sizeof...(P)>{});
}
-template <class... P>
+template <typename... P>
void call_with_variant_args_static_ret(void (*p_method)(P...), const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) {
#ifdef DEBUG_METHODS_ENABLED
if ((size_t)p_argcount > sizeof...(P)) {
@@ -847,7 +847,7 @@ void call_with_variant_args_static_ret(void (*p_method)(P...), const Variant **p
call_with_variant_args_static<P...>(p_method, p_args, r_error, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
void call_with_variant_args_retc(T *p_instance, R (T::*p_method)(P...) const, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) {
#ifdef DEBUG_METHODS_ENABLED
if ((size_t)p_argcount > sizeof...(P)) {
@@ -865,7 +865,7 @@ void call_with_variant_args_retc(T *p_instance, R (T::*p_method)(P...) const, co
call_with_variant_args_retc_helper<T, R, P...>(p_instance, p_method, p_args, r_ret, r_error, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class R, class... P, size_t... Is>
+template <typename T, typename R, typename... P, size_t... Is>
void call_with_variant_args_retc_static_helper(T *p_instance, R (*p_method)(T *, P...), const Variant **p_args, Variant &r_ret, Callable::CallError &r_error, IndexSequence<Is...>) {
r_error.error = Callable::CallError::CALL_OK;
@@ -878,7 +878,7 @@ void call_with_variant_args_retc_static_helper(T *p_instance, R (*p_method)(T *,
(void)p_args;
}
-template <class T, class R, class... P>
+template <typename T, typename R, typename... P>
void call_with_variant_args_retc_static_helper_dv(T *p_instance, R (*p_method)(T *, P...), const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &default_values, Callable::CallError &r_error) {
#ifdef DEBUG_ENABLED
if ((size_t)p_argcount > sizeof...(P)) {
@@ -911,7 +911,7 @@ void call_with_variant_args_retc_static_helper_dv(T *p_instance, R (*p_method)(T
call_with_variant_args_retc_static_helper(p_instance, p_method, args, r_ret, r_error, BuildIndexSequence<sizeof...(P)>{});
}
-template <class T, class... P, size_t... Is>
+template <typename T, typename... P, size_t... Is>
void call_with_variant_args_static_helper(T *p_instance, void (*p_method)(T *, P...), const Variant **p_args, Callable::CallError &r_error, IndexSequence<Is...>) {
r_error.error = Callable::CallError::CALL_OK;
@@ -924,7 +924,7 @@ void call_with_variant_args_static_helper(T *p_instance, void (*p_method)(T *, P
(void)p_args;
}
-template <class T, class... P>
+template <typename T, typename... P>
void call_with_variant_args_static_helper_dv(T *p_instance, void (*p_method)(T *, P...), const Variant **p_args, int p_argcount, const Vector<Variant> &default_values, Callable::CallError &r_error) {
#ifdef DEBUG_ENABLED
if ((size_t)p_argcount > sizeof...(P)) {
@@ -957,7 +957,7 @@ void call_with_variant_args_static_helper_dv(T *p_instance, void (*p_method)(T *
call_with_variant_args_static_helper(p_instance, p_method, args, r_error, BuildIndexSequence<sizeof...(P)>{});
}
-template <class R, class... P>
+template <typename R, typename... P>
void call_with_variant_args_static_ret_dv(R (*p_method)(P...), const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error, const Vector<Variant> &default_values) {
#ifdef DEBUG_ENABLED
if ((size_t)p_argcount > sizeof...(P)) {
@@ -990,7 +990,7 @@ void call_with_variant_args_static_ret_dv(R (*p_method)(P...), const Variant **p
call_with_variant_args_static_ret(p_method, args, r_ret, r_error, BuildIndexSequence<sizeof...(P)>{});
}
-template <class... P>
+template <typename... P>
void call_with_variant_args_static_dv(void (*p_method)(P...), const Variant **p_args, int p_argcount, Callable::CallError &r_error, const Vector<Variant> &default_values) {
#ifdef DEBUG_ENABLED
if ((size_t)p_argcount > sizeof...(P)) {
diff --git a/core/variant/method_ptrcall.h b/core/variant/method_ptrcall.h
index 1be54ba3fd..123f2067e2 100644
--- a/core/variant/method_ptrcall.h
+++ b/core/variant/method_ptrcall.h
@@ -35,7 +35,7 @@
#include "core/typedefs.h"
#include "core/variant/variant.h"
-template <class T>
+template <typename T>
struct PtrToArg {};
#define MAKE_PTRARG(m_type) \
@@ -156,7 +156,7 @@ MAKE_PTRARG_BY_REFERENCE(Variant);
// This is for Object.
-template <class T>
+template <typename T>
struct PtrToArg<T *> {
_FORCE_INLINE_ static T *convert(const void *p_ptr) {
if (p_ptr == nullptr) {
@@ -170,7 +170,7 @@ struct PtrToArg<T *> {
}
};
-template <class T>
+template <typename T>
struct PtrToArg<const T *> {
_FORCE_INLINE_ static const T *convert(const void *p_ptr) {
if (p_ptr == nullptr) {
diff --git a/core/variant/native_ptr.h b/core/variant/native_ptr.h
index 9199b12845..33ba038132 100644
--- a/core/variant/native_ptr.h
+++ b/core/variant/native_ptr.h
@@ -35,7 +35,7 @@
#include "core/variant/method_ptrcall.h"
#include "core/variant/type_info.h"
-template <class T>
+template <typename T>
struct GDExtensionConstPtr {
const T *data = nullptr;
GDExtensionConstPtr(const T *p_assign) { data = p_assign; }
@@ -44,7 +44,7 @@ struct GDExtensionConstPtr {
operator Variant() const { return uint64_t(data); }
};
-template <class T>
+template <typename T>
struct GDExtensionPtr {
T *data = nullptr;
GDExtensionPtr(T *p_assign) { data = p_assign; }
@@ -95,7 +95,7 @@ struct GDExtensionPtr {
static _FORCE_INLINE_ void set(Variant *v, const GDExtensionPtr<m_type> &p_value) { *VariantInternal::get_int(v) = uint64_t(p_value.data); } \
};
-template <class T>
+template <typename T>
struct GetTypeInfo<GDExtensionConstPtr<T>> {
static const Variant::Type VARIANT_TYPE = Variant::NIL;
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
@@ -104,7 +104,7 @@ struct GetTypeInfo<GDExtensionConstPtr<T>> {
}
};
-template <class T>
+template <typename T>
struct GetTypeInfo<GDExtensionPtr<T>> {
static const Variant::Type VARIANT_TYPE = Variant::NIL;
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
@@ -113,7 +113,7 @@ struct GetTypeInfo<GDExtensionPtr<T>> {
}
};
-template <class T>
+template <typename T>
struct PtrToArg<GDExtensionConstPtr<T>> {
_FORCE_INLINE_ static GDExtensionConstPtr<T> convert(const void *p_ptr) {
return GDExtensionConstPtr<T>(reinterpret_cast<const T *>(p_ptr));
@@ -123,7 +123,7 @@ struct PtrToArg<GDExtensionConstPtr<T>> {
*((const T **)p_ptr) = p_val.data;
}
};
-template <class T>
+template <typename T>
struct PtrToArg<GDExtensionPtr<T>> {
_FORCE_INLINE_ static GDExtensionPtr<T> convert(const void *p_ptr) {
return GDExtensionPtr<T>(reinterpret_cast<const T *>(p_ptr));
diff --git a/core/variant/type_info.h b/core/variant/type_info.h
index 49c4db8229..32c410463b 100644
--- a/core/variant/type_info.h
+++ b/core/variant/type_info.h
@@ -80,7 +80,7 @@ enum Metadata {
// If 'T' is a class that inherits 'Object', make sure it can see the actual class declaration
// instead of a forward declaration. You can always forward declare 'T' in a header file, and then
// include the actual declaration of 'T' in the source file where 'GetTypeInfo<T>' is instantiated.
-template <class T, typename = void>
+template <typename T, typename = void>
struct GetTypeInfo;
#define MAKE_TYPE_INFO(m_type, m_var_type) \
@@ -278,7 +278,7 @@ inline StringName __constant_get_enum_name(T param, const String &p_constant) {
return GetTypeInfo<T>::get_class_info().class_name;
}
-template <class T>
+template <typename T>
class BitField {
int64_t value = 0;
diff --git a/core/variant/typed_array.h b/core/variant/typed_array.h
index ed973b9daa..0befd19864 100644
--- a/core/variant/typed_array.h
+++ b/core/variant/typed_array.h
@@ -38,7 +38,7 @@
#include "core/variant/type_info.h"
#include "core/variant/variant.h"
-template <class T>
+template <typename T>
class TypedArray : public Array {
public:
_FORCE_INLINE_ void operator=(const Array &p_array) {
@@ -56,12 +56,12 @@ public:
}
};
-template <class T>
+template <typename T>
struct VariantInternalAccessor<TypedArray<T>> {
static _FORCE_INLINE_ TypedArray<T> get(const Variant *v) { return *VariantInternal::get_array(v); }
static _FORCE_INLINE_ void set(Variant *v, const TypedArray<T> &p_array) { *VariantInternal::get_array(v) = p_array; }
};
-template <class T>
+template <typename T>
struct VariantInternalAccessor<const TypedArray<T> &> {
static _FORCE_INLINE_ TypedArray<T> get(const Variant *v) { return *VariantInternal::get_array(v); }
static _FORCE_INLINE_ void set(Variant *v, const TypedArray<T> &p_array) { *VariantInternal::get_array(v) = p_array; }
@@ -136,7 +136,7 @@ MAKE_TYPED_ARRAY(PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY)
MAKE_TYPED_ARRAY(PackedColorArray, Variant::PACKED_COLOR_ARRAY)
MAKE_TYPED_ARRAY(IPAddress, Variant::STRING)
-template <class T>
+template <typename T>
struct PtrToArg<TypedArray<T>> {
_FORCE_INLINE_ static TypedArray<T> convert(const void *p_ptr) {
return TypedArray<T>(*reinterpret_cast<const Array *>(p_ptr));
@@ -147,7 +147,7 @@ struct PtrToArg<TypedArray<T>> {
}
};
-template <class T>
+template <typename T>
struct PtrToArg<const TypedArray<T> &> {
typedef Array EncodeT;
_FORCE_INLINE_ static TypedArray<T> convert(const void *p_ptr) {
@@ -155,7 +155,7 @@ struct PtrToArg<const TypedArray<T> &> {
}
};
-template <class T>
+template <typename T>
struct GetTypeInfo<TypedArray<T>> {
static const Variant::Type VARIANT_TYPE = Variant::ARRAY;
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
@@ -164,7 +164,7 @@ struct GetTypeInfo<TypedArray<T>> {
}
};
-template <class T>
+template <typename T>
struct GetTypeInfo<const TypedArray<T> &> {
static const Variant::Type VARIANT_TYPE = Variant::ARRAY;
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index ce5423fafc..89c22c05dd 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -1697,7 +1697,7 @@ String stringify_variant_clean(const Variant &p_variant, int recursion_count) {
return s;
}
-template <class T>
+template <typename T>
String stringify_vector(const T &vec, int recursion_count) {
String str("[");
for (int i = 0; i < vec.size(); i++) {
@@ -2188,7 +2188,7 @@ Variant::operator Signal() const {
}
}
-template <class DA, class SA>
+template <typename DA, typename SA>
inline DA _convert_array(const SA &p_array) {
DA da;
da.resize(p_array.size());
@@ -2200,7 +2200,7 @@ inline DA _convert_array(const SA &p_array) {
return da;
}
-template <class DA>
+template <typename DA>
inline DA _convert_array_from_variant(const Variant &p_variant) {
switch (p_variant.get_type()) {
case Variant::ARRAY: {
diff --git a/core/variant/variant.h b/core/variant/variant.h
index c358559c9b..e8eec8171b 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -204,7 +204,7 @@ private:
_FORCE_INLINE_ virtual ~PackedArrayRefBase() {} //needs virtual destructor, but make inline
};
- template <class T>
+ template <typename T>
struct PackedArrayRef : public PackedArrayRefBase {
Vector<T> array;
static _FORCE_INLINE_ PackedArrayRef<T> *create() {
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index 40c9a588d8..ba7c44e405 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -43,329 +43,329 @@
typedef void (*VariantFunc)(Variant &r_ret, Variant &p_self, const Variant **p_args);
typedef void (*VariantConstructFunc)(Variant &r_ret, const Variant **p_args);
-template <class R, class... P>
+template <typename R, typename... P>
static _FORCE_INLINE_ void vc_static_method_call(R (*method)(P...), const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) {
call_with_variant_args_static_ret_dv(method, p_args, p_argcount, r_ret, r_error, p_defvals);
}
-template <class... P>
+template <typename... P>
static _FORCE_INLINE_ void vc_static_method_call(void (*method)(P...), const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) {
call_with_variant_args_static_dv(method, p_args, p_argcount, r_error, p_defvals);
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ void vc_method_call(R (T::*method)(P...), Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) {
call_with_variant_args_ret_dv(VariantGetInternalPtr<T>::get_ptr(base), method, p_args, p_argcount, r_ret, r_error, p_defvals);
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ void vc_method_call(R (T::*method)(P...) const, Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) {
call_with_variant_args_retc_dv(VariantGetInternalPtr<T>::get_ptr(base), method, p_args, p_argcount, r_ret, r_error, p_defvals);
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ void vc_method_call(void (T::*method)(P...), Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) {
VariantInternal::clear(&r_ret);
call_with_variant_args_dv(VariantGetInternalPtr<T>::get_ptr(base), method, p_args, p_argcount, r_error, p_defvals);
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ void vc_method_call(void (T::*method)(P...) const, Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) {
VariantInternal::clear(&r_ret);
call_with_variant_argsc_dv(VariantGetInternalPtr<T>::get_ptr(base), method, p_args, p_argcount, r_error, p_defvals);
}
-template <class From, class R, class T, class... P>
+template <typename From, typename R, typename T, typename... P>
static _FORCE_INLINE_ void vc_convert_method_call(R (T::*method)(P...), Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) {
T converted(static_cast<T>(*VariantGetInternalPtr<From>::get_ptr(base)));
call_with_variant_args_ret_dv(&converted, method, p_args, p_argcount, r_ret, r_error, p_defvals);
}
-template <class From, class R, class T, class... P>
+template <typename From, typename R, typename T, typename... P>
static _FORCE_INLINE_ void vc_convert_method_call(R (T::*method)(P...) const, Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) {
T converted(static_cast<T>(*VariantGetInternalPtr<From>::get_ptr(base)));
call_with_variant_args_retc_dv(&converted, method, p_args, p_argcount, r_ret, r_error, p_defvals);
}
-template <class From, class T, class... P>
+template <typename From, typename T, typename... P>
static _FORCE_INLINE_ void vc_convert_method_call(void (T::*method)(P...), Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) {
T converted(static_cast<T>(*VariantGetInternalPtr<From>::get_ptr(base)));
call_with_variant_args_dv(&converted, method, p_args, p_argcount, r_error, p_defvals);
}
-template <class From, class T, class... P>
+template <typename From, typename T, typename... P>
static _FORCE_INLINE_ void vc_convert_method_call(void (T::*method)(P...) const, Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) {
T converted(static_cast<T>(*VariantGetInternalPtr<From>::get_ptr(base)));
call_with_variant_argsc_dv(&converted, method, p_args, p_argcount, r_error, p_defvals);
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ void vc_method_call_static(R (*method)(T *, P...), Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) {
call_with_variant_args_retc_static_helper_dv(VariantGetInternalPtr<T>::get_ptr(base), method, p_args, p_argcount, r_ret, p_defvals, r_error);
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ void vc_method_call_static(void (*method)(T *, P...), Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) {
call_with_variant_args_static_helper_dv(VariantGetInternalPtr<T>::get_ptr(base), method, p_args, p_argcount, p_defvals, r_error);
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ void vc_validated_call(R (T::*method)(P...), Variant *base, const Variant **p_args, Variant *r_ret) {
call_with_validated_variant_args_ret(base, method, p_args, r_ret);
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ void vc_validated_call(R (T::*method)(P...) const, Variant *base, const Variant **p_args, Variant *r_ret) {
call_with_validated_variant_args_retc(base, method, p_args, r_ret);
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ void vc_validated_call(void (T::*method)(P...), Variant *base, const Variant **p_args, Variant *r_ret) {
call_with_validated_variant_args(base, method, p_args);
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ void vc_validated_call(void (T::*method)(P...) const, Variant *base, const Variant **p_args, Variant *r_ret) {
call_with_validated_variant_argsc(base, method, p_args);
}
-template <class From, class R, class T, class... P>
+template <typename From, typename R, typename T, typename... P>
static _FORCE_INLINE_ void vc_convert_validated_call(R (T::*method)(P...), Variant *base, const Variant **p_args, Variant *r_ret) {
T converted(static_cast<T>(*VariantGetInternalPtr<From>::get_ptr(base)));
call_with_validated_variant_args_ret_helper<T, R, P...>(&converted, method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
}
-template <class From, class R, class T, class... P>
+template <typename From, typename R, typename T, typename... P>
static _FORCE_INLINE_ void vc_convert_validated_call(R (T::*method)(P...) const, Variant *base, const Variant **p_args, Variant *r_ret) {
T converted(static_cast<T>(*VariantGetInternalPtr<From>::get_ptr(base)));
call_with_validated_variant_args_retc_helper<T, R, P...>(&converted, method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
}
-template <class From, class T, class... P>
+template <typename From, typename T, typename... P>
static _FORCE_INLINE_ void vc_convert_validated_call(void (T::*method)(P...), Variant *base, const Variant **p_args, Variant *r_ret) {
T converted(static_cast<T>(*VariantGetInternalPtr<From>::get_ptr(base)));
call_with_validated_variant_args_helper<T, P...>(&converted, method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
}
-template <class From, class T, class... P>
+template <typename From, typename T, typename... P>
static _FORCE_INLINE_ void vc_convert_validated_call(void (T::*method)(P...) const, Variant *base, const Variant **p_args, Variant *r_ret) {
T converted(static_cast<T>(*VariantGetInternalPtr<From>::get_ptr(base)));
call_with_validated_variant_argsc_helper<T, P...>(&converted, method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ void vc_validated_call_static(R (*method)(T *, P...), Variant *base, const Variant **p_args, Variant *r_ret) {
call_with_validated_variant_args_static_retc(base, method, p_args, r_ret);
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ void vc_validated_call_static(void (*method)(T *, P...), Variant *base, const Variant **p_args, Variant *r_ret) {
call_with_validated_variant_args_static(base, method, p_args);
}
-template <class R, class... P>
+template <typename R, typename... P>
static _FORCE_INLINE_ void vc_validated_static_call(R (*method)(P...), const Variant **p_args, Variant *r_ret) {
call_with_validated_variant_args_static_method_ret(method, p_args, r_ret);
}
-template <class... P>
+template <typename... P>
static _FORCE_INLINE_ void vc_validated_static_call(void (*method)(P...), const Variant **p_args, Variant *r_ret) {
call_with_validated_variant_args_static_method(method, p_args);
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ void vc_ptrcall(R (T::*method)(P...), void *p_base, const void **p_args, void *r_ret) {
call_with_ptr_args_ret(reinterpret_cast<T *>(p_base), method, p_args, r_ret);
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ void vc_ptrcall(R (T::*method)(P...) const, void *p_base, const void **p_args, void *r_ret) {
call_with_ptr_args_retc(reinterpret_cast<T *>(p_base), method, p_args, r_ret);
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ void vc_ptrcall(void (T::*method)(P...), void *p_base, const void **p_args, void *r_ret) {
call_with_ptr_args(reinterpret_cast<T *>(p_base), method, p_args);
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ void vc_ptrcall(void (T::*method)(P...) const, void *p_base, const void **p_args, void *r_ret) {
call_with_ptr_argsc(reinterpret_cast<T *>(p_base), method, p_args);
}
-template <class From, class R, class T, class... P>
+template <typename From, typename R, typename T, typename... P>
static _FORCE_INLINE_ void vc_convert_ptrcall(R (T::*method)(P...), void *p_base, const void **p_args, void *r_ret) {
T converted(*reinterpret_cast<From *>(p_base));
call_with_ptr_args_ret(&converted, method, p_args, r_ret);
}
-template <class From, class R, class T, class... P>
+template <typename From, typename R, typename T, typename... P>
static _FORCE_INLINE_ void vc_convert_ptrcall(R (T::*method)(P...) const, void *p_base, const void **p_args, void *r_ret) {
T converted(*reinterpret_cast<From *>(p_base));
call_with_ptr_args_retc(&converted, method, p_args, r_ret);
}
-template <class From, class T, class... P>
+template <typename From, typename T, typename... P>
static _FORCE_INLINE_ void vc_convert_ptrcall(void (T::*method)(P...), void *p_base, const void **p_args, void *r_ret) {
T converted(*reinterpret_cast<From *>(p_base));
call_with_ptr_args(&converted, method, p_args);
}
-template <class From, class T, class... P>
+template <typename From, typename T, typename... P>
static _FORCE_INLINE_ void vc_convert_ptrcall(void (T::*method)(P...) const, void *p_base, const void **p_args, void *r_ret) {
T converted(*reinterpret_cast<From *>(p_base));
call_with_ptr_argsc(&converted, method, p_args);
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ int vc_get_argument_count(R (T::*method)(P...)) {
return sizeof...(P);
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ int vc_get_argument_count(R (T::*method)(P...) const) {
return sizeof...(P);
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ int vc_get_argument_count(void (T::*method)(P...)) {
return sizeof...(P);
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ int vc_get_argument_count(void (T::*method)(P...) const) {
return sizeof...(P);
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ int vc_get_argument_count(R (*method)(T *, P...)) {
return sizeof...(P);
}
-template <class R, class... P>
+template <typename R, typename... P>
static _FORCE_INLINE_ int vc_get_argument_count_static(R (*method)(P...)) {
return sizeof...(P);
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ Variant::Type vc_get_argument_type(R (T::*method)(P...), int p_arg) {
return call_get_argument_type<P...>(p_arg);
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ Variant::Type vc_get_argument_type(R (T::*method)(P...) const, int p_arg) {
return call_get_argument_type<P...>(p_arg);
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ Variant::Type vc_get_argument_type(void (T::*method)(P...), int p_arg) {
return call_get_argument_type<P...>(p_arg);
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ Variant::Type vc_get_argument_type(void (T::*method)(P...) const, int p_arg) {
return call_get_argument_type<P...>(p_arg);
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ Variant::Type vc_get_argument_type(R (*method)(T *, P...), int p_arg) {
return call_get_argument_type<P...>(p_arg);
}
-template <class R, class... P>
+template <typename R, typename... P>
static _FORCE_INLINE_ Variant::Type vc_get_argument_type_static(R (*method)(P...), int p_arg) {
return call_get_argument_type<P...>(p_arg);
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ Variant::Type vc_get_return_type(R (T::*method)(P...)) {
return GetTypeInfo<R>::VARIANT_TYPE;
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ Variant::Type vc_get_return_type(R (T::*method)(P...) const) {
return GetTypeInfo<R>::VARIANT_TYPE;
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ Variant::Type vc_get_return_type(void (T::*method)(P...)) {
return Variant::NIL;
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ Variant::Type vc_get_return_type(void (T::*method)(P...) const) {
return Variant::NIL;
}
-template <class R, class... P>
+template <typename R, typename... P>
static _FORCE_INLINE_ Variant::Type vc_get_return_type(R (*method)(P...)) {
return GetTypeInfo<R>::VARIANT_TYPE;
}
-template <class... P>
+template <typename... P>
static _FORCE_INLINE_ Variant::Type vc_get_return_type(void (*method)(P...)) {
return Variant::NIL;
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ bool vc_has_return_type(R (T::*method)(P...)) {
return true;
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ bool vc_has_return_type(R (T::*method)(P...) const) {
return true;
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ bool vc_has_return_type(void (T::*method)(P...)) {
return false;
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ bool vc_has_return_type(void (T::*method)(P...) const) {
return false;
}
-template <class... P>
+template <typename... P>
static _FORCE_INLINE_ bool vc_has_return_type_static(void (*method)(P...)) {
return false;
}
-template <class R, class... P>
+template <typename R, typename... P>
static _FORCE_INLINE_ bool vc_has_return_type_static(R (*method)(P...)) {
return true;
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ bool vc_is_const(R (T::*method)(P...)) {
return false;
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ bool vc_is_const(R (T::*method)(P...) const) {
return true;
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ bool vc_is_const(void (T::*method)(P...)) {
return false;
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ bool vc_is_const(void (T::*method)(P...) const) {
return true;
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ Variant::Type vc_get_base_type(R (T::*method)(P...)) {
return GetTypeInfo<T>::VARIANT_TYPE;
}
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ Variant::Type vc_get_base_type(R (T::*method)(P...) const) {
return GetTypeInfo<T>::VARIANT_TYPE;
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ Variant::Type vc_get_base_type(void (T::*method)(P...)) {
return GetTypeInfo<T>::VARIANT_TYPE;
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ Variant::Type vc_get_base_type(void (T::*method)(P...) const) {
return GetTypeInfo<T>::VARIANT_TYPE;
}
@@ -450,12 +450,12 @@ static _FORCE_INLINE_ Variant::Type vc_get_base_type(void (T::*method)(P...) con
} \
};
-template <class R, class... P>
+template <typename R, typename... P>
static _FORCE_INLINE_ void vc_static_ptrcall(R (*method)(P...), const void **p_args, void *r_ret) {
call_with_ptr_args_static_method_ret<R, P...>(method, p_args, r_ret);
}
-template <class... P>
+template <typename... P>
static _FORCE_INLINE_ void vc_static_ptrcall(void (*method)(P...), const void **p_args, void *r_ret) {
call_with_ptr_args_static_method<P...>(method, p_args);
}
@@ -500,12 +500,12 @@ static _FORCE_INLINE_ void vc_static_ptrcall(void (*method)(P...), const void **
} \
};
-template <class R, class T, class... P>
+template <typename R, typename T, typename... P>
static _FORCE_INLINE_ void vc_ptrcall(R (*method)(T *, P...), void *p_base, const void **p_args, void *r_ret) {
call_with_ptr_args_static_retc<T, R, P...>(reinterpret_cast<T *>(p_base), method, p_args, r_ret);
}
-template <class T, class... P>
+template <typename T, typename... P>
static _FORCE_INLINE_ void vc_ptrcall(void (*method)(T *, P...), void *p_base, const void **p_args, void *r_ret) {
call_with_ptr_args_static<T, P...>(reinterpret_cast<T *>(p_base), method, p_args);
}
@@ -1164,7 +1164,7 @@ typedef OAHashMap<StringName, VariantBuiltInMethodInfo> BuiltinMethodMap;
static BuiltinMethodMap *builtin_method_info;
static List<StringName> *builtin_method_names;
-template <class T>
+template <typename T>
static void register_builtin_method(const Vector<String> &p_argnames, const Vector<Variant> &p_def_args) {
StringName name = T::get_name();
@@ -1644,6 +1644,8 @@ static void _register_variant_builtin_methods() {
bind_string_method(nocasecmp_to, sarray("to"), varray());
bind_string_method(naturalcasecmp_to, sarray("to"), varray());
bind_string_method(naturalnocasecmp_to, sarray("to"), varray());
+ bind_string_method(filecasecmp_to, sarray("to"), varray());
+ bind_string_method(filenocasecmp_to, sarray("to"), varray());
bind_string_method(length, sarray(), varray());
bind_string_method(substr, sarray("from", "len"), varray(-1));
bind_string_method(get_slice, sarray("delimiter", "slice"), varray());
diff --git a/core/variant/variant_construct.cpp b/core/variant/variant_construct.cpp
index 3427950224..b0ed49be5d 100644
--- a/core/variant/variant_construct.cpp
+++ b/core/variant/variant_construct.cpp
@@ -41,7 +41,7 @@ struct VariantConstructData {
static LocalVector<VariantConstructData> construct_data[Variant::VARIANT_MAX];
-template <class T>
+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()) + ".");
diff --git a/core/variant/variant_construct.h b/core/variant/variant_construct.h
index 36935907ae..a93723a910 100644
--- a/core/variant/variant_construct.h
+++ b/core/variant/variant_construct.h
@@ -42,7 +42,7 @@
#include "core/templates/local_vector.h"
#include "core/templates/oa_hash_map.h"
-template <class T>
+template <typename T>
struct PtrConstruct {};
#define MAKE_PTRCONSTRUCT(m_type) \
@@ -99,7 +99,7 @@ MAKE_PTRCONSTRUCT(PackedVector3Array);
MAKE_PTRCONSTRUCT(PackedColorArray);
MAKE_PTRCONSTRUCT(Variant);
-template <class T, class... P>
+template <typename T, typename... P>
class VariantConstructor {
template <size_t... Is>
static _FORCE_INLINE_ void construct_helper(T &base, const Variant **p_args, Callable::CallError &r_error, IndexSequence<Is...>) {
@@ -222,7 +222,7 @@ public:
}
};
-template <class T>
+template <typename T>
class VariantConstructorFromString {
public:
static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) {
@@ -470,7 +470,7 @@ public:
}
};
-template <class T>
+template <typename T>
class VariantConstructorToArray {
public:
static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) {
@@ -529,7 +529,7 @@ public:
}
};
-template <class T>
+template <typename T>
class VariantConstructorFromArray {
public:
static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) {
@@ -622,7 +622,7 @@ public:
}
};
-template <class T>
+template <typename T>
class VariantConstructNoArgs {
public:
static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) {
diff --git a/core/variant/variant_destruct.cpp b/core/variant/variant_destruct.cpp
index 5308eba97d..c7455d5117 100644
--- a/core/variant/variant_destruct.cpp
+++ b/core/variant/variant_destruct.cpp
@@ -34,7 +34,7 @@
static Variant::PTRDestructor destruct_pointers[Variant::VARIANT_MAX] = { nullptr };
-template <class T>
+template <typename T>
static void add_destructor() {
destruct_pointers[T::get_base_type()] = T::ptr_destruct;
}
diff --git a/core/variant/variant_destruct.h b/core/variant/variant_destruct.h
index c5f9c260c0..c496189c6d 100644
--- a/core/variant/variant_destruct.h
+++ b/core/variant/variant_destruct.h
@@ -35,7 +35,7 @@
#include "core/object/class_db.h"
-template <class T>
+template <typename T>
struct VariantDestruct {};
#define MAKE_PTRDESTRUCT(m_type) \
diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h
index 79bed9be33..dbd4a6a7ad 100644
--- a/core/variant/variant_internal.h
+++ b/core/variant/variant_internal.h
@@ -211,7 +211,7 @@ public:
_FORCE_INLINE_ static const ObjectID get_object_id(const Variant *v) { return v->_get_obj().id; }
- template <class T>
+ template <typename T>
_FORCE_INLINE_ static void init_generic(Variant *v) {
v->type = GetTypeInfo<T>::VARIANT_TYPE;
}
@@ -510,7 +510,7 @@ public:
}
};
-template <class T>
+template <typename T>
struct VariantGetInternalPtr {
};
@@ -797,7 +797,7 @@ struct VariantGetInternalPtr<PackedColorArray> {
static const PackedColorArray *get_ptr(const Variant *v) { return VariantInternal::get_color_array(v); }
};
-template <class T>
+template <typename T>
struct VariantInternalAccessor {
};
@@ -830,13 +830,13 @@ struct VariantInternalAccessor<ObjectID> {
static _FORCE_INLINE_ void set(Variant *v, ObjectID p_value) { *VariantInternal::get_int(v) = p_value; }
};
-template <class T>
+template <typename T>
struct VariantInternalAccessor<T *> {
static _FORCE_INLINE_ T *get(const Variant *v) { return const_cast<T *>(static_cast<const T *>(*VariantInternal::get_object(v))); }
static _FORCE_INLINE_ void set(Variant *v, const T *p_value) { VariantInternal::object_assign(v, p_value); }
};
-template <class T>
+template <typename T>
struct VariantInternalAccessor<const T *> {
static _FORCE_INLINE_ const T *get(const Variant *v) { return static_cast<const T *>(*VariantInternal::get_object(v)); }
static _FORCE_INLINE_ void set(Variant *v, const T *p_value) { VariantInternal::object_assign(v, p_value); }
@@ -1091,7 +1091,7 @@ struct VariantInternalAccessor<Vector<Variant>> {
}
};
-template <class T>
+template <typename T>
struct VariantInitializer {
};
@@ -1301,7 +1301,7 @@ struct VariantInitializer<Object *> {
static _FORCE_INLINE_ void init(Variant *v) { VariantInternal::init_object(v); }
};
-template <class T>
+template <typename T>
struct VariantDefaultInitializer {
};
@@ -1490,7 +1490,7 @@ struct VariantDefaultInitializer<PackedColorArray> {
static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_color_array(v) = PackedColorArray(); }
};
-template <class T>
+template <typename T>
struct VariantTypeChanger {
static _FORCE_INLINE_ void change(Variant *v) {
if (v->get_type() != GetTypeInfo<T>::VARIANT_TYPE || GetTypeInfo<T>::VARIANT_TYPE >= Variant::PACKED_BYTE_ARRAY) { //second condition removed by optimizer
@@ -1508,7 +1508,7 @@ struct VariantTypeChanger {
}
};
-template <class T>
+template <typename T>
struct VariantTypeAdjust {
_FORCE_INLINE_ static void adjust(Variant *r_ret) {
VariantTypeChanger<typename GetSimpleTypeT<T>::type_t>::change(r_ret);
@@ -1532,7 +1532,7 @@ struct VariantTypeAdjust<Object *> {
// GDExtension helpers.
-template <class T>
+template <typename T>
struct VariantTypeConstructor {
_FORCE_INLINE_ static void variant_from_type(void *r_variant, void *p_value) {
// r_variant is provided by caller as uninitialized memory
diff --git a/core/variant/variant_op.cpp b/core/variant/variant_op.cpp
index 4f9c38dc4c..60ae09c6f1 100644
--- a/core/variant/variant_op.cpp
+++ b/core/variant/variant_op.cpp
@@ -37,7 +37,7 @@ static VariantEvaluatorFunction operator_evaluator_table[Variant::OP_MAX][Varian
static Variant::ValidatedOperatorEvaluator validated_operator_evaluator_table[Variant::OP_MAX][Variant::VARIANT_MAX][Variant::VARIANT_MAX];
static Variant::PTROperatorEvaluator ptr_operator_evaluator_table[Variant::OP_MAX][Variant::VARIANT_MAX][Variant::VARIANT_MAX];
-template <class T>
+template <typename T>
void register_op(Variant::Operator p_op, Variant::Type p_type_a, Variant::Type p_type_b) {
operator_return_type_table[p_op][p_type_a][p_type_b] = T::get_return_type();
operator_evaluator_table[p_op][p_type_a][p_type_b] = T::evaluate;
diff --git a/core/variant/variant_op.h b/core/variant/variant_op.h
index 17ad126891..3142f49fc6 100644
--- a/core/variant/variant_op.h
+++ b/core/variant/variant_op.h
@@ -37,7 +37,7 @@
#include "core/debugger/engine_debugger.h"
#include "core/object/class_db.h"
-template <class R, class A, class B>
+template <typename R, typename A, typename B>
class OperatorEvaluatorAdd {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -55,7 +55,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; }
};
-template <class R, class A, class B>
+template <typename R, typename A, typename B>
class OperatorEvaluatorSub {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -73,7 +73,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; }
};
-template <class R, class A, class B>
+template <typename R, typename A, typename B>
class OperatorEvaluatorMul {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -91,7 +91,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; }
};
-template <class R, class A, class B>
+template <typename R, typename A, typename B>
class OperatorEvaluatorPow {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -109,7 +109,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; }
};
-template <class R, class A, class B>
+template <typename R, typename A, typename B>
class OperatorEvaluatorXForm {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -127,7 +127,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; }
};
-template <class R, class A, class B>
+template <typename R, typename A, typename B>
class OperatorEvaluatorXFormInv {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -145,7 +145,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; }
};
-template <class R, class A, class B>
+template <typename R, typename A, typename B>
class OperatorEvaluatorDiv {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -163,7 +163,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; }
};
-template <class R, class A, class B>
+template <typename R, typename A, typename B>
class OperatorEvaluatorDivNZ {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -258,7 +258,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<Vector4i>::VARIANT_TYPE; }
};
-template <class R, class A, class B>
+template <typename R, typename A, typename B>
class OperatorEvaluatorMod {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -276,7 +276,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; }
};
-template <class R, class A, class B>
+template <typename R, typename A, typename B>
class OperatorEvaluatorModNZ {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -371,7 +371,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<Vector4i>::VARIANT_TYPE; }
};
-template <class R, class A>
+template <typename R, typename A>
class OperatorEvaluatorNeg {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -388,7 +388,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; }
};
-template <class R, class A>
+template <typename R, typename A>
class OperatorEvaluatorPos {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -405,7 +405,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; }
};
-template <class R, class A, class B>
+template <typename R, typename A, typename B>
class OperatorEvaluatorShiftLeft {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -431,7 +431,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; }
};
-template <class R, class A, class B>
+template <typename R, typename A, typename B>
class OperatorEvaluatorShiftRight {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -457,7 +457,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; }
};
-template <class R, class A, class B>
+template <typename R, typename A, typename B>
class OperatorEvaluatorBitOr {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -475,7 +475,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; }
};
-template <class R, class A, class B>
+template <typename R, typename A, typename B>
class OperatorEvaluatorBitAnd {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -493,7 +493,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; }
};
-template <class R, class A, class B>
+template <typename R, typename A, typename B>
class OperatorEvaluatorBitXor {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -511,7 +511,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; }
};
-template <class R, class A>
+template <typename R, typename A>
class OperatorEvaluatorBitNeg {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -528,7 +528,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; }
};
-template <class A, class B>
+template <typename A, typename B>
class OperatorEvaluatorEqual {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -599,7 +599,7 @@ public:
static Variant::Type get_return_type() { return Variant::BOOL; }
};
-template <class A, class B>
+template <typename A, typename B>
class OperatorEvaluatorNotEqual {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -670,7 +670,7 @@ public:
static Variant::Type get_return_type() { return Variant::BOOL; }
};
-template <class A, class B>
+template <typename A, typename B>
class OperatorEvaluatorLess {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -688,7 +688,7 @@ public:
static Variant::Type get_return_type() { return Variant::BOOL; }
};
-template <class A, class B>
+template <typename A, typename B>
class OperatorEvaluatorLessEqual {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -706,7 +706,7 @@ public:
static Variant::Type get_return_type() { return Variant::BOOL; }
};
-template <class A, class B>
+template <typename A, typename B>
class OperatorEvaluatorGreater {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -724,7 +724,7 @@ public:
static Variant::Type get_return_type() { return Variant::BOOL; }
};
-template <class A, class B>
+template <typename A, typename B>
class OperatorEvaluatorGreaterEqual {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -742,7 +742,7 @@ public:
static Variant::Type get_return_type() { return Variant::BOOL; }
};
-template <class A, class B>
+template <typename A, typename B>
class OperatorEvaluatorAnd {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -760,7 +760,7 @@ public:
static Variant::Type get_return_type() { return Variant::BOOL; }
};
-template <class A, class B>
+template <typename A, typename B>
class OperatorEvaluatorOr {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -779,7 +779,7 @@ public:
};
#define XOR_OP(m_a, m_b) (((m_a) || (m_b)) && !((m_a) && (m_b)))
-template <class A, class B>
+template <typename A, typename B>
class OperatorEvaluatorXor {
public:
_FORCE_INLINE_ static bool xor_op(const A &a, const B &b) {
@@ -800,7 +800,7 @@ public:
static Variant::Type get_return_type() { return Variant::BOOL; }
};
-template <class A>
+template <typename A>
class OperatorEvaluatorNot {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -857,7 +857,7 @@ public:
static Variant::Type get_return_type() { return Variant::ARRAY; }
};
-template <class T>
+template <typename T>
class OperatorEvaluatorAppendArray {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -880,7 +880,7 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<Vector<T>>::VARIANT_TYPE; }
};
-template <class Left, class Right>
+template <typename Left, typename Right>
class OperatorEvaluatorStringConcat {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -902,10 +902,10 @@ public:
static Variant::Type get_return_type() { return Variant::STRING; }
};
-template <class S, class T>
+template <typename S, typename T>
class OperatorEvaluatorStringFormat;
-template <class S>
+template <typename S>
class OperatorEvaluatorStringFormat<S, void> {
public:
_FORCE_INLINE_ static String do_mod(const String &s, bool *r_valid) {
@@ -933,7 +933,7 @@ public:
static Variant::Type get_return_type() { return Variant::STRING; }
};
-template <class S>
+template <typename S>
class OperatorEvaluatorStringFormat<S, Array> {
public:
_FORCE_INLINE_ static String do_mod(const String &s, const Array &p_values, bool *r_valid) {
@@ -958,7 +958,7 @@ public:
static Variant::Type get_return_type() { return Variant::STRING; }
};
-template <class S>
+template <typename S>
class OperatorEvaluatorStringFormat<S, Object> {
public:
_FORCE_INLINE_ static String do_mod(const String &s, const Object *p_object, bool *r_valid) {
@@ -986,7 +986,7 @@ public:
static Variant::Type get_return_type() { return Variant::STRING; }
};
-template <class S, class T>
+template <typename S, typename T>
class OperatorEvaluatorStringFormat {
public:
_FORCE_INLINE_ static String do_mod(const String &s, const T &p_value, bool *r_valid) {
@@ -1317,10 +1317,10 @@ public:
////
-template <class Left, class Right>
+template <typename Left, typename Right>
class OperatorEvaluatorInStringFind;
-template <class Left>
+template <typename Left>
class OperatorEvaluatorInStringFind<Left, String> {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -1341,7 +1341,7 @@ public:
static Variant::Type get_return_type() { return Variant::BOOL; }
};
-template <class Left>
+template <typename Left>
class OperatorEvaluatorInStringFind<Left, StringName> {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -1362,7 +1362,7 @@ public:
static Variant::Type get_return_type() { return Variant::BOOL; }
};
-template <class A, class B>
+template <typename A, typename B>
class OperatorEvaluatorInArrayFind {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
@@ -1417,7 +1417,7 @@ public:
static Variant::Type get_return_type() { return Variant::BOOL; }
};
-template <class A>
+template <typename A>
class OperatorEvaluatorInDictionaryHas {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp
index e35751fd61..46c450d9f8 100644
--- a/core/variant/variant_parser.cpp
+++ b/core/variant/variant_parser.cpp
@@ -546,7 +546,7 @@ Error VariantParser::_parse_enginecfg(Stream *p_stream, Vector<String> &strings,
}
}
-template <class T>
+template <typename T>
Error VariantParser::_parse_construct(Stream *p_stream, Vector<T> &r_construct, int &line, String &r_err_str) {
Token token;
get_token(p_stream, token, line, r_err_str);
diff --git a/core/variant/variant_parser.h b/core/variant/variant_parser.h
index 8505fff739..18448100e0 100644
--- a/core/variant/variant_parser.h
+++ b/core/variant/variant_parser.h
@@ -139,7 +139,7 @@ public:
private:
static const char *tk_name[TK_MAX];
- template <class T>
+ template <typename T>
static Error _parse_construct(Stream *p_stream, Vector<T> &r_construct, int &line, String &r_err_str);
static Error _parse_enginecfg(Stream *p_stream, Vector<String> &strings, int &line, String &r_err_str);
static Error _parse_dictionary(Dictionary &object, Stream *p_stream, int &line, String &r_err_str, ResourceParser *p_res_parser = nullptr);
diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp
index 20941b944f..9d5ed22b1a 100644
--- a/core/variant/variant_setget.cpp
+++ b/core/variant/variant_setget.cpp
@@ -45,7 +45,7 @@ struct VariantSetterGetterInfo {
static LocalVector<VariantSetterGetterInfo> variant_setters_getters[Variant::VARIANT_MAX];
static LocalVector<StringName> variant_setters_getters_names[Variant::VARIANT_MAX]; //one next to another to make it cache friendly
-template <class T>
+template <typename T>
static void register_member(Variant::Type p_type, const StringName &p_member) {
VariantSetterGetterInfo sgi;
sgi.setter = T::set;
@@ -873,7 +873,7 @@ struct VariantIndexedSetterGetterInfo {
static VariantIndexedSetterGetterInfo variant_indexed_setters_getters[Variant::VARIANT_MAX];
-template <class T>
+template <typename T>
static void register_indexed_member(Variant::Type p_type) {
VariantIndexedSetterGetterInfo &sgi = variant_indexed_setters_getters[p_type];
@@ -1094,7 +1094,7 @@ struct VariantKeyedSetterGetterInfo {
static VariantKeyedSetterGetterInfo variant_keyed_setters_getters[Variant::VARIANT_MAX];
-template <class T>
+template <typename T>
static void register_keyed_member(Variant::Type p_type) {
VariantKeyedSetterGetterInfo &sgi = variant_keyed_setters_getters[p_type];
diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp
index 7136fa00c4..916ba7aa2f 100644
--- a/core/variant/variant_utility.cpp
+++ b/core/variant/variant_utility.cpp
@@ -1235,7 +1235,7 @@ bool VariantUtilityFunctions::is_same(const Variant &p_a, const Variant &p_b) {
#define VCALL p_func(VariantCaster<P>::cast(*p_args[Is])...)
#endif
-template <class R, class... P, size_t... Is>
+template <typename R, typename... P, size_t... Is>
static _FORCE_INLINE_ void call_helperpr(R (*p_func)(P...), Variant *ret, const Variant **p_args, Callable::CallError &r_error, IndexSequence<Is...>) {
r_error.error = Callable::CallError::CALL_OK;
VCALLR;
@@ -1243,51 +1243,51 @@ static _FORCE_INLINE_ void call_helperpr(R (*p_func)(P...), Variant *ret, const
(void)r_error;
}
-template <class R, class... P, size_t... Is>
+template <typename R, typename... P, size_t... Is>
static _FORCE_INLINE_ void validated_call_helperpr(R (*p_func)(P...), Variant *ret, const Variant **p_args, IndexSequence<Is...>) {
*ret = p_func(VariantCaster<P>::cast(*p_args[Is])...);
(void)p_args;
}
-template <class R, class... P, size_t... Is>
+template <typename R, typename... P, size_t... Is>
static _FORCE_INLINE_ void ptr_call_helperpr(R (*p_func)(P...), void *ret, const void **p_args, IndexSequence<Is...>) {
PtrToArg<R>::encode(p_func(PtrToArg<P>::convert(p_args[Is])...), ret);
(void)p_args;
}
-template <class R, class... P>
+template <typename R, typename... P>
static _FORCE_INLINE_ void call_helperr(R (*p_func)(P...), Variant *ret, const Variant **p_args, Callable::CallError &r_error) {
call_helperpr(p_func, ret, p_args, r_error, BuildIndexSequence<sizeof...(P)>{});
}
-template <class R, class... P>
+template <typename R, typename... P>
static _FORCE_INLINE_ void validated_call_helperr(R (*p_func)(P...), Variant *ret, const Variant **p_args) {
validated_call_helperpr(p_func, ret, p_args, BuildIndexSequence<sizeof...(P)>{});
}
-template <class R, class... P>
+template <typename R, typename... P>
static _FORCE_INLINE_ void ptr_call_helperr(R (*p_func)(P...), void *ret, const void **p_args) {
ptr_call_helperpr(p_func, ret, p_args, BuildIndexSequence<sizeof...(P)>{});
}
-template <class R, class... P>
+template <typename R, typename... P>
static _FORCE_INLINE_ int get_arg_count_helperr(R (*p_func)(P...)) {
return sizeof...(P);
}
-template <class R, class... P>
+template <typename R, typename... P>
static _FORCE_INLINE_ Variant::Type get_arg_type_helperr(R (*p_func)(P...), int p_arg) {
return call_get_argument_type<P...>(p_arg);
}
-template <class R, class... P>
+template <typename R, typename... P>
static _FORCE_INLINE_ Variant::Type get_ret_type_helperr(R (*p_func)(P...)) {
return GetTypeInfo<R>::VARIANT_TYPE;
}
// WITHOUT RET
-template <class... P, size_t... Is>
+template <typename... P, size_t... Is>
static _FORCE_INLINE_ void call_helperp(void (*p_func)(P...), const Variant **p_args, Callable::CallError &r_error, IndexSequence<Is...>) {
r_error.error = Callable::CallError::CALL_OK;
VCALL;
@@ -1295,44 +1295,44 @@ static _FORCE_INLINE_ void call_helperp(void (*p_func)(P...), const Variant **p_
(void)r_error;
}
-template <class... P, size_t... Is>
+template <typename... P, size_t... Is>
static _FORCE_INLINE_ void validated_call_helperp(void (*p_func)(P...), const Variant **p_args, IndexSequence<Is...>) {
p_func(VariantCaster<P>::cast(*p_args[Is])...);
(void)p_args;
}
-template <class... P, size_t... Is>
+template <typename... P, size_t... Is>
static _FORCE_INLINE_ void ptr_call_helperp(void (*p_func)(P...), const void **p_args, IndexSequence<Is...>) {
p_func(PtrToArg<P>::convert(p_args[Is])...);
(void)p_args;
}
-template <class... P>
+template <typename... P>
static _FORCE_INLINE_ void call_helper(void (*p_func)(P...), const Variant **p_args, Callable::CallError &r_error) {
call_helperp(p_func, p_args, r_error, BuildIndexSequence<sizeof...(P)>{});
}
-template <class... P>
+template <typename... P>
static _FORCE_INLINE_ void validated_call_helper(void (*p_func)(P...), const Variant **p_args) {
validated_call_helperp(p_func, p_args, BuildIndexSequence<sizeof...(P)>{});
}
-template <class... P>
+template <typename... P>
static _FORCE_INLINE_ void ptr_call_helper(void (*p_func)(P...), const void **p_args) {
ptr_call_helperp(p_func, p_args, BuildIndexSequence<sizeof...(P)>{});
}
-template <class... P>
+template <typename... P>
static _FORCE_INLINE_ int get_arg_count_helper(void (*p_func)(P...)) {
return sizeof...(P);
}
-template <class... P>
+template <typename... P>
static _FORCE_INLINE_ Variant::Type get_arg_type_helper(void (*p_func)(P...), int p_arg) {
return call_get_argument_type<P...>(p_arg);
}
-template <class... P>
+template <typename... P>
static _FORCE_INLINE_ Variant::Type get_ret_type_helper(void (*p_func)(P...)) {
return Variant::NIL;
}
@@ -1645,7 +1645,7 @@ struct VariantUtilityFunctionInfo {
static OAHashMap<StringName, VariantUtilityFunctionInfo> utility_function_table;
static List<StringName> utility_function_name_table;
-template <class T>
+template <typename T>
static void register_utility_function(const String &p_name, const Vector<String> &argnames) {
String name = p_name;
if (name.begins_with("_")) {
diff --git a/doc/classes/AnimationNode.xml b/doc/classes/AnimationNode.xml
index d7fb735b4d..960bbe68ad 100644
--- a/doc/classes/AnimationNode.xml
+++ b/doc/classes/AnimationNode.xml
@@ -6,6 +6,13 @@
<description>
Base resource for [AnimationTree] nodes. In general, it's not used directly, but you can create custom ones with custom blending formulas.
Inherit this when creating animation nodes mainly for use in [AnimationNodeBlendTree], otherwise [AnimationRootNode] should be used instead.
+ You can access the time information as read-only parameter which is processed and stored in the previous frame for all nodes except [AnimationNodeOutput].
+ [b]Note:[/b] If more than two inputs exist in the [AnimationNode], which time information takes precedence depends on the type of [AnimationNode].
+ [codeblock]
+ var current_length = $AnimationTree[parameters/AnimationNodeName/current_length]
+ var current_position = $AnimationTree[parameters/AnimationNodeName/current_position]
+ var current_delta = $AnimationTree[parameters/AnimationNodeName/current_delta]
+ [/codeblock]
</description>
<tutorials>
<link title="Using AnimationTree">$DOCS_URL/tutorials/animation/animation_tree.html</link>
@@ -56,7 +63,7 @@
When inheriting from [AnimationRootNode], implement this virtual method to return whether the [param parameter] is read-only. Parameters are custom local memory used for your animation nodes, given a resource can be reused in multiple trees.
</description>
</method>
- <method name="_process" qualifiers="virtual const">
+ <method name="_process" qualifiers="virtual const" deprecated="Currently this is mostly useless as there is a lack of many APIs to extend AnimationNode by GDScript. It is planned that a more flexible API using structures will be provided in the future.">
<return type="float" />
<param index="0" name="time" type="float" />
<param index="1" name="seek" type="bool" />
@@ -65,7 +72,7 @@
<description>
When inheriting from [AnimationRootNode], implement this virtual method to run some code when this animation node is processed. The [param time] parameter is a relative delta, unless [param seek] is [code]true[/code], in which case it is absolute.
Here, call the [method blend_input], [method blend_node] or [method blend_animation] functions. You can also use [method get_parameter] and [method set_parameter] to modify local memory.
- This function should return the time left for the current animation to finish (if unsure, pass the value from the main blend being called).
+ This function should return the delta.
</description>
</method>
<method name="add_input">
diff --git a/doc/classes/AnimationNodeAnimation.xml b/doc/classes/AnimationNodeAnimation.xml
index d965d31b03..5683371182 100644
--- a/doc/classes/AnimationNodeAnimation.xml
+++ b/doc/classes/AnimationNodeAnimation.xml
@@ -15,9 +15,27 @@
<member name="animation" type="StringName" setter="set_animation" getter="get_animation" default="&amp;&quot;&quot;">
Animation to use as an output. It is one of the animations provided by [member AnimationTree.anim_player].
</member>
+ <member name="loop_mode" type="int" setter="set_loop_mode" getter="get_loop_mode" enum="Animation.LoopMode">
+ If [member use_custom_timeline] is [code]true[/code], override the loop settings of the original [Animation] resource with the value.
+ </member>
<member name="play_mode" type="int" setter="set_play_mode" getter="get_play_mode" enum="AnimationNodeAnimation.PlayMode" default="0">
Determines the playback direction of the animation.
</member>
+ <member name="start_offset" type="float" setter="set_start_offset" getter="get_start_offset">
+ If [member use_custom_timeline] is [code]true[/code], offset the start position of the animation.
+ This is useful for adjusting which foot steps first in 3D walking animations.
+ </member>
+ <member name="stretch_time_scale" type="bool" setter="set_stretch_time_scale" getter="is_stretching_time_scale">
+ If [code]true[/code], scales the time so that the length specified in [member timeline_length] is one cycle.
+ This is useful for matching the periods of walking and running animations.
+ If [code]false[/code], the original animation length is respected. If you set the loop to [member loop_mode], the animation will loop in [member timeline_length].
+ </member>
+ <member name="timeline_length" type="float" setter="set_timeline_length" getter="get_timeline_length">
+ If [member use_custom_timeline] is [code]true[/code], offset the start position of the animation.
+ </member>
+ <member name="use_custom_timeline" type="bool" setter="set_use_custom_timeline" getter="is_using_custom_timeline" default="false">
+ If [code]true[/code], [AnimationNode] provides an animation based on the [Animation] resource with some parameters adjusted.
+ </member>
</members>
<constants>
<constant name="PLAY_MODE_FORWARD" value="0" enum="PlayMode">
diff --git a/doc/classes/AnimationNodeOneShot.xml b/doc/classes/AnimationNodeOneShot.xml
index ac7cf70133..6ff2d6f6db 100644
--- a/doc/classes/AnimationNodeOneShot.xml
+++ b/doc/classes/AnimationNodeOneShot.xml
@@ -66,17 +66,22 @@
<member name="autorestart_random_delay" type="float" setter="set_autorestart_random_delay" getter="get_autorestart_random_delay" default="0.0">
If [member autorestart] is [code]true[/code], a random additional delay (in seconds) between 0 and this value will be added to [member autorestart_delay].
</member>
+ <member name="break_loop_at_end" type="bool" setter="set_break_loop_at_end" getter="is_loop_broken_at_end" default="false">
+ If [code]true[/code], breaks the loop at the end of the loop cycle for transition, even if the animation is looping.
+ </member>
<member name="fadein_curve" type="Curve" setter="set_fadein_curve" getter="get_fadein_curve">
Determines how cross-fading between animations is eased. If empty, the transition will be linear.
</member>
<member name="fadein_time" type="float" setter="set_fadein_time" getter="get_fadein_time" default="0.0">
The fade-in duration. For example, setting this to [code]1.0[/code] for a 5 second length animation will produce a cross-fade that starts at 0 second and ends at 1 second during the animation.
+ [b]Note:[/b] [AnimationNodeOneShot] transitions the current state after the end of the fading. When [AnimationNodeOutput] is considered as the most upstream, so the [member fadein_time] is scaled depending on the downstream delta. For example, if this value is set to [code]1.0[/code] and a [AnimationNodeTimeScale] with a value of [code]2.0[/code] is chained downstream, the actual processing time will be 0.5 second.
</member>
<member name="fadeout_curve" type="Curve" setter="set_fadeout_curve" getter="get_fadeout_curve">
Determines how cross-fading between animations is eased. If empty, the transition will be linear.
</member>
<member name="fadeout_time" type="float" setter="set_fadeout_time" getter="get_fadeout_time" default="0.0">
The fade-out duration. For example, setting this to [code]1.0[/code] for a 5 second length animation will produce a cross-fade that starts at 4 second and ends at 5 second during the animation.
+ [b]Note:[/b] [AnimationNodeOneShot] transitions the current state after the end of the fading. When [AnimationNodeOutput] is considered as the most upstream, so the [member fadeout_time] is scaled depending on the downstream delta. For example, if this value is set to [code]1.0[/code] and an [AnimationNodeTimeScale] with a value of [code]2.0[/code] is chained downstream, the actual processing time will be 0.5 second.
</member>
<member name="mix_mode" type="int" setter="set_mix_mode" getter="get_mix_mode" enum="AnimationNodeOneShot.MixMode" default="0">
The blend type.
diff --git a/doc/classes/AnimationNodeStateMachineTransition.xml b/doc/classes/AnimationNodeStateMachineTransition.xml
index 7b7797f594..7bd0bd7e7e 100644
--- a/doc/classes/AnimationNodeStateMachineTransition.xml
+++ b/doc/classes/AnimationNodeStateMachineTransition.xml
@@ -28,6 +28,9 @@
<member name="advance_mode" type="int" setter="set_advance_mode" getter="get_advance_mode" enum="AnimationNodeStateMachineTransition.AdvanceMode" default="1">
Determines whether the transition should disabled, enabled when using [method AnimationNodeStateMachinePlayback.travel], or traversed automatically if the [member advance_condition] and [member advance_expression] checks are true (if assigned).
</member>
+ <member name="break_loop_at_end" type="bool" setter="set_break_loop_at_end" getter="is_loop_broken_at_end" default="false">
+ If [code]true[/code], breaks the loop at the end of the loop cycle for transition, even if the animation is looping.
+ </member>
<member name="priority" type="int" setter="set_priority" getter="get_priority" default="1">
Lower priority transitions are preferred when travelling through the tree via [method AnimationNodeStateMachinePlayback.travel] or [member advance_mode] is set to [constant ADVANCE_MODE_AUTO].
</member>
@@ -42,6 +45,7 @@
</member>
<member name="xfade_time" type="float" setter="set_xfade_time" getter="get_xfade_time" default="0.0">
The time to cross-fade between this state and the next.
+ [b]Note:[/b] [AnimationNodeStateMachine] transitions the current state immediately after the start of the fading. The precise remaining time can only be inferred from the main animation. When [AnimationNodeOutput] is considered as the most upstream, so the [member xfade_time] is not scaled depending on the downstream delta. See also [member AnimationNodeOneShot.fadeout_time].
</member>
</members>
<signals>
diff --git a/doc/classes/AnimationNodeTransition.xml b/doc/classes/AnimationNodeTransition.xml
index 3e1a0a28b5..775a208735 100644
--- a/doc/classes/AnimationNodeTransition.xml
+++ b/doc/classes/AnimationNodeTransition.xml
@@ -42,6 +42,13 @@
<link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link>
</tutorials>
<methods>
+ <method name="is_input_loop_broken_at_end" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="input" type="int" />
+ <description>
+ Returns whether the animation breaks the loop at the end of the loop cycle for transition.
+ </description>
+ </method>
<method name="is_input_reset" qualifiers="const">
<return type="bool" />
<param index="0" name="input" type="int" />
@@ -64,6 +71,14 @@
Enables or disables auto-advance for the given [param input] index. If enabled, state changes to the next input after playing the animation once. If enabled for the last input state, it loops to the first.
</description>
</method>
+ <method name="set_input_break_loop_at_end">
+ <return type="void" />
+ <param index="0" name="input" type="int" />
+ <param index="1" name="enable" type="bool" />
+ <description>
+ If [code]true[/code], breaks the loop at the end of the loop cycle for transition, even if the animation is looping.
+ </description>
+ </method>
<method name="set_input_reset">
<return type="void" />
<param index="0" name="input" type="int" />
@@ -85,6 +100,7 @@
</member>
<member name="xfade_time" type="float" setter="set_xfade_time" getter="get_xfade_time" default="0.0">
Cross-fading time (in seconds) between each animation connected to the inputs.
+ [b]Note:[/b] [AnimationNodeTransition] transitions the current state immediately after the start of the fading. The precise remaining time can only be inferred from the main animation. When [AnimationNodeOutput] is considered as the most upstream, so the [member xfade_time] is not scaled depending on the downstream delta. See also [member AnimationNodeOneShot.fadeout_time].
</member>
</members>
</class>
diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml
index 43c3f5c1be..697afed636 100644
--- a/doc/classes/Control.xml
+++ b/doc/classes/Control.xml
@@ -1012,6 +1012,7 @@
Distance between the node's top edge and its parent control, based on [member anchor_top].
Offsets are often controlled by one or multiple parent [Container] nodes, so you should not modify them manually if your node is a direct child of a [Container]. Offsets update automatically when you move or resize the node.
</member>
+ <member name="physics_interpolation_mode" type="int" setter="set_physics_interpolation_mode" getter="get_physics_interpolation_mode" overrides="Node" enum="Node.PhysicsInterpolationMode" default="2" />
<member name="pivot_offset" type="Vector2" setter="set_pivot_offset" getter="get_pivot_offset" default="Vector2(0, 0)">
By default, the node's pivot is its top-left corner. When you change its [member rotation] or [member scale], it will rotate or scale around this pivot. Set this property to [member size] / 2 to pivot around the Control's center.
</member>
diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml
index f446d5bb1f..48ed191db1 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -1031,6 +1031,12 @@
The number of pixels to scroll with every mouse wheel increment. Higher values make the script scroll by faster when using the mouse wheel.
[b]Note:[/b] You can hold down [kbd]Alt[/kbd] while using the mouse wheel to temporarily scroll 5 times faster.
</member>
+ <member name="text_editor/completion/add_node_path_literals" type="bool" setter="" getter="">
+ If [code]true[/code], uses [NodePath] instead of [String] when appropriate for code autocompletion or for drag and dropping object properties into the script editor.
+ </member>
+ <member name="text_editor/completion/add_string_name_literals" type="bool" setter="" getter="">
+ If [code]true[/code], uses [StringName] instead of [String] when appropriate for code autocompletion.
+ </member>
<member name="text_editor/completion/add_type_hints" type="bool" setter="" getter="">
If [code]true[/code], adds [url=$DOCS_URL/tutorials/scripting/gdscript/static_typing.html]GDScript static typing[/url] hints such as [code]-&gt; void[/code] and [code]: int[/code] when using code autocompletion or when creating onready variables by drag and dropping nodes into the script editor while pressing the [kbd]Ctrl[/kbd] key. If [code]true[/code], newly created scripts will also automatically have type hints added to their method parameters and return types.
</member>
diff --git a/doc/classes/InputEvent.xml b/doc/classes/InputEvent.xml
index 391d060fc3..96a4612466 100644
--- a/doc/classes/InputEvent.xml
+++ b/doc/classes/InputEvent.xml
@@ -117,7 +117,12 @@
<members>
<member name="device" type="int" setter="set_device" getter="get_device" default="0">
The event's device ID.
- [b]Note:[/b] This device ID will always be [code]-1[/code] for emulated mouse input from a touchscreen. This can be used to distinguish emulated mouse input from physical mouse input.
+ [b]Note:[/b] [member device] can be negative for special use cases that don't refer to devices physically present on the system. See [constant DEVICE_ID_EMULATION].
</member>
</members>
+ <constants>
+ <constant name="DEVICE_ID_EMULATION" value="-1">
+ Device ID used for emulated mouse input from a touchscreen, or for emulated touch input from a mouse. This can be used to distinguish emulated mouse input from physical mouse input, or emulated touch input from physical touch input.
+ </constant>
+ </constants>
</class>
diff --git a/doc/classes/InputEventScreenDrag.xml b/doc/classes/InputEventScreenDrag.xml
index bd6c26f561..e77040204c 100644
--- a/doc/classes/InputEventScreenDrag.xml
+++ b/doc/classes/InputEventScreenDrag.xml
@@ -17,7 +17,7 @@
Returns [code]true[/code] when using the eraser end of a stylus pen.
</member>
<member name="position" type="Vector2" setter="set_position" getter="get_position" default="Vector2(0, 0)">
- The drag position.
+ The drag position in the viewport the node is in, using the coordinate system of this viewport.
</member>
<member name="pressure" type="float" setter="set_pressure" getter="get_pressure" default="0.0">
Represents the pressure the user puts on the pen. Ranges from [code]0.0[/code] to [code]1.0[/code].
diff --git a/doc/classes/InputEventScreenTouch.xml b/doc/classes/InputEventScreenTouch.xml
index 9ed6fc191c..a42b122e3b 100644
--- a/doc/classes/InputEventScreenTouch.xml
+++ b/doc/classes/InputEventScreenTouch.xml
@@ -20,7 +20,7 @@
The touch index in the case of a multi-touch event. One index = one finger.
</member>
<member name="position" type="Vector2" setter="set_position" getter="get_position" default="Vector2(0, 0)">
- The touch position, in screen (global) coordinates.
+ The touch position in the viewport the node is in, using the coordinate system of this viewport.
</member>
<member name="pressed" type="bool" setter="set_pressed" getter="is_pressed" default="false">
If [code]true[/code], the touch's state is pressed. If [code]false[/code], the touch's state is released.
diff --git a/doc/classes/NativeMenu.xml b/doc/classes/NativeMenu.xml
index 159666ded8..cfe3f3f5c5 100644
--- a/doc/classes/NativeMenu.xml
+++ b/doc/classes/NativeMenu.xml
@@ -5,6 +5,35 @@
</brief_description>
<description>
[NativeMenu] handles low-level access to the OS native global menu bar and popup menus.
+ [b]Note:[/b] This is low-level API, consider using [MenuBar] with [member MenuBar.prefer_global_menu] set to [code]true[/code], and [PopupMenu] with [member PopupMenu.prefer_native_menu] set to [code]true[/code].
+ To create a menu, use [method create_menu], add menu items using [code]add_*_item[/code] methods. To remove a menu, use [method free_menu].
+ [codeblock]
+ var menu
+
+ func _menu_callback(item_id):
+ if item_id == "ITEM_CUT":
+ cut()
+ elif item_id == "ITEM_COPY":
+ copy()
+ elif item_id == "ITEM_PASTE":
+ paste()
+
+ func _enter_tree():
+ # Create new menu and add items:
+ menu = NativeMenu.create_menu()
+ NativeMenu.add_item(menu, "Cut", _menu_callback, Callable(), "ITEM_CUT")
+ NativeMenu.add_item(menu, "Copy", _menu_callback, Callable(), "ITEM_COPY")
+ NativeMenu.add_separator(menu)
+ NativeMenu.add_item(menu, "Paste", _menu_callback, Callable(), "ITEM_PASTE")
+
+ func _on_button_pressed():
+ # Show popup menu at mouse position:
+ NativeMenu.popup(menu, DisplayServer.mouse_get_position())
+
+ func _exit_tree():
+ # Remove menu when it's no longer needed:
+ NativeMenu.free_menu(menu)
+ [/codeblock]
</description>
<tutorials>
</tutorials>
@@ -23,7 +52,8 @@
Returns index of the inserted item, it's not guaranteed to be the same as [param index] value.
An [param accelerator] can optionally be defined, which is a keyboard shortcut that can be pressed to trigger the menu button even if it's not currently open. The [param accelerator] is generally a combination of [enum KeyModifierMask]s and [enum Key]s using bitwise OR such as [code]KEY_MASK_CTRL | KEY_A[/code] ([kbd]Ctrl + A[/kbd]).
[b]Note:[/b] The [param callback] and [param key_callback] Callables need to accept exactly one Variant parameter, the parameter passed to the Callables will be the value passed to [param tag].
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
+ [b]Note:[/b] On Windows, [param accelerator] and [param key_callback] are ignored.
</description>
</method>
<method name="add_icon_check_item">
@@ -41,7 +71,8 @@
Returns index of the inserted item, it's not guaranteed to be the same as [param index] value.
An [param accelerator] can optionally be defined, which is a keyboard shortcut that can be pressed to trigger the menu button even if it's not currently open. The [param accelerator] is generally a combination of [enum KeyModifierMask]s and [enum Key]s using bitwise OR such as [code]KEY_MASK_CTRL | KEY_A[/code] ([kbd]Ctrl + A[/kbd]).
[b]Note:[/b] The [param callback] and [param key_callback] Callables need to accept exactly one Variant parameter, the parameter passed to the Callables will be the value passed to [param tag].
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
+ [b]Note:[/b] On Windows, [param accelerator] and [param key_callback] are ignored.
</description>
</method>
<method name="add_icon_item">
@@ -59,7 +90,8 @@
Returns index of the inserted item, it's not guaranteed to be the same as [param index] value.
An [param accelerator] can optionally be defined, which is a keyboard shortcut that can be pressed to trigger the menu button even if it's not currently open. The [param accelerator] is generally a combination of [enum KeyModifierMask]s and [enum Key]s using bitwise OR such as [code]KEY_MASK_CTRL | KEY_A[/code] ([kbd]Ctrl + A[/kbd]).
[b]Note:[/b] The [param callback] and [param key_callback] Callables need to accept exactly one Variant parameter, the parameter passed to the Callables will be the value passed to [param tag].
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
+ [b]Note:[/b] On Windows, [param accelerator] and [param key_callback] are ignored.
</description>
</method>
<method name="add_icon_radio_check_item">
@@ -78,7 +110,8 @@
An [param accelerator] can optionally be defined, which is a keyboard shortcut that can be pressed to trigger the menu button even if it's not currently open. The [param accelerator] is generally a combination of [enum KeyModifierMask]s and [enum Key]s using bitwise OR such as [code]KEY_MASK_CTRL | KEY_A[/code] ([kbd]Ctrl + A[/kbd]).
[b]Note:[/b] Radio-checkable items just display a checkmark, but don't have any built-in checking behavior and must be checked/unchecked manually. See [method set_item_checked] for more info on how to control it.
[b]Note:[/b] The [param callback] and [param key_callback] Callables need to accept exactly one Variant parameter, the parameter passed to the Callables will be the value passed to [param tag].
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
+ [b]Note:[/b] On Windows, [param accelerator] and [param key_callback] are ignored.
</description>
</method>
<method name="add_item">
@@ -95,7 +128,8 @@
Returns index of the inserted item, it's not guaranteed to be the same as [param index] value.
An [param accelerator] can optionally be defined, which is a keyboard shortcut that can be pressed to trigger the menu button even if it's not currently open. The [param accelerator] is generally a combination of [enum KeyModifierMask]s and [enum Key]s using bitwise OR such as [code]KEY_MASK_CTRL | KEY_A[/code] ([kbd]Ctrl + A[/kbd]).
[b]Note:[/b] The [param callback] and [param key_callback] Callables need to accept exactly one Variant parameter, the parameter passed to the Callables will be the value passed to [param tag].
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
+ [b]Note:[/b] On Windows, [param accelerator] and [param key_callback] are ignored.
</description>
</method>
<method name="add_multistate_item">
@@ -116,7 +150,8 @@
An [param accelerator] can optionally be defined, which is a keyboard shortcut that can be pressed to trigger the menu button even if it's not currently open. The [param accelerator] is generally a combination of [enum KeyModifierMask]s and [enum Key]s using bitwise OR such as [code]KEY_MASK_CTRL | KEY_A[/code] ([kbd]Ctrl + A[/kbd]).
[b]Note:[/b] By default, there's no indication of the current item state, it should be changed manually.
[b]Note:[/b] The [param callback] and [param key_callback] Callables need to accept exactly one Variant parameter, the parameter passed to the Callables will be the value passed to [param tag].
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
+ [b]Note:[/b] On Windows, [param accelerator] and [param key_callback] are ignored.
</description>
</method>
<method name="add_radio_check_item">
@@ -134,7 +169,8 @@
An [param accelerator] can optionally be defined, which is a keyboard shortcut that can be pressed to trigger the menu button even if it's not currently open. The [param accelerator] is generally a combination of [enum KeyModifierMask]s and [enum Key]s using bitwise OR such as [code]KEY_MASK_CTRL | KEY_A[/code] ([kbd]Ctrl + A[/kbd]).
[b]Note:[/b] Radio-checkable items just display a checkmark, but don't have any built-in checking behavior and must be checked/unchecked manually. See [method set_item_checked] for more info on how to control it.
[b]Note:[/b] The [param callback] and [param key_callback] Callables need to accept exactly one Variant parameter, the parameter passed to the Callables will be the value passed to [param tag].
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
+ [b]Note:[/b] On Windows, [param accelerator] and [param key_callback] are ignored.
</description>
</method>
<method name="add_separator">
@@ -144,7 +180,7 @@
<description>
Adds a separator between items to the global menu [param rid]. Separators also occupy an index.
Returns index of the inserted item, it's not guaranteed to be the same as [param index] value.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="add_submenu_item">
@@ -157,7 +193,7 @@
<description>
Adds an item that will act as a submenu of the global menu [param rid]. The [param submenu_rid] argument is the RID of the global menu that will be shown when the item is clicked.
Returns index of the inserted item, it's not guaranteed to be the same as [param index] value.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="clear">
@@ -165,14 +201,14 @@
<param index="0" name="rid" type="RID" />
<description>
Removes all items from the global menu [param rid].
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="create_menu">
<return type="RID" />
<description>
Creates a new global menu object.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="find_item_index_with_tag" qualifiers="const">
@@ -181,7 +217,7 @@
<param index="1" name="tag" type="Variant" />
<description>
Returns the index of the item with the specified [param tag]. Index is automatically assigned to each item by the engine. Index can not be set manually.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="find_item_index_with_text" qualifiers="const">
@@ -190,7 +226,7 @@
<param index="1" name="text" type="String" />
<description>
Returns the index of the item with the specified [param text]. Index is automatically assigned to each item by the engine. Index can not be set manually.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="free_menu">
@@ -198,7 +234,7 @@
<param index="0" name="rid" type="RID" />
<description>
Frees a global menu object created by this [NativeMenu].
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="get_item_accelerator" qualifiers="const">
@@ -216,7 +252,7 @@
<param index="1" name="idx" type="int" />
<description>
Returns the callback of the item at index [param idx].
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="get_item_count" qualifiers="const">
@@ -224,7 +260,7 @@
<param index="0" name="rid" type="RID" />
<description>
Returns number of items in the global menu [param rid].
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="get_item_icon" qualifiers="const">
@@ -233,7 +269,7 @@
<param index="1" name="idx" type="int" />
<description>
Returns the icon of the item at index [param idx].
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="get_item_indentation_level" qualifiers="const">
@@ -260,7 +296,7 @@
<param index="1" name="idx" type="int" />
<description>
Returns number of states of a multistate item. See [method add_multistate_item] for details.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="get_item_state" qualifiers="const">
@@ -269,7 +305,7 @@
<param index="1" name="idx" type="int" />
<description>
Returns the state of a multistate item. See [method add_multistate_item] for details.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="get_item_submenu" qualifiers="const">
@@ -278,7 +314,7 @@
<param index="1" name="idx" type="int" />
<description>
Returns the submenu ID of the item at index [param idx]. See [method add_submenu_item] for more info on how to add a submenu.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="get_item_tag" qualifiers="const">
@@ -287,7 +323,7 @@
<param index="1" name="idx" type="int" />
<description>
Returns the metadata of the specified item, which might be of any type. You can set it with [method set_item_tag], which provides a simple way of assigning context data to items.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="get_item_text" qualifiers="const">
@@ -296,7 +332,7 @@
<param index="1" name="idx" type="int" />
<description>
Returns the text of the item at index [param idx].
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="get_item_tooltip" qualifiers="const">
@@ -337,7 +373,7 @@
<param index="0" name="rid" type="RID" />
<description>
Returns global menu size.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="get_system_menu" qualifiers="const">
@@ -361,7 +397,7 @@
<param index="0" name="feature" type="int" enum="NativeMenu.Feature" />
<description>
Returns [code]true[/code] if the specified [param feature] is supported by the current [NativeMenu], [code]false[/code] otherwise.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="has_menu" qualifiers="const">
@@ -369,7 +405,7 @@
<param index="0" name="rid" type="RID" />
<description>
Returns [code]true[/code] if [param rid] is valid global menu.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="has_system_menu" qualifiers="const">
@@ -386,7 +422,7 @@
<param index="1" name="idx" type="int" />
<description>
Returns [code]true[/code] if the item at index [param idx] is checkable in some way, i.e. if it has a checkbox or radio button.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="is_item_checked" qualifiers="const">
@@ -395,7 +431,7 @@
<param index="1" name="idx" type="int" />
<description>
Returns [code]true[/code] if the item at index [param idx] is checked.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="is_item_disabled" qualifiers="const">
@@ -405,7 +441,7 @@
<description>
Returns [code]true[/code] if the item at index [param idx] is disabled. When it is disabled it can't be selected, or its action invoked.
See [method set_item_disabled] for more info on how to disable an item.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="is_item_hidden" qualifiers="const">
@@ -425,7 +461,7 @@
<description>
Returns [code]true[/code] if the item at index [param idx] has radio button-style checkability.
[b]Note:[/b] This is purely cosmetic; you must add the logic for checking/unchecking items in radio groups.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="is_system_menu" qualifiers="const">
@@ -442,7 +478,7 @@
<param index="1" name="position" type="Vector2i" />
<description>
Shows the global menu at [param position] in the screen coordinates.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="remove_item">
@@ -452,7 +488,16 @@
<description>
Removes the item at index [param idx] from the global menu [param rid].
[b]Note:[/b] The indices of items after the removed item will be shifted by one.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
+ </description>
+ </method>
+ <method name="set_interface_direction">
+ <return type="void" />
+ <param index="0" name="rid" type="RID" />
+ <param index="1" name="is_rtl" type="bool" />
+ <description>
+ Sets the menu text layout direction from right-to-left if [param is_rtl] is [code]true[/code].
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="set_item_accelerator">
@@ -473,7 +518,7 @@
<description>
Sets the callback of the item at index [param idx]. Callback is emitted when an item is pressed.
[b]Note:[/b] The [param callback] Callable needs to accept exactly one Variant parameter, the parameter passed to the Callable will be the value passed to the [code]tag[/code] parameter when the menu item was created.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="set_item_checkable">
@@ -483,7 +528,7 @@
<param index="2" name="checkable" type="bool" />
<description>
Sets whether the item at index [param idx] has a checkbox. If [code]false[/code], sets the type of the item to plain text.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="set_item_checked">
@@ -493,7 +538,7 @@
<param index="2" name="checked" type="bool" />
<description>
Sets the checkstate status of the item at index [param idx].
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="set_item_disabled">
@@ -503,7 +548,7 @@
<param index="2" name="disabled" type="bool" />
<description>
Enables/disables the item at index [param idx]. When it is disabled, it can't be selected and its action can't be invoked.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="set_item_hidden">
@@ -534,8 +579,8 @@
<param index="2" name="icon" type="Texture2D" />
<description>
Replaces the [Texture2D] icon of the specified [param idx].
- [b]Note:[/b] This method is implemented only on macOS.
- [b]Note:[/b] This method is not supported by macOS "_dock" menu items.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
+ [b]Note:[/b] This method is not supported by macOS Dock menu items.
</description>
</method>
<method name="set_item_indentation_level">
@@ -566,7 +611,7 @@
<param index="2" name="max_states" type="int" />
<description>
Sets number of state of a multistate item. See [method add_multistate_item] for details.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="set_item_radio_checkable">
@@ -577,7 +622,7 @@
<description>
Sets the type of the item at the specified index [param idx] to radio button. If [code]false[/code], sets the type of the item to plain text.
[b]Note:[/b] This is purely cosmetic; you must add the logic for checking/unchecking items in radio groups.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="set_item_state">
@@ -587,7 +632,7 @@
<param index="2" name="state" type="int" />
<description>
Sets the state of a multistate item. See [method add_multistate_item] for details.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="set_item_submenu">
@@ -597,7 +642,7 @@
<param index="2" name="submenu_rid" type="RID" />
<description>
Sets the submenu RID of the item at index [param idx]. The submenu is a global menu that would be shown when the item is clicked.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="set_item_tag">
@@ -607,7 +652,7 @@
<param index="2" name="tag" type="Variant" />
<description>
Sets the metadata of an item, which may be of any type. You can later get it with [method get_item_tag], which provides a simple way of assigning context data to items.
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="set_item_text">
@@ -617,7 +662,7 @@
<param index="2" name="text" type="String" />
<description>
Sets the text of the item at index [param idx].
- [b]Note:[/b] This method is implemented only on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="set_item_tooltip">
@@ -645,6 +690,7 @@
<param index="1" name="callback" type="Callable" />
<description>
Registers callable to emit when the menu is about to show.
+ [b]Note:[/b] This method is implemented only on macOS.
</description>
</method>
<method name="set_popup_open_callback">
@@ -653,6 +699,7 @@
<param index="1" name="callback" type="Callable" />
<description>
Registers callable to emit when the menu is about to closed.
+ [b]Note:[/b] This method is implemented only on macOS.
</description>
</method>
</methods>
@@ -663,6 +710,15 @@
<constant name="FEATURE_POPUP_MENU" value="1" enum="Feature">
[NativeMenu] supports native popup menus.
</constant>
+ <constant name="FEATURE_OPEN_CLOSE_CALLBACK" value="2" enum="Feature">
+ [NativeMenu] supports menu open and close callbacks.
+ </constant>
+ <constant name="FEATURE_HOVER_CALLBACK" value="3" enum="Feature">
+ [NativeMenu] supports menu item hover callback.
+ </constant>
+ <constant name="FEATURE_KEY_CALLBACK" value="4" enum="Feature">
+ [NativeMenu] supports menu item accelerator/key callback.
+ </constant>
<constant name="INVALID_MENU_ID" value="0" enum="SystemMenus">
Invalid special system menu ID.
</constant>
diff --git a/doc/classes/NavigationMeshSourceGeometryData2D.xml b/doc/classes/NavigationMeshSourceGeometryData2D.xml
index 9c05248eff..609877fadc 100644
--- a/doc/classes/NavigationMeshSourceGeometryData2D.xml
+++ b/doc/classes/NavigationMeshSourceGeometryData2D.xml
@@ -16,6 +16,14 @@
Adds the outline points of a shape as obstructed area.
</description>
</method>
+ <method name="add_projected_obstruction">
+ <return type="void" />
+ <param index="0" name="vertices" type="PackedVector2Array" />
+ <param index="1" name="carve" type="bool" />
+ <description>
+ Adds a projected obstruction shape to the source geometry. If [param carve] is [code]true[/code] the carved shape will not be affected by additional offsets (e.g. agent radius) of the navigation mesh baking process.
+ </description>
+ </method>
<method name="add_traversable_outline">
<return type="void" />
<param index="0" name="shape_outline" type="PackedVector2Array" />
@@ -29,12 +37,26 @@
Clears the internal data.
</description>
</method>
+ <method name="clear_projected_obstructions">
+ <return type="void" />
+ <description>
+ Clears all projected obstructions.
+ </description>
+ </method>
<method name="get_obstruction_outlines" qualifiers="const">
<return type="PackedVector2Array[]" />
<description>
Returns all the obstructed area outlines arrays.
</description>
</method>
+ <method name="get_projected_obstructions" qualifiers="const">
+ <return type="Array" />
+ <description>
+ Returns the projected obstructions as an [Array] of dictionaries. Each [Dictionary] contains the following entries:
+ - [code]vertices[/code] - A [PackedFloat32Array] that defines the outline points of the projected shape.
+ - [code]carve[/code] - A [bool] that defines how the projected shape affects the navigation mesh baking. If [code]true[/code] the projected shape will not be affected by addition offsets, e.g. agent radius.
+ </description>
+ </method>
<method name="get_traversable_outlines" qualifiers="const">
<return type="PackedVector2Array[]" />
<description>
@@ -61,6 +83,19 @@
Sets all the obstructed area outlines arrays.
</description>
</method>
+ <method name="set_projected_obstructions">
+ <return type="void" />
+ <param index="0" name="projected_obstructions" type="Array" />
+ <description>
+ Sets the projected obstructions with an Array of Dictionaries with the following key value pairs:
+ [codeblocks]
+ [gdscript]
+ "vertices" : PackedFloat32Array
+ "carve" : bool
+ [/gdscript]
+ [/codeblocks]
+ </description>
+ </method>
<method name="set_traversable_outlines">
<return type="void" />
<param index="0" name="traversable_outlines" type="PackedVector2Array[]" />
diff --git a/doc/classes/NavigationMeshSourceGeometryData3D.xml b/doc/classes/NavigationMeshSourceGeometryData3D.xml
index a3dcd4d209..322e2e7c66 100644
--- a/doc/classes/NavigationMeshSourceGeometryData3D.xml
+++ b/doc/classes/NavigationMeshSourceGeometryData3D.xml
@@ -33,18 +33,44 @@
Adds an [Array] the size of [constant Mesh.ARRAY_MAX] and with vertices at index [constant Mesh.ARRAY_VERTEX] and indices at index [constant Mesh.ARRAY_INDEX] to the navigation mesh baking data. The array must have valid triangulated mesh data to be considered. Since [NavigationMesh] resources have no transform, all vertex positions need to be offset by the node's transform using [param xform].
</description>
</method>
+ <method name="add_projected_obstruction">
+ <return type="void" />
+ <param index="0" name="vertices" type="PackedVector3Array" />
+ <param index="1" name="elevation" type="float" />
+ <param index="2" name="height" type="float" />
+ <param index="3" name="carve" type="bool" />
+ <description>
+ Adds a projected obstruction shape to the source geometry. The [param vertices] are considered projected on a xz-axes plane, placed at the global y-axis [param elevation] and extruded by [param height]. If [param carve] is [code]true[/code] the carved shape will not be affected by additional offsets (e.g. agent radius) of the navigation mesh baking process.
+ </description>
+ </method>
<method name="clear">
<return type="void" />
<description>
Clears the internal data.
</description>
</method>
+ <method name="clear_projected_obstructions">
+ <return type="void" />
+ <description>
+ Clears all projected obstructions.
+ </description>
+ </method>
<method name="get_indices" qualifiers="const">
<return type="PackedInt32Array" />
<description>
Returns the parsed source geometry data indices array.
</description>
</method>
+ <method name="get_projected_obstructions" qualifiers="const">
+ <return type="Array" />
+ <description>
+ Returns the projected obstructions as an [Array] of dictionaries. Each [Dictionary] contains the following entries:
+ - [code]vertices[/code] - A [PackedFloat32Array] that defines the outline points of the projected shape.
+ - [code]elevation[/code] - A [float] that defines the projected shape placement on the y-axis.
+ - [code]height[/code] - A [float] that defines how much the projected shape is extruded along the y-axis.
+ - [code]carve[/code] - A [bool] that defines how the obstacle affects the navigation mesh baking. If [code]true[/code] the projected shape will not be affected by addition offsets, e.g. agent radius.
+ </description>
+ </method>
<method name="get_vertices" qualifiers="const">
<return type="PackedFloat32Array" />
<description>
@@ -72,6 +98,21 @@
[b]Warning:[/b] Inappropriate data can crash the baking process of the involved third-party libraries.
</description>
</method>
+ <method name="set_projected_obstructions">
+ <return type="void" />
+ <param index="0" name="projected_obstructions" type="Array" />
+ <description>
+ Sets the projected obstructions with an Array of Dictionaries with the following key value pairs:
+ [codeblocks]
+ [gdscript]
+ "vertices" : PackedFloat32Array
+ "elevation" : float
+ "height" : float
+ "carve" : bool
+ [/gdscript]
+ [/codeblocks]
+ </description>
+ </method>
<method name="set_vertices">
<return type="void" />
<param index="0" name="vertices" type="PackedFloat32Array" />
diff --git a/doc/classes/NavigationObstacle2D.xml b/doc/classes/NavigationObstacle2D.xml
index 19b515c7cb..12205e2ac3 100644
--- a/doc/classes/NavigationObstacle2D.xml
+++ b/doc/classes/NavigationObstacle2D.xml
@@ -1,13 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="NavigationObstacle2D" inherits="Node2D" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
- 2D Obstacle used in navigation to constrain avoidance controlled agents outside or inside an area.
+ 2D obstacle used to affect navigation mesh baking or constrain velocities of avoidance controlled agents.
</brief_description>
<description>
- 2D Obstacle used in navigation to constrain avoidance controlled agents outside or inside an area. The obstacle needs a navigation map and outline vertices defined to work correctly.
- If the obstacle's vertices are winded in clockwise order, avoidance agents will be pushed in by the obstacle, otherwise, avoidance agents will be pushed out. Outlines must not cross or overlap.
- Obstacles are [b]not[/b] a replacement for a (re)baked navigation mesh. Obstacles [b]don't[/b] change the resulting path from the pathfinding, obstacles only affect the navigation avoidance agent movement by altering the suggested velocity of the avoidance agent.
- Obstacles using vertices can warp to a new position but should not moved every frame as each move requires a rebuild of the avoidance map.
+ An obstacle needs a navigation map and outline [member vertices] defined to work correctly. The outlines can not cross or overlap.
+ Obstacles can be included in the navigation mesh baking process when [member affect_navigation_mesh] is enabled. They do not add walkable geometry, instead their role is to discard other source geometry inside the shape. This can be used to prevent navigation mesh from appearing in unwanted places. If [member carve_navigation_mesh] is enabled the baked shape will not be affected by offsets of the navigation mesh baking, e.g. the agent radius.
+ With [member avoidance_enabled] the obstacle can constrain the avoidance velocities of avoidance using agents. If the obstacle's vertices are wound in clockwise order, avoidance agents will be pushed in by the obstacle, otherwise, avoidance agents will be pushed out. Obstacles using vertices and avoidance can warp to a new position but should not be moved every single frame as each change requires a rebuild of the avoidance map.
</description>
<tutorials>
<link title="Using NavigationObstacles">$DOCS_URL/tutorials/navigation/navigation_using_navigationobstacles.html</link>
@@ -49,12 +48,20 @@
</method>
</methods>
<members>
+ <member name="affect_navigation_mesh" type="bool" setter="set_affect_navigation_mesh" getter="get_affect_navigation_mesh" default="false">
+ If enabled and parsed in a navigation mesh baking process the obstacle will discard source geometry inside its [member vertices] defined shape.
+ </member>
<member name="avoidance_enabled" type="bool" setter="set_avoidance_enabled" getter="get_avoidance_enabled" default="true">
If [code]true[/code] the obstacle affects avoidance using agents.
</member>
<member name="avoidance_layers" type="int" setter="set_avoidance_layers" getter="get_avoidance_layers" default="1">
A bitfield determining the avoidance layers for this obstacle. Agents with a matching bit on the their avoidance mask will avoid this obstacle.
</member>
+ <member name="carve_navigation_mesh" type="bool" setter="set_carve_navigation_mesh" getter="get_carve_navigation_mesh" default="false">
+ If enabled the obstacle vertices will carve into the baked navigation mesh with the shape unaffected by additional offsets (e.g. agent radius).
+ It will still be affected by further postprocessing of the baking process, like edge and polygon simplification.
+ Requires [member affect_navigation_mesh] to be enabled.
+ </member>
<member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.0">
Sets the avoidance radius for the obstacle.
</member>
diff --git a/doc/classes/NavigationObstacle3D.xml b/doc/classes/NavigationObstacle3D.xml
index 998279b3c4..bc6dbabc0e 100644
--- a/doc/classes/NavigationObstacle3D.xml
+++ b/doc/classes/NavigationObstacle3D.xml
@@ -1,13 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="NavigationObstacle3D" inherits="Node3D" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
- 3D Obstacle used in navigation to constrain avoidance controlled agents outside or inside an area.
+ 3D obstacle used to affect navigation mesh baking or constrain velocities of avoidance controlled agents.
</brief_description>
<description>
- 3D Obstacle used in navigation to constrain avoidance controlled agents outside or inside an area. The obstacle needs a navigation map and outline vertices defined to work correctly.
- If the obstacle's vertices are winded in clockwise order, avoidance agents will be pushed in by the obstacle, otherwise, avoidance agents will be pushed out. Outlines must not cross or overlap.
- Obstacles are [b]not[/b] a replacement for a (re)baked navigation mesh. Obstacles [b]don't[/b] change the resulting path from the pathfinding, obstacles only affect the navigation avoidance agent movement by altering the suggested velocity of the avoidance agent.
- Obstacles using vertices can warp to a new position but should not moved every frame as each move requires a rebuild of the avoidance map.
+ An obstacle needs a navigation map and outline [member vertices] defined to work correctly. The outlines can not cross or overlap and are restricted to a plane projection. This means the y-axis of the vertices is ignored, instead the obstacle's global y-axis position is used for placement. The projected shape is extruded by the obstacles height along the y-axis.
+ Obstacles can be included in the navigation mesh baking process when [member affect_navigation_mesh] is enabled. They do not add walkable geometry, instead their role is to discard other source geometry inside the shape. This can be used to prevent navigation mesh from appearing in unwanted places, e.g. inside "solid" geometry or on top of it. If [member carve_navigation_mesh] is enabled the baked shape will not be affected by offsets of the navigation mesh baking, e.g. the agent radius.
+ With [member avoidance_enabled] the obstacle can constrain the avoidance velocities of avoidance using agents. If the obstacle's vertices are wound in clockwise order, avoidance agents will be pushed in by the obstacle, otherwise, avoidance agents will be pushed out. Obstacles using vertices and avoidance can warp to a new position but should not be moved every single frame as each change requires a rebuild of the avoidance map.
</description>
<tutorials>
<link title="Using NavigationObstacles">$DOCS_URL/tutorials/navigation/navigation_using_navigationobstacles.html</link>
@@ -49,12 +48,20 @@
</method>
</methods>
<members>
+ <member name="affect_navigation_mesh" type="bool" setter="set_affect_navigation_mesh" getter="get_affect_navigation_mesh" default="false">
+ If enabled and parsed in a navigation mesh baking process the obstacle will discard source geometry inside its [member vertices] and [member height] defined shape.
+ </member>
<member name="avoidance_enabled" type="bool" setter="set_avoidance_enabled" getter="get_avoidance_enabled" default="true">
If [code]true[/code] the obstacle affects avoidance using agents.
</member>
<member name="avoidance_layers" type="int" setter="set_avoidance_layers" getter="get_avoidance_layers" default="1">
A bitfield determining the avoidance layers for this obstacle. Agents with a matching bit on the their avoidance mask will avoid this obstacle.
</member>
+ <member name="carve_navigation_mesh" type="bool" setter="set_carve_navigation_mesh" getter="get_carve_navigation_mesh" default="false">
+ If enabled the obstacle vertices will carve into the baked navigation mesh with the shape unaffected by additional offsets (e.g. agent radius).
+ It will still be affected by further postprocessing of the baking process, like edge and polygon simplification.
+ Requires [member affect_navigation_mesh] to be enabled.
+ </member>
<member name="height" type="float" setter="set_height" getter="get_height" default="1.0">
Sets the obstacle height used in 2D avoidance. 2D avoidance using agent's ignore obstacles that are below or above them.
</member>
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index b786b67933..c69e5edf0c 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -619,6 +619,21 @@
[method request_ready] resets it back to [code]false[/code].
</description>
</method>
+ <method name="is_physics_interpolated" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if physics interpolation is enabled for this node (see [member physics_interpolation_mode]).
+ [b]Note:[/b] Interpolation will only be active if both the flag is set [b]and[/b] physics interpolation is enabled within the [SceneTree]. This can be tested using [method is_physics_interpolated_and_enabled].
+ </description>
+ </method>
+ <method name="is_physics_interpolated_and_enabled" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if physics interpolation is enabled (see [member physics_interpolation_mode]) [b]and[/b] enabled in the [SceneTree].
+ This is a convenience version of [method is_physics_interpolated] that also checks whether physics interpolation is enabled globally.
+ See [member SceneTree.physics_interpolation] and [member ProjectSettings.physics/common/physics_interpolation].
+ </description>
+ </method>
<method name="is_physics_processing" qualifiers="const">
<return type="bool" />
<description>
@@ -793,6 +808,15 @@
[b]Note:[/b] This method only affects the current node. If the node's children also need to request ready, this method needs to be called for each one of them. When the node and its children enter the tree again, the order of [method _ready] callbacks will be the same as normal.
</description>
</method>
+ <method name="reset_physics_interpolation">
+ <return type="void" />
+ <description>
+ When physics interpolation is active, moving a node to a radically different transform (such as placement within a level) can result in a visible glitch as the object is rendered moving from the old to new position over the physics tick.
+ That glitch can be prevented by calling this method, which temporarily disables interpolation until the physics tick is complete.
+ The notification [constant NOTIFICATION_RESET_PHYSICS_INTERPOLATION] will be received by the node and all children recursively.
+ [b]Note:[/b] This function should be called [b]after[/b] moving the node, rather than before.
+ </description>
+ </method>
<method name="rpc" qualifiers="vararg">
<return type="int" enum="Error" />
<param index="0" name="method" type="StringName" />
@@ -964,6 +988,10 @@
The owner of this node. The owner must be an ancestor of this node. When packing the owner node in a [PackedScene], all the nodes it owns are also saved with it.
[b]Note:[/b] In the editor, nodes not owned by the scene root are usually not displayed in the Scene dock, and will [b]not[/b] be saved. To prevent this, remember to set the owner after calling [method add_child]. See also (see [member unique_name_in_owner])
</member>
+ <member name="physics_interpolation_mode" type="int" setter="set_physics_interpolation_mode" getter="get_physics_interpolation_mode" enum="Node.PhysicsInterpolationMode" default="0">
+ Allows enabling or disabling physics interpolation per node, offering a finer grain of control than turning physics interpolation on and off globally. See [member ProjectSettings.physics/common/physics_interpolation] and [member SceneTree.physics_interpolation] for the global setting.
+ [b]Note:[/b] When teleporting a node to a distant position you should temporarily disable interpolation with [method Node.reset_physics_interpolation].
+ </member>
<member name="process_mode" type="int" setter="set_process_mode" getter="get_process_mode" enum="Node.ProcessMode" default="0">
The node's processing behavior (see [enum ProcessMode]). To check if the node can process in its current mode, use [method can_process].
</member>
@@ -1122,6 +1150,9 @@
<constant name="NOTIFICATION_ENABLED" value="29">
Notification received when the node is enabled again after being disabled. See [constant PROCESS_MODE_DISABLED].
</constant>
+ <constant name="NOTIFICATION_RESET_PHYSICS_INTERPOLATION" value="2001">
+ Notification received when [method reset_physics_interpolation] is called on the node or its ancestors.
+ </constant>
<constant name="NOTIFICATION_EDITOR_PRE_SAVE" value="9001">
Notification received right before the scene with the node is saved in the editor. This notification is only sent in the Godot editor and will not occur in exported projects.
</constant>
@@ -1237,6 +1268,15 @@
<constant name="FLAG_PROCESS_THREAD_MESSAGES_ALL" value="3" enum="ProcessThreadMessages" is_bitfield="true">
Allows this node to process threaded messages created with [method call_deferred_thread_group] right before either [method _process] or [method _physics_process] are called.
</constant>
+ <constant name="PHYSICS_INTERPOLATION_MODE_INHERIT" value="0" enum="PhysicsInterpolationMode">
+ Inherits [member physics_interpolation_mode] from the node's parent. This is the default for any newly created node.
+ </constant>
+ <constant name="PHYSICS_INTERPOLATION_MODE_ON" value="1" enum="PhysicsInterpolationMode">
+ Enables physics interpolation for this node and for children set to [constant PHYSICS_INTERPOLATION_MODE_INHERIT]. This is the default for the root node.
+ </constant>
+ <constant name="PHYSICS_INTERPOLATION_MODE_OFF" value="2" enum="PhysicsInterpolationMode">
+ Disables physics interpolation for this node and for children set to [constant PHYSICS_INTERPOLATION_MODE_INHERIT].
+ </constant>
<constant name="DUPLICATE_SIGNALS" value="1" enum="DuplicateFlags">
Duplicate the node's signal connections.
</constant>
diff --git a/doc/classes/Parallax2D.xml b/doc/classes/Parallax2D.xml
index 6db29b7a33..4e712a7e2a 100644
--- a/doc/classes/Parallax2D.xml
+++ b/doc/classes/Parallax2D.xml
@@ -25,6 +25,7 @@
<member name="limit_end" type="Vector2" setter="set_limit_end" getter="get_limit_end" default="Vector2(1e+07, 1e+07)">
Bottom-right limits for scrolling to end. If the camera is outside of this limit, the [Parallax2D] will stop scrolling. Must be higher than [member limit_begin] and the viewport size combined to work.
</member>
+ <member name="physics_interpolation_mode" type="int" setter="set_physics_interpolation_mode" getter="get_physics_interpolation_mode" overrides="Node" enum="Node.PhysicsInterpolationMode" default="2" />
<member name="repeat_size" type="Vector2" setter="set_repeat_size" getter="get_repeat_size" default="Vector2(0, 0)">
Repeats the [Texture2D] of each of this node's children and offsets them by this value. When scrolling, the node's position loops, giving the illusion of an infinite scrolling background if the values are larger than the screen size. If an axis is set to [code]0[/code], the [Texture2D] will not be repeated.
</member>
diff --git a/doc/classes/ParallaxLayer.xml b/doc/classes/ParallaxLayer.xml
index fb92c9d85f..12482d6f66 100644
--- a/doc/classes/ParallaxLayer.xml
+++ b/doc/classes/ParallaxLayer.xml
@@ -23,5 +23,6 @@
<member name="motion_scale" type="Vector2" setter="set_motion_scale" getter="get_motion_scale" default="Vector2(1, 1)">
Multiplies the ParallaxLayer's motion. If an axis is set to [code]0[/code], it will not scroll.
</member>
+ <member name="physics_interpolation_mode" type="int" setter="set_physics_interpolation_mode" getter="get_physics_interpolation_mode" overrides="Node" enum="Node.PhysicsInterpolationMode" default="2" />
</members>
</class>
diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml
index 1e0b4d12e0..7831ebd1b9 100644
--- a/doc/classes/PopupMenu.xml
+++ b/doc/classes/PopupMenu.xml
@@ -634,6 +634,9 @@
<member name="item_count" type="int" setter="set_item_count" getter="get_item_count" default="0">
The number of items currently in the list.
</member>
+ <member name="prefer_native_menu" type="bool" setter="set_prefer_native_menu" getter="is_prefer_native_menu" default="false">
+ If [code]true[/code], [MenuBar] will use native menu when supported.
+ </member>
<member name="submenu_popup_delay" type="float" setter="set_submenu_popup_delay" getter="get_submenu_popup_delay" default="0.3">
Sets the delay time in seconds for the submenu item to popup on mouse hovering. If the popup menu is added as a child of another (acting as a submenu), it will inherit the delay time of the parent menu item.
</member>
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 407041289c..98e8939c3f 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -2266,9 +2266,15 @@
Controls the maximum number of physics steps that can be simulated each rendered frame. The default value is tuned to avoid "spiral of death" situations where expensive physics simulations trigger more expensive simulations indefinitely. However, the game will appear to slow down if the rendering FPS is less than [code]1 / max_physics_steps_per_frame[/code] of [member physics/common/physics_ticks_per_second]. This occurs even if [code]delta[/code] is consistently used in physics calculations. To avoid this, increase [member physics/common/max_physics_steps_per_frame] if you have increased [member physics/common/physics_ticks_per_second] significantly above its default value.
[b]Note:[/b] This property is only read when the project starts. To change the maximum number of simulated physics steps per frame at runtime, set [member Engine.max_physics_steps_per_frame] instead.
</member>
+ <member name="physics/common/physics_interpolation" type="bool" setter="" getter="" default="false">
+ If [code]true[/code], the renderer will interpolate the transforms of physics objects between the last two transforms, so that smooth motion is seen even when physics ticks do not coincide with rendered frames. See also [member Node.physics_interpolation_mode] and [method Node.reset_physics_interpolation].
+ [b]Note:[/b] If [code]true[/code], the physics jitter fix should be disabled by setting [member physics/common/physics_jitter_fix] to [code]0.0[/code].
+ [b]Note:[/b] This property is only read when the project starts. To toggle physics interpolation at runtime, set [member SceneTree.physics_interpolation] instead.
+ [b]Note:[/b] This feature is currently only implemented in the 2D renderer.
+ </member>
<member name="physics/common/physics_jitter_fix" type="float" setter="" getter="" default="0.5">
Controls how much physics ticks are synchronized with real time. For 0 or less, the ticks are synchronized. Such values are recommended for network games, where clock synchronization matters. Higher values cause higher deviation of in-game clock and real clock, but allows smoothing out framerate jitters. The default value of 0.5 should be good enough for most; values above 2 could cause the game to react to dropped frames with a noticeable delay and are not recommended.
- [b]Note:[/b] For best results, when using a custom physics interpolation solution, the physics jitter fix should be disabled by setting [member physics/common/physics_jitter_fix] to [code]0[/code].
+ [b]Note:[/b] When using a physics interpolation solution (such as enabling [member physics/common/physics_interpolation] or using a custom solution), the physics jitter fix should be disabled by setting [member physics/common/physics_jitter_fix] to [code]0.0[/code].
[b]Note:[/b] This property is only read when the project starts. To change the physics jitter fix at runtime, set [member Engine.physics_jitter_fix] instead.
</member>
<member name="physics/common/physics_ticks_per_second" type="int" setter="" getter="" default="60">
diff --git a/doc/classes/RayCast2D.xml b/doc/classes/RayCast2D.xml
index a101fd24de..31daaab417 100644
--- a/doc/classes/RayCast2D.xml
+++ b/doc/classes/RayCast2D.xml
@@ -69,13 +69,14 @@
<return type="Vector2" />
<description>
Returns the normal of the intersecting object's shape at the collision point, or [code]Vector2(0, 0)[/code] if the ray starts inside the shape and [member hit_from_inside] is [code]true[/code].
+ [b]Note:[/b] Check that [method is_colliding] returns [code]true[/code] before calling this method to ensure the returned normal is valid and up-to-date.
</description>
</method>
<method name="get_collision_point" qualifiers="const">
<return type="Vector2" />
<description>
- Returns the collision point at which the ray intersects the closest object. If [member hit_from_inside] is [code]true[/code] and the ray starts inside of a collision shape, this function will return the origin point of the ray.
- [b]Note:[/b] This point is in the [b]global[/b] coordinate system.
+ Returns the collision point at which the ray intersects the closest object, in the global coordinate system. If [member hit_from_inside] is [code]true[/code] and the ray starts inside of a collision shape, this function will return the origin point of the ray.
+ [b]Note:[/b] Check that [method is_colliding] returns [code]true[/code] before calling this method to ensure the returned point is valid and up-to-date.
</description>
</method>
<method name="is_colliding" qualifiers="const">
diff --git a/doc/classes/RayCast3D.xml b/doc/classes/RayCast3D.xml
index 641c5ec653..f9f94e5cfc 100644
--- a/doc/classes/RayCast3D.xml
+++ b/doc/classes/RayCast3D.xml
@@ -76,13 +76,14 @@
<return type="Vector3" />
<description>
Returns the normal of the intersecting object's shape at the collision point, or [code]Vector3(0, 0, 0)[/code] if the ray starts inside the shape and [member hit_from_inside] is [code]true[/code].
+ [b]Note:[/b] Check that [method is_colliding] returns [code]true[/code] before calling this method to ensure the returned normal is valid and up-to-date.
</description>
</method>
<method name="get_collision_point" qualifiers="const">
<return type="Vector3" />
<description>
- Returns the collision point at which the ray intersects the closest object. If [member hit_from_inside] is [code]true[/code] and the ray starts inside of a collision shape, this function will return the origin point of the ray.
- [b]Note:[/b] This point is in the [b]global[/b] coordinate system.
+ Returns the collision point at which the ray intersects the closest object, in the global coordinate system. If [member hit_from_inside] is [code]true[/code] and the ray starts inside of a collision shape, this function will return the origin point of the ray.
+ [b]Note:[/b] Check that [method is_colliding] returns [code]true[/code] before calling this method to ensure the returned point is valid and up-to-date.
</description>
</method>
<method name="is_colliding" qualifiers="const">
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index 324e6d50b6..5efda5f83f 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -424,6 +424,14 @@
[b]Note:[/b] The equivalent node is [CanvasItem].
</description>
</method>
+ <method name="canvas_item_reset_physics_interpolation">
+ <return type="void" />
+ <param index="0" name="item" type="RID" />
+ <description>
+ Prevents physics interpolation for the current physics tick.
+ This is useful when moving a canvas item to a new location, to give an instantaneous change rather than interpolation from the previous location.
+ </description>
+ </method>
<method name="canvas_item_set_canvas_group_mode">
<return type="void" />
<param index="0" name="item" type="RID" />
@@ -504,6 +512,14 @@
Sets the index for the [CanvasItem].
</description>
</method>
+ <method name="canvas_item_set_interpolated">
+ <return type="void" />
+ <param index="0" name="item" type="RID" />
+ <param index="1" name="interpolated" type="bool" />
+ <description>
+ If [param interpolated] is [code]true[/code], turns on physics interpolation for the canvas item.
+ </description>
+ </method>
<method name="canvas_item_set_light_mask">
<return type="void" />
<param index="0" name="item" type="RID" />
@@ -612,6 +628,15 @@
Sets the [CanvasItem]'s Z index, i.e. its draw order (lower indexes are drawn first).
</description>
</method>
+ <method name="canvas_item_transform_physics_interpolation">
+ <return type="void" />
+ <param index="0" name="item" type="RID" />
+ <param index="1" name="transform" type="Transform2D" />
+ <description>
+ Transforms both the current and previous stored transform for a canvas item.
+ This allows transforming a canvas item without creating a "glitch" in the interpolation, which is particularly useful for large worlds utilising a shifting origin.
+ </description>
+ </method>
<method name="canvas_light_attach_to_canvas">
<return type="void" />
<param index="0" name="light" type="RID" />
@@ -644,6 +669,14 @@
[b]Note:[/b] The equivalent node is [LightOccluder2D].
</description>
</method>
+ <method name="canvas_light_occluder_reset_physics_interpolation">
+ <return type="void" />
+ <param index="0" name="occluder" type="RID" />
+ <description>
+ Prevents physics interpolation for the current physics tick.
+ This is useful when moving an occluder to a new location, to give an instantaneous change rather than interpolation from the previous location.
+ </description>
+ </method>
<method name="canvas_light_occluder_set_as_sdf_collision">
<return type="void" />
<param index="0" name="occluder" type="RID" />
@@ -659,6 +692,14 @@
Enables or disables light occluder.
</description>
</method>
+ <method name="canvas_light_occluder_set_interpolated">
+ <return type="void" />
+ <param index="0" name="occluder" type="RID" />
+ <param index="1" name="interpolated" type="bool" />
+ <description>
+ If [param interpolated] is [code]true[/code], turns on physics interpolation for the light occluder.
+ </description>
+ </method>
<method name="canvas_light_occluder_set_light_mask">
<return type="void" />
<param index="0" name="occluder" type="RID" />
@@ -683,6 +724,23 @@
Sets a light occluder's [Transform2D].
</description>
</method>
+ <method name="canvas_light_occluder_transform_physics_interpolation">
+ <return type="void" />
+ <param index="0" name="occluder" type="RID" />
+ <param index="1" name="transform" type="Transform2D" />
+ <description>
+ Transforms both the current and previous stored transform for a light occluder.
+ This allows transforming an occluder without creating a "glitch" in the interpolation, which is particularly useful for large worlds utilising a shifting origin.
+ </description>
+ </method>
+ <method name="canvas_light_reset_physics_interpolation">
+ <return type="void" />
+ <param index="0" name="light" type="RID" />
+ <description>
+ Prevents physics interpolation for the current physics tick.
+ This is useful when moving a canvas item to a new location, to give an instantaneous change rather than interpolation from the previous location.
+ </description>
+ </method>
<method name="canvas_light_set_blend_mode">
<return type="void" />
<param index="0" name="light" type="RID" />
@@ -723,6 +781,14 @@
Sets a canvas light's height.
</description>
</method>
+ <method name="canvas_light_set_interpolated">
+ <return type="void" />
+ <param index="0" name="light" type="RID" />
+ <param index="1" name="interpolated" type="bool" />
+ <description>
+ If [param interpolated] is [code]true[/code], turns on physics interpolation for the canvas light.
+ </description>
+ </method>
<method name="canvas_light_set_item_cull_mask">
<return type="void" />
<param index="0" name="light" type="RID" />
@@ -829,6 +895,15 @@
Sets the Z range of objects that will be affected by this light. Equivalent to [member Light2D.range_z_min] and [member Light2D.range_z_max].
</description>
</method>
+ <method name="canvas_light_transform_physics_interpolation">
+ <return type="void" />
+ <param index="0" name="light" type="RID" />
+ <param index="1" name="transform" type="Transform2D" />
+ <description>
+ Transforms both the current and previous stored transform for a canvas light.
+ This allows transforming a light without creating a "glitch" in the interpolation, which is is particularly useful for large worlds utilising a shifting origin.
+ </description>
+ </method>
<method name="canvas_occluder_polygon_create">
<return type="RID" />
<description>
diff --git a/doc/classes/SceneTree.xml b/doc/classes/SceneTree.xml
index f1bb5a1cf6..bae5fe1205 100644
--- a/doc/classes/SceneTree.xml
+++ b/doc/classes/SceneTree.xml
@@ -264,6 +264,10 @@
- 2D and 3D physics will be stopped, as well as collision detection and related signals.
- Depending on each node's [member Node.process_mode], their [method Node._process], [method Node._physics_process] and [method Node._input] callback methods may not called anymore.
</member>
+ <member name="physics_interpolation" type="bool" setter="set_physics_interpolation_enabled" getter="is_physics_interpolation_enabled" default="false">
+ If [code]true[/code], the renderer will interpolate the transforms of physics objects between the last two transforms, so that smooth motion is seen even when physics ticks do not coincide with rendered frames.
+ The default value of this property is controlled by [member ProjectSettings.physics/common/physics_interpolation].
+ </member>
<member name="quit_on_go_back" type="bool" setter="set_quit_on_go_back" getter="is_quit_on_go_back" default="true">
If [code]true[/code], the application quits automatically when navigating back (e.g. using the system "Back" button on Android).
To handle 'Go Back' button when this option is disabled, use [constant DisplayServer.WINDOW_EVENT_GO_BACK_REQUEST].
diff --git a/doc/classes/ScriptExtension.xml b/doc/classes/ScriptExtension.xml
index 6c7888510e..d102676035 100644
--- a/doc/classes/ScriptExtension.xml
+++ b/doc/classes/ScriptExtension.xml
@@ -84,6 +84,7 @@
<return type="Variant" />
<param index="0" name="method" type="StringName" />
<description>
+ Return the expected argument count for the given [param method], or [code]null[/code] if it can't be determined (which will then fall back to the default behavior).
</description>
</method>
<method name="_get_script_method_list" qualifiers="virtual const">
diff --git a/doc/classes/String.xml b/doc/classes/String.xml
index 17f953f48f..7592342602 100644
--- a/doc/classes/String.xml
+++ b/doc/classes/String.xml
@@ -112,7 +112,7 @@
<description>
Performs a case-sensitive comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "Less than" and "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order.
With different string lengths, returns [code]1[/code] if this string is longer than the [param to] string, or [code]-1[/code] if shorter. Note that the length of empty strings is [i]always[/i] [code]0[/code].
- To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to], [method naturalcasecmp_to], and [method naturalnocasecmp_to].
+ To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to], [method filecasecmp_to], and [method naturalcasecmp_to].
</description>
</method>
<method name="chr" qualifiers="static">
@@ -184,6 +184,22 @@
Returns a string with [param chars] characters erased starting from [param position]. If [param chars] goes beyond the string's length given the specified [param position], fewer characters will be erased from the returned string. Returns an empty string if either [param position] or [param chars] is negative. Returns the original string unmodified if [param chars] is [code]0[/code].
</description>
</method>
+ <method name="filecasecmp_to" qualifiers="const">
+ <return type="int" />
+ <param index="0" name="to" type="String" />
+ <description>
+ Like [method naturalcasecmp_to] but prioritises strings that begin with periods ([code].[/code]) and underscores ([code]_[/code]) before any other character. Useful when sorting folders or file names.
+ To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method filenocasecmp_to], [method naturalcasecmp_to], and [method casecmp_to].
+ </description>
+ </method>
+ <method name="filenocasecmp_to" qualifiers="const">
+ <return type="int" />
+ <param index="0" name="to" type="String" />
+ <description>
+ Like [method naturalnocasecmp_to] but prioritises strings that begin with periods ([code].[/code]) and underscores ([code]_[/code]) before any other character. Useful when sorting folders or file names.
+ To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method filecasecmp_to], [method naturalnocasecmp_to], and [method nocasecmp_to].
+ </description>
+ </method>
<method name="find" qualifiers="const">
<return type="int" />
<param index="0" name="what" type="String" />
@@ -586,7 +602,7 @@
Performs a [b]case-sensitive[/b], [i]natural order[/i] comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "Less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order.
When used for sorting, natural order comparison orders sequences of numbers by the combined value of each digit as is often expected, instead of the single digit's value. A sorted sequence of numbered strings will be [code]["1", "2", "3", ...][/code], not [code]["1", "10", "2", "3", ...][/code].
With different string lengths, returns [code]1[/code] if this string is longer than the [param to] string, or [code]-1[/code] if shorter. Note that the length of empty strings is [i]always[/i] [code]0[/code].
- To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method naturalnocasecmp_to], [method nocasecmp_to], and [method casecmp_to].
+ To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method naturalnocasecmp_to], [method filecasecmp_to], and [method nocasecmp_to].
</description>
</method>
<method name="naturalnocasecmp_to" qualifiers="const">
@@ -596,7 +612,7 @@
Performs a [b]case-insensitive[/b], [i]natural order[/i] comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "Less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. Internally, lowercase characters are converted to uppercase for the comparison.
When used for sorting, natural order comparison orders sequences of numbers by the combined value of each digit as is often expected, instead of the single digit's value. A sorted sequence of numbered strings will be [code]["1", "2", "3", ...][/code], not [code]["1", "10", "2", "3", ...][/code].
With different string lengths, returns [code]1[/code] if this string is longer than the [param to] string, or [code]-1[/code] if shorter. Note that the length of empty strings is [i]always[/i] [code]0[/code].
- To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method naturalcasecmp_to], [method nocasecmp_to], and [method casecmp_to].
+ To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method naturalcasecmp_to], [method filenocasecmp_to], and [method casecmp_to].
</description>
</method>
<method name="nocasecmp_to" qualifiers="const">
@@ -605,7 +621,7 @@
<description>
Performs a [b]case-insensitive[/b] comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "Less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. Internally, lowercase characters are converted to uppercase for the comparison.
With different string lengths, returns [code]1[/code] if this string is longer than the [param to] string, or [code]-1[/code] if shorter. Note that the length of empty strings is [i]always[/i] [code]0[/code].
- To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method casecmp_to], [method naturalcasecmp_to], and [method naturalnocasecmp_to].
+ To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method casecmp_to], [method filenocasecmp_to], and [method naturalnocasecmp_to].
</description>
</method>
<method name="num" qualifiers="static">
diff --git a/doc/classes/StringName.xml b/doc/classes/StringName.xml
index 41763489f1..e837b65199 100644
--- a/doc/classes/StringName.xml
+++ b/doc/classes/StringName.xml
@@ -107,7 +107,7 @@
<description>
Performs a case-sensitive comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "Less than" and "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order.
With different string lengths, returns [code]1[/code] if this string is longer than the [param to] string, or [code]-1[/code] if shorter. Note that the length of empty strings is [i]always[/i] [code]0[/code].
- To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to], [method naturalcasecmp_to], and [method naturalnocasecmp_to].
+ To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to], [method filecasecmp_to], and [method naturalcasecmp_to].
</description>
</method>
<method name="contains" qualifiers="const">
@@ -168,6 +168,22 @@
Returns a string with [param chars] characters erased starting from [param position]. If [param chars] goes beyond the string's length given the specified [param position], fewer characters will be erased from the returned string. Returns an empty string if either [param position] or [param chars] is negative. Returns the original string unmodified if [param chars] is [code]0[/code].
</description>
</method>
+ <method name="filecasecmp_to" qualifiers="const">
+ <return type="int" />
+ <param index="0" name="to" type="String" />
+ <description>
+ Like [method naturalcasecmp_to] but prioritises strings that begin with periods ([code].[/code]) and underscores ([code]_[/code]) before any other character. Useful when sorting folders or file names.
+ To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method filenocasecmp_to], [method naturalcasecmp_to], and [method casecmp_to].
+ </description>
+ </method>
+ <method name="filenocasecmp_to" qualifiers="const">
+ <return type="int" />
+ <param index="0" name="to" type="String" />
+ <description>
+ Like [method naturalnocasecmp_to] but prioritises strings that begin with periods ([code].[/code]) and underscores ([code]_[/code]) before any other character. Useful when sorting folders or file names.
+ To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method filecasecmp_to], [method naturalnocasecmp_to], and [method nocasecmp_to].
+ </description>
+ </method>
<method name="find" qualifiers="const">
<return type="int" />
<param index="0" name="what" type="String" />
@@ -562,7 +578,7 @@
Performs a [b]case-sensitive[/b], [i]natural order[/i] comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "Less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order.
When used for sorting, natural order comparison orders sequences of numbers by the combined value of each digit as is often expected, instead of the single digit's value. A sorted sequence of numbered strings will be [code]["1", "2", "3", ...][/code], not [code]["1", "10", "2", "3", ...][/code].
With different string lengths, returns [code]1[/code] if this string is longer than the [param to] string, or [code]-1[/code] if shorter. Note that the length of empty strings is [i]always[/i] [code]0[/code].
- To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method naturalnocasecmp_to], [method nocasecmp_to], and [method casecmp_to].
+ To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method naturalnocasecmp_to], [method filecasecmp_to], and [method nocasecmp_to].
</description>
</method>
<method name="naturalnocasecmp_to" qualifiers="const">
@@ -572,7 +588,7 @@
Performs a [b]case-insensitive[/b], [i]natural order[/i] comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "Less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. Internally, lowercase characters are converted to uppercase for the comparison.
When used for sorting, natural order comparison orders sequences of numbers by the combined value of each digit as is often expected, instead of the single digit's value. A sorted sequence of numbered strings will be [code]["1", "2", "3", ...][/code], not [code]["1", "10", "2", "3", ...][/code].
With different string lengths, returns [code]1[/code] if this string is longer than the [param to] string, or [code]-1[/code] if shorter. Note that the length of empty strings is [i]always[/i] [code]0[/code].
- To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method naturalcasecmp_to], [method nocasecmp_to], and [method casecmp_to].
+ To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method naturalcasecmp_to], [method filenocasecmp_to], and [method casecmp_to].
</description>
</method>
<method name="nocasecmp_to" qualifiers="const">
@@ -581,7 +597,7 @@
<description>
Performs a [b]case-insensitive[/b] comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "Less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. Internally, lowercase characters are converted to uppercase for the comparison.
With different string lengths, returns [code]1[/code] if this string is longer than the [param to] string, or [code]-1[/code] if shorter. Note that the length of empty strings is [i]always[/i] [code]0[/code].
- To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method casecmp_to], [method naturalcasecmp_to], and [method naturalnocasecmp_to].
+ To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method casecmp_to], [method filenocasecmp_to], and [method naturalnocasecmp_to].
</description>
</method>
<method name="pad_decimals" qualifiers="const">
diff --git a/doc/classes/Time.xml b/doc/classes/Time.xml
index 79c332327f..2948d20fbb 100644
--- a/doc/classes/Time.xml
+++ b/doc/classes/Time.xml
@@ -7,7 +7,7 @@
The Time singleton allows converting time between various formats and also getting time information from the system.
This class conforms with as many of the ISO 8601 standards as possible. All dates follow the Proleptic Gregorian calendar. As such, the day before [code]1582-10-15[/code] is [code]1582-10-14[/code], not [code]1582-10-04[/code]. The year before 1 AD (aka 1 BC) is number [code]0[/code], with the year before that (2 BC) being [code]-1[/code], etc.
Conversion methods assume "the same timezone", and do not handle timezone conversions or DST automatically. Leap seconds are also not handled, they must be done manually if desired. Suffixes such as "Z" are not handled, you need to strip them away manually.
- When getting time information from the system, the time can either be in the local timezone or UTC depending on the [code]utc[/code] parameter. However, the [method get_unix_time_from_system] method always returns the time in UTC.
+ When getting time information from the system, the time can either be in the local timezone or UTC depending on the [code]utc[/code] parameter. However, the [method get_unix_time_from_system] method always uses UTC as it returns the seconds passed since the [url=https://en.wikipedia.org/wiki/Unix_time]Unix epoch[/url].
[b]Important:[/b] The [code]_from_system[/code] methods use the system clock that the user can manually set. [b]Never use[/b] this method for precise time calculation since its results are subject to automatic adjustments by the user or the operating system. [b]Always use[/b] [method get_ticks_usec] or [method get_ticks_msec] for precise time calculation instead, since they are guaranteed to be monotonic (i.e. never decrease).
</description>
<tutorials>
@@ -180,7 +180,7 @@
<method name="get_unix_time_from_system" qualifiers="const">
<return type="float" />
<description>
- Returns the current Unix timestamp in seconds based on the system time in UTC. This method is implemented by the operating system and always returns the time in UTC.
+ Returns the current Unix timestamp in seconds based on the system time in UTC. This method is implemented by the operating system and always returns the time in UTC. The Unix timestamp is the number of seconds passed since 1970-01-01 at 00:00:00, the [url=https://en.wikipedia.org/wiki/Unix_time]Unix epoch[/url].
[b]Note:[/b] Unlike other methods that use integer timestamps, this method returns the timestamp as a [float] for sub-second precision.
</description>
</method>
diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp
index 304dc9e328..9fa95a93f8 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.cpp
+++ b/drivers/gles3/rasterizer_canvas_gles3.cpp
@@ -38,6 +38,7 @@
#include "core/config/project_settings.h"
#include "core/math/geometry_2d.h"
+#include "core/math/transform_interpolator.h"
#include "servers/rendering/rendering_server_default.h"
#include "storage/config.h"
#include "storage/material_storage.h"
@@ -226,7 +227,15 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
ERR_CONTINUE(!clight);
}
- Vector2 canvas_light_pos = p_canvas_transform.xform(l->xform.get_origin()); //convert light position to canvas coordinates, as all computation is done in canvas coords to avoid precision loss
+ Transform2D final_xform;
+ if (!RSG::canvas->_interpolation_data.interpolation_enabled || !l->interpolated) {
+ final_xform = l->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(l->xform_prev, l->xform_curr, final_xform, f);
+ }
+ // Convert light position to canvas coordinates, as all computation is done in canvas coordinates to avoid precision loss.
+ Vector2 canvas_light_pos = p_canvas_transform.xform(final_xform.get_origin());
state.light_uniforms[index].position[0] = canvas_light_pos.x;
state.light_uniforms[index].position[1] = canvas_light_pos.y;
@@ -820,7 +829,7 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
Transform2D base_transform = p_canvas_transform_inverse * p_item->final_transform;
if (p_offset.x || p_offset.y) {
- base_transform *= Transform2D(0, p_offset / p_item->xform.get_scale());
+ base_transform *= Transform2D(0, p_offset / p_item->xform_curr.get_scale()); // TODO: Interpolate or explain why not needed.
}
Transform2D draw_transform; // Used by transform command
diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h
index ce0876972a..71cd152520 100644
--- a/drivers/gles3/rasterizer_scene_gles3.h
+++ b/drivers/gles3/rasterizer_scene_gles3.h
@@ -144,7 +144,7 @@ private:
RS::ViewportDebugDraw debug_draw = RS::VIEWPORT_DEBUG_DRAW_DISABLED;
uint64_t scene_pass = 0;
- template <class T>
+ template <typename T>
struct InstanceSort {
float depth;
T *instance = nullptr;
diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp
index a032535827..c955b3f708 100644
--- a/drivers/gles3/storage/texture_storage.cpp
+++ b/drivers/gles3/storage/texture_storage.cpp
@@ -2812,8 +2812,7 @@ void TextureStorage::_render_target_allocate_sdf(RenderTarget *rt) {
}
rt->process_size = size * scale / 100;
- rt->process_size.x = MAX(rt->process_size.x, 1);
- rt->process_size.y = MAX(rt->process_size.y, 1);
+ rt->process_size = rt->process_size.max(Size2i(1, 1));
glGenTextures(2, rt->sdf_texture_process);
glBindTexture(GL_TEXTURE_2D, rt->sdf_texture_process[0]);
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp
index 297407da41..1906d168fe 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp
@@ -760,8 +760,7 @@ Error RenderingDeviceDriverVulkan::_check_device_capabilities() {
vrs_capabilities.max_texel_size.y = vrs_properties.maxFragmentShadingRateAttachmentTexelSize.height;
// We'll attempt to default to a texel size of 16x16.
- vrs_capabilities.texel_size.x = CLAMP(16, vrs_capabilities.min_texel_size.x, vrs_capabilities.max_texel_size.x);
- vrs_capabilities.texel_size.y = CLAMP(16, vrs_capabilities.min_texel_size.y, vrs_capabilities.max_texel_size.y);
+ vrs_capabilities.texel_size = Vector2i(16, 16).clamp(vrs_capabilities.min_texel_size, vrs_capabilities.max_texel_size);
print_verbose(String(" Attachment fragment shading rate") + String(", min texel size: (") + itos(vrs_capabilities.min_texel_size.x) + String(", ") + itos(vrs_capabilities.min_texel_size.y) + String(")") + String(", max texel size: (") + itos(vrs_capabilities.max_texel_size.x) + String(", ") + itos(vrs_capabilities.max_texel_size.y) + String(")"));
}
diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp
index fd06bf0533..a16446aea6 100644
--- a/editor/animation_bezier_editor.cpp
+++ b/editor/animation_bezier_editor.cpp
@@ -34,6 +34,7 @@
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
+#include "editor/gui/editor_spin_slider.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/view_panner.h"
#include "scene/resources/text_line.h"
@@ -266,23 +267,11 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
RBMap<String, Vector<int>> track_indices;
int track_count = animation->get_track_count();
for (int i = 0; i < track_count; ++i) {
- if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER) {
+ if (!_is_track_displayed(i)) {
continue;
}
String base_path = animation->track_get_path(i);
- if (is_filtered) {
- if (root && root->has_node(base_path)) {
- Node *node = root->get_node(base_path);
- if (!node) {
- continue; // No node, no filter.
- }
- if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {
- continue; // Skip track due to not selected.
- }
- }
- }
-
int end = base_path.find(":");
if (end != -1) {
base_path = base_path.substr(0, end + 1);
@@ -520,28 +509,11 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
float scale = timeline->get_zoom_scale();
for (int i = 0; i < track_count; ++i) {
- if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER || hidden_tracks.has(i)) {
- continue;
- }
-
- if (hidden_tracks.has(i) || locked_tracks.has(i)) {
+ if (!_is_track_curves_displayed(i) || locked_tracks.has(i)) {
continue;
}
int key_count = animation->track_get_key_count(i);
- String path = animation->track_get_path(i);
-
- if (is_filtered) {
- if (root && root->has_node(path)) {
- Node *node = root->get_node(path);
- if (!node) {
- continue; // No node, no filter.
- }
- if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {
- continue; // Skip track due to not selected.
- }
- }
- }
for (int j = 0; j < key_count; ++j) {
float offset = animation->track_get_key_time(i, j);
@@ -648,6 +620,43 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
}
}
+// Check if a track is displayed in the bezier editor (track type = bezier and track not filtered).
+bool AnimationBezierTrackEdit::_is_track_displayed(int p_track_index) {
+ if (animation->track_get_type(p_track_index) != Animation::TrackType::TYPE_BEZIER) {
+ return false;
+ }
+
+ if (is_filtered) {
+ String path = animation->track_get_path(p_track_index);
+ if (root && root->has_node(path)) {
+ Node *node = root->get_node(path);
+ if (!node) {
+ return false; // No node, no filter.
+ }
+ if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {
+ return false; // Skip track due to not selected.
+ }
+ }
+ }
+
+ return true;
+}
+
+// Check if the curves for a track are displayed in the editor (not hidden). Includes the check on the track visibility.
+bool AnimationBezierTrackEdit::_is_track_curves_displayed(int p_track_index) {
+ //Is the track is visible in the editor?
+ if (!_is_track_displayed(p_track_index)) {
+ return false;
+ }
+
+ //And curves visible?
+ if (hidden_tracks.has(p_track_index)) {
+ return false;
+ }
+
+ return true;
+}
+
Ref<Animation> AnimationBezierTrackEdit::get_animation() const {
return animation;
}
@@ -741,6 +750,60 @@ void AnimationBezierTrackEdit::set_filtered(bool p_filtered) {
queue_redraw();
}
+void AnimationBezierTrackEdit::auto_fit_vertically() {
+ int track_count = animation->get_track_count();
+ real_t minimum_value = INFINITY;
+ real_t maximum_value = -INFINITY;
+
+ int nb_track_visible = 0;
+ for (int i = 0; i < track_count; ++i) {
+ if (!_is_track_curves_displayed(i) || locked_tracks.has(i)) {
+ continue;
+ }
+
+ int key_count = animation->track_get_key_count(i);
+
+ for (int j = 0; j < key_count; ++j) {
+ real_t value = animation->bezier_track_get_key_value(i, j);
+
+ minimum_value = MIN(value, minimum_value);
+ maximum_value = MAX(value, maximum_value);
+
+ // We also want to includes the handles...
+ Vector2 in_vec = animation->bezier_track_get_key_in_handle(i, j);
+ Vector2 out_vec = animation->bezier_track_get_key_out_handle(i, j);
+
+ minimum_value = MIN(value + in_vec.y, minimum_value);
+ maximum_value = MAX(value + in_vec.y, maximum_value);
+ minimum_value = MIN(value + out_vec.y, minimum_value);
+ maximum_value = MAX(value + out_vec.y, maximum_value);
+ }
+
+ nb_track_visible++;
+ }
+
+ if (nb_track_visible == 0) {
+ // No visible track... we will not adjust the vertical zoom
+ return;
+ }
+
+ if (Math::is_finite(minimum_value) && Math::is_finite(maximum_value)) {
+ _zoom_vertically(minimum_value, maximum_value);
+ queue_redraw();
+ }
+}
+
+void AnimationBezierTrackEdit::_zoom_vertically(real_t p_minimum_value, real_t p_maximum_value) {
+ real_t target_height = p_maximum_value - p_minimum_value;
+ if (target_height <= CMP_EPSILON) {
+ timeline_v_scroll = p_maximum_value;
+ return;
+ }
+
+ timeline_v_scroll = (p_maximum_value + p_minimum_value) / 2.0;
+ timeline_v_zoom = target_height / ((get_size().height - timeline->get_size().height) * 0.9);
+}
+
void AnimationBezierTrackEdit::_zoom_changed() {
queue_redraw();
play_position->queue_redraw();
@@ -838,7 +901,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
if (p_event->is_pressed()) {
if (ED_IS_SHORTCUT("animation_editor/duplicate_selected_keys", p_event)) {
if (!read_only) {
- duplicate_selected_keys(-1.0);
+ duplicate_selected_keys(-1.0, false);
}
accept_event();
}
@@ -856,7 +919,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
}
if (ED_IS_SHORTCUT("animation_editor/paste_keys", p_event)) {
if (!read_only) {
- paste_keys(-1.0);
+ paste_keys(-1.0, false);
}
accept_event();
}
@@ -931,10 +994,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
}
if (Math::is_finite(minimum_value) && Math::is_finite(maximum_value)) {
- timeline_v_scroll = (maximum_value + minimum_value) / 2.0;
- if (maximum_value - minimum_value > CMP_EPSILON) {
- timeline_v_zoom = (maximum_value - minimum_value) / ((get_size().height - timeline->get_size().height) * 0.9);
- }
+ _zoom_vertically(minimum_value, maximum_value);
}
queue_redraw();
@@ -1179,6 +1239,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
moving_selection_attempt = true;
moving_selection = false;
+ moving_selection_mouse_begin_x = mb->get_position().x;
moving_selection_from_key = index;
moving_selection_from_track = selected_track;
moving_selection_offset = Vector2();
@@ -1260,7 +1321,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
if (moving_selection_attempt && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
if (!read_only) {
- if (moving_selection && (abs(moving_selection_offset.x) > 0 || abs(moving_selection_offset.y) > 0)) {
+ if (moving_selection && (abs(moving_selection_offset.x) > CMP_EPSILON || abs(moving_selection_offset.y) > CMP_EPSILON)) {
//combit it
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
@@ -1274,7 +1335,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
}
// 2- remove overlapped keys
for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
- real_t newtime = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x);
+ real_t newtime = animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x;
int idx = animation->track_find_key(E->get().first, newtime, Animation::FIND_MODE_APPROX);
if (idx == -1) {
@@ -1298,7 +1359,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
// 3-move the keys (re insert them)
for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
- real_t newpos = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x);
+ real_t newpos = animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x;
Array key = animation->track_get_key_value(E->get().first, E->get().second);
real_t h = key[0];
h += moving_selection_offset.y;
@@ -1316,7 +1377,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
// 4-(undo) remove inserted keys
for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
- real_t newpos = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x);
+ real_t newpos = animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x;
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newpos);
}
@@ -1358,7 +1419,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
real_t oldpos = animation->track_get_key_time(E->get().first, E->get().second);
- real_t newpos = editor->snap_time(oldpos + moving_selection_offset.x);
+ real_t newpos = oldpos + moving_selection_offset.x;
undo_redo->add_do_method(this, "_select_at_anim", animation, E->get().first, newpos);
undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, oldpos);
@@ -1366,14 +1427,15 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
undo_redo->commit_action();
- moving_selection = false;
} else if (select_single_attempt != IntPair(-1, -1)) {
selection.clear();
selection.insert(select_single_attempt);
set_animation_and_track(animation, select_single_attempt.first, read_only);
}
+ moving_selection = false;
moving_selection_attempt = false;
+ moving_selection_mouse_begin_x = 0.0;
queue_redraw();
}
}
@@ -1385,11 +1447,22 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
select_single_attempt = IntPair(-1, -1);
}
- float y = (get_size().height / 2.0 - mm->get_position().y) * timeline_v_zoom + timeline_v_scroll;
- float x = editor->snap_time(((mm->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value());
-
if (!read_only) {
- moving_selection_offset = Vector2(x - animation->track_get_key_time(moving_selection_from_track, moving_selection_from_key), y - animation->bezier_track_get_key_value(moving_selection_from_track, moving_selection_from_key));
+ float y = (get_size().height / 2.0 - mm->get_position().y) * timeline_v_zoom + timeline_v_scroll;
+ float moving_selection_begin_time = ((moving_selection_mouse_begin_x - limit) / timeline->get_zoom_scale()) + timeline->get_value();
+ float new_time = ((mm->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value();
+ float moving_selection_pivot = animation->track_get_key_time(moving_selection_from_track, moving_selection_from_key);
+ float time_delta = new_time - moving_selection_begin_time;
+
+ float snapped_time = editor->snap_time(moving_selection_pivot + time_delta);
+ float time_offset = 0.0;
+ if (abs(moving_selection_offset.x) > CMP_EPSILON || (snapped_time > moving_selection_pivot && time_delta > CMP_EPSILON) || (snapped_time < moving_selection_pivot && time_delta < -CMP_EPSILON)) {
+ time_offset = snapped_time - moving_selection_pivot;
+ }
+ float moving_selection_begin_value = animation->bezier_track_get_key_value(moving_selection_from_track, moving_selection_from_key);
+ float y_offset = y - moving_selection_begin_value;
+
+ moving_selection_offset = Vector2(time_offset, y_offset);
}
additional_moving_handle_lefts.clear();
@@ -1503,17 +1576,18 @@ bool AnimationBezierTrackEdit::_try_select_at_ui_pos(const Point2 &p_pos, bool p
moving_selection_attempt = true;
moving_selection_from_key = pair.second;
moving_selection_from_track = pair.first;
+ moving_selection_mouse_begin_x = p_pos.x;
moving_selection_offset = Vector2();
moving_handle_track = pair.first;
moving_handle_left = animation->bezier_track_get_key_in_handle(pair.first, pair.second);
moving_handle_right = animation->bezier_track_get_key_out_handle(pair.first, pair.second);
if (selection.has(pair)) {
- select_single_attempt = pair;
moving_selection = false;
} else {
moving_selection = true;
}
+ select_single_attempt = pair;
}
set_animation_and_track(animation, pair.first, read_only);
@@ -1583,25 +1657,28 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {
real_t time = ((menu_insert_key.x - limit) / timeline->get_zoom_scale()) + timeline->get_value();
- while (animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX) != -1) {
- time += 0.001;
- }
-
switch (p_index) {
case MENU_KEY_INSERT: {
if (animation->get_track_count() > 0) {
+ if (editor->snap->is_pressed() && editor->step->get_value() != 0) {
+ time = editor->snap_time(time);
+ }
+ while (animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX) != -1) {
+ time += 0.001;
+ }
float h = (get_size().height / 2.0 - menu_insert_key.y) * timeline_v_zoom + timeline_v_scroll;
Array new_point = make_default_bezier_key(h);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Add Bezier Point"));
undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point);
+ undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time);
undo_redo->commit_action();
queue_redraw();
}
} break;
case MENU_KEY_DUPLICATE: {
- duplicate_selected_keys(time);
+ duplicate_selected_keys(time, true);
} break;
case MENU_KEY_DELETE: {
delete_selection();
@@ -1613,7 +1690,7 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {
copy_selected_keys(false);
} break;
case MENU_KEY_PASTE: {
- paste_keys(time);
+ paste_keys(time, true);
} break;
case MENU_KEY_SET_HANDLE_FREE: {
_change_selected_keys_handle_mode(Animation::HANDLE_MODE_FREE);
@@ -1636,7 +1713,7 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {
}
}
-void AnimationBezierTrackEdit::duplicate_selected_keys(real_t p_ofs) {
+void AnimationBezierTrackEdit::duplicate_selected_keys(real_t p_ofs, bool p_ofs_valid) {
if (selection.size() == 0) {
return;
}
@@ -1656,7 +1733,14 @@ void AnimationBezierTrackEdit::duplicate_selected_keys(real_t p_ofs) {
for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
real_t t = animation->track_get_key_time(E->get().first, E->get().second);
- real_t insert_pos = p_ofs >= 0 ? p_ofs : timeline->get_play_position();
+ real_t insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
+
+ if (p_ofs_valid) {
+ if (editor->snap->is_pressed() && editor->step->get_value() != 0) {
+ insert_pos = editor->snap_time(insert_pos);
+ }
+ }
+
real_t dst_time = t + (insert_pos - top_time);
int existing_idx = animation->track_find_key(E->get().first, dst_time, Animation::FIND_MODE_APPROX);
@@ -1734,7 +1818,7 @@ void AnimationBezierTrackEdit::copy_selected_keys(bool p_cut) {
}
}
-void AnimationBezierTrackEdit::paste_keys(real_t p_ofs) {
+void AnimationBezierTrackEdit::paste_keys(real_t p_ofs, bool p_ofs_valid) {
if (editor->is_key_clipboard_active() && animation.is_valid() && (selected_track >= 0 && selected_track < animation->get_track_count())) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Animation Paste Keys"));
@@ -1765,7 +1849,12 @@ void AnimationBezierTrackEdit::paste_keys(real_t p_ofs) {
for (int i = 0; i < editor->key_clipboard.keys.size(); i++) {
const AnimationTrackEditor::KeyClipboard::Key key = editor->key_clipboard.keys[i];
- float insert_pos = p_ofs >= 0 ? p_ofs : timeline->get_play_position();
+ float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
+ if (p_ofs_valid) {
+ if (editor->snap->is_pressed() && editor->step->get_value() != 0) {
+ insert_pos = editor->snap_time(insert_pos);
+ }
+ }
float dst_time = key.time + insert_pos;
int existing_idx = animation->track_find_key(selected_track, dst_time, Animation::FIND_MODE_APPROX);
diff --git a/editor/animation_bezier_editor.h b/editor/animation_bezier_editor.h
index ec2b52221e..ab667421f3 100644
--- a/editor/animation_bezier_editor.h
+++ b/editor/animation_bezier_editor.h
@@ -101,12 +101,15 @@ class AnimationBezierTrackEdit : public Control {
void _menu_selected(int p_index);
void _play_position_draw();
+ bool _is_track_displayed(int p_track_index);
+ bool _is_track_curves_displayed(int p_track_index);
Vector2 insert_at_pos;
typedef Pair<int, int> IntPair;
bool moving_selection_attempt = false;
+ float moving_selection_mouse_begin_x = 0.0;
IntPair select_single_attempt;
bool moving_selection = false;
int moving_selection_from_key = 0;
@@ -188,6 +191,7 @@ class AnimationBezierTrackEdit : public Control {
void _draw_track(int p_track, const Color &p_color);
float _bezier_h_to_pixel(float p_h);
+ void _zoom_vertically(real_t p_minimum_value, real_t p_maximum_value);
protected:
static void _bind_methods();
@@ -208,13 +212,14 @@ public:
void set_editor(AnimationTrackEditor *p_editor);
void set_root(Node *p_root);
void set_filtered(bool p_filtered);
+ void auto_fit_vertically();
void set_play_position(real_t p_pos);
void update_play_position();
- void duplicate_selected_keys(real_t p_ofs);
+ void duplicate_selected_keys(real_t p_ofs, bool p_ofs_valid);
void copy_selected_keys(bool p_cut);
- void paste_keys(real_t p_ofs);
+ void paste_keys(real_t p_ofs, bool p_ofs_valid);
void delete_selection();
void _bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode);
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 6f1439a91f..2746e9acb4 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -1305,7 +1305,11 @@ void AnimationTimelineEdit::_zoom_changed(double) {
}
float AnimationTimelineEdit::get_zoom_scale() const {
- float zv = zoom->get_max() - zoom->get_value();
+ return _get_zoom_scale(zoom->get_value());
+}
+
+float AnimationTimelineEdit::_get_zoom_scale(double p_zoom_value) const {
+ float zv = zoom->get_max() - p_zoom_value;
if (zv < 1) {
zv = 1.0 - zv;
return Math::pow(1.0f + zv, 8.0f) * 100;
@@ -1633,6 +1637,68 @@ void AnimationTimelineEdit::set_zoom(Range *p_zoom) {
zoom->connect("value_changed", callable_mp(this, &AnimationTimelineEdit::_zoom_changed));
}
+void AnimationTimelineEdit::auto_fit() {
+ if (!animation.is_valid()) {
+ return;
+ }
+
+ float anim_end = animation->get_length();
+ float anim_start = 0;
+
+ // Search for keyframe outside animation boundaries to include keyframes before animation start and after animation length.
+ int track_count = animation->get_track_count();
+ for (int track = 0; track < track_count; ++track) {
+ for (int i = 0; i < animation->track_get_key_count(track); i++) {
+ float key_time = animation->track_get_key_time(track, i);
+ if (key_time > anim_end) {
+ anim_end = key_time;
+ }
+ if (key_time < anim_start) {
+ anim_start = key_time;
+ }
+ }
+ }
+
+ float anim_length = anim_end - anim_start;
+ int timeline_width_pixels = get_size().width - get_buttons_width() - get_name_limit();
+
+ // I want a little buffer at the end... (5% looks nice and we should keep some space for the bezier handles)
+ timeline_width_pixels *= 0.95;
+
+ // The technique is to reuse the _get_zoom_scale function directly to be sure that the auto_fit is always calculated
+ // the same way as the zoom slider. It's a little bit more calculation then doing the inverse of get_zoom_scale but
+ // it's really easier to understand and should always be accurate.
+ float new_zoom = zoom->get_max();
+ while (true) {
+ double test_zoom_scale = _get_zoom_scale(new_zoom);
+
+ if (anim_length * test_zoom_scale <= timeline_width_pixels) {
+ // It fits...
+ break;
+ }
+
+ new_zoom -= zoom->get_step();
+
+ if (new_zoom <= zoom->get_min()) {
+ new_zoom = zoom->get_min();
+ break;
+ }
+ }
+
+ // Horizontal scroll to get_min which should include keyframes that are before the animation start.
+ hscroll->set_value(hscroll->get_min());
+ // Set the zoom value... the signal value_changed will be emitted and the timeline will be refreshed correctly!
+ zoom->set_value(new_zoom);
+ // The new zoom value must be applied correctly so the scrollbar are updated before we move the scrollbar to
+ // the beginning of the animation, hence the call deferred.
+ callable_mp(this, &AnimationTimelineEdit::_scroll_to_start).call_deferred();
+}
+
+void AnimationTimelineEdit::_scroll_to_start() {
+ // Horizontal scroll to get_min which should include keyframes that are before the animation start.
+ hscroll->set_value(hscroll->get_min());
+}
+
void AnimationTimelineEdit::set_track_edit(AnimationTrackEdit *p_track_edit) {
track_edit = p_track_edit;
}
@@ -2001,13 +2067,13 @@ void AnimationTrackEdit::_notification(int p_what) {
for (int i = 0; i < animation->track_get_key_count(track); i++) {
float offset = animation->track_get_key_time(track, i) - timeline->get_value();
if (editor->is_key_selected(track, i) && editor->is_moving_selection()) {
- offset = editor->snap_time(offset + editor->get_moving_selection_offset(), true);
+ offset = offset + editor->get_moving_selection_offset();
}
offset = offset * scale + limit;
if (i < animation->track_get_key_count(track) - 1) {
float offset_n = animation->track_get_key_time(track, i + 1) - timeline->get_value();
if (editor->is_key_selected(track, i + 1) && editor->is_moving_selection()) {
- offset_n = editor->snap_time(offset_n + editor->get_moving_selection_offset());
+ offset_n = offset_n + editor->get_moving_selection_offset();
}
offset_n = offset_n * scale + limit;
@@ -2688,7 +2754,7 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
if (p_event->is_pressed()) {
if (ED_IS_SHORTCUT("animation_editor/duplicate_selected_keys", p_event)) {
if (!read_only) {
- emit_signal(SNAME("duplicate_request"), -1.0);
+ emit_signal(SNAME("duplicate_request"), -1.0, false);
}
accept_event();
}
@@ -2707,7 +2773,7 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
if (ED_IS_SHORTCUT("animation_editor/paste_keys", p_event)) {
if (!read_only) {
- emit_signal(SNAME("paste_request"), -1.0);
+ emit_signal(SNAME("paste_request"), -1.0, false);
}
accept_event();
}
@@ -2908,8 +2974,10 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
if (mb.is_valid() && moving_selection_attempt) {
if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
moving_selection_attempt = false;
- if (moving_selection) {
- emit_signal(SNAME("move_selection_commit"));
+ if (moving_selection && moving_selection_effective) {
+ if (abs(editor->get_moving_selection_offset()) > CMP_EPSILON) {
+ emit_signal(SNAME("move_selection_commit"));
+ }
} else if (select_single_attempt != -1) {
emit_signal(SNAME("select_key"), select_single_attempt, true);
}
@@ -2982,8 +3050,18 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
emit_signal(SNAME("move_selection_begin"));
}
- float new_ofs = (mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale();
- emit_signal(SNAME("move_selection"), new_ofs - moving_selection_from_ofs);
+ float moving_begin_time = ((moving_selection_mouse_begin_x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value();
+ float new_time = ((mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value();
+ float delta = new_time - moving_begin_time;
+ float snapped_time = editor->snap_time(moving_selection_pivot + delta);
+
+ float offset = 0.0;
+ if (abs(editor->get_moving_selection_offset()) > CMP_EPSILON || (snapped_time > moving_selection_pivot && delta > CMP_EPSILON) || (snapped_time < moving_selection_pivot && delta < -CMP_EPSILON)) {
+ offset = snapped_time - moving_selection_pivot;
+ moving_selection_effective = true;
+ }
+
+ emit_signal(SNAME("move_selection"), offset);
}
}
@@ -3026,12 +3104,16 @@ bool AnimationTrackEdit::_try_select_at_ui_pos(const Point2 &p_pos, bool p_aggre
if (editor->is_key_selected(track, key_idx)) {
if (p_deselectable) {
emit_signal(SNAME("deselect_key"), key_idx);
+ moving_selection_pivot = 0.0f;
+ moving_selection_mouse_begin_x = 0.0f;
}
} else {
emit_signal(SNAME("select_key"), key_idx, false);
moving_selection_attempt = true;
+ moving_selection_effective = false;
select_single_attempt = -1;
- moving_selection_from_ofs = (p_pos.x - limit) / timeline->get_zoom_scale();
+ moving_selection_pivot = animation->track_get_key_time(track, key_idx);
+ moving_selection_mouse_begin_x = p_pos.x;
}
} else {
if (!editor->is_key_selected(track, key_idx)) {
@@ -3042,12 +3124,15 @@ bool AnimationTrackEdit::_try_select_at_ui_pos(const Point2 &p_pos, bool p_aggre
}
moving_selection_attempt = true;
- moving_selection_from_ofs = (p_pos.x - limit) / timeline->get_zoom_scale();
+ moving_selection_effective = false;
+ moving_selection_pivot = animation->track_get_key_time(track, key_idx);
+ moving_selection_mouse_begin_x = p_pos.x;
}
if (read_only) {
moving_selection_attempt = false;
- moving_selection_from_ofs = 0.0f;
+ moving_selection_pivot = 0.0f;
+ moving_selection_mouse_begin_x = 0.0f;
}
return true;
}
@@ -3182,7 +3267,7 @@ void AnimationTrackEdit::_menu_selected(int p_index) {
emit_signal(SNAME("insert_key"), insert_at_pos);
} break;
case MENU_KEY_DUPLICATE: {
- emit_signal(SNAME("duplicate_request"), insert_at_pos);
+ emit_signal(SNAME("duplicate_request"), insert_at_pos, true);
} break;
case MENU_KEY_CUT: {
emit_signal(SNAME("cut_request"));
@@ -3191,7 +3276,7 @@ void AnimationTrackEdit::_menu_selected(int p_index) {
emit_signal(SNAME("copy_request"));
} break;
case MENU_KEY_PASTE: {
- emit_signal(SNAME("paste_request"), insert_at_pos);
+ emit_signal(SNAME("paste_request"), insert_at_pos, true);
} break;
case MENU_KEY_ADD_RESET: {
emit_signal(SNAME("create_reset_request"));
@@ -3265,11 +3350,11 @@ void AnimationTrackEdit::_bind_methods() {
ADD_SIGNAL(MethodInfo("move_selection_commit"));
ADD_SIGNAL(MethodInfo("move_selection_cancel"));
- ADD_SIGNAL(MethodInfo("duplicate_request", PropertyInfo(Variant::FLOAT, "offset")));
+ ADD_SIGNAL(MethodInfo("duplicate_request", PropertyInfo(Variant::FLOAT, "offset"), PropertyInfo(Variant::BOOL, "is_offset_valid")));
ADD_SIGNAL(MethodInfo("create_reset_request"));
ADD_SIGNAL(MethodInfo("copy_request"));
ADD_SIGNAL(MethodInfo("cut_request"));
- ADD_SIGNAL(MethodInfo("paste_request", PropertyInfo(Variant::FLOAT, "offset")));
+ ADD_SIGNAL(MethodInfo("paste_request", PropertyInfo(Variant::FLOAT, "offset"), PropertyInfo(Variant::BOOL, "is_offset_valid")));
ADD_SIGNAL(MethodInfo("delete_request"));
}
@@ -3446,6 +3531,8 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re
step->set_read_only(false);
snap->set_disabled(false);
snap_mode->set_disabled(false);
+ auto_fit->set_disabled(false);
+ auto_fit_bezier->set_disabled(false);
imported_anim_warning->hide();
for (int i = 0; i < animation->get_track_count(); i++) {
@@ -3466,6 +3553,8 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re
snap->set_disabled(true);
snap_mode->set_disabled(true);
bezier_edit_icon->set_disabled(true);
+ auto_fit->set_disabled(true);
+ auto_fit_bezier->set_disabled(true);
}
}
@@ -4763,6 +4852,8 @@ void AnimationTrackEditor::_notification(int p_what) {
inactive_player_warning->set_icon(get_editor_theme_icon(SNAME("NodeWarning")));
main_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree")));
edit->get_popup()->set_item_icon(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), get_editor_theme_icon(SNAME("Reload")));
+ auto_fit->set_icon(get_editor_theme_icon(SNAME("AnimationAutoFit")));
+ auto_fit_bezier->set_icon(get_editor_theme_icon(SNAME("AnimationAutoFitBezier")));
} break;
case NOTIFICATION_READY: {
@@ -5086,6 +5177,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
undo_redo->create_action(TTR("Add Position Key"));
undo_redo->add_do_method(animation.ptr(), "position_track_insert_key", p_track, p_ofs, pos);
+ undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
undo_redo->commit_action();
@@ -5106,6 +5198,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
undo_redo->create_action(TTR("Add Rotation Key"));
undo_redo->add_do_method(animation.ptr(), "rotation_track_insert_key", p_track, p_ofs, rot);
+ undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
undo_redo->commit_action();
@@ -5124,6 +5217,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
undo_redo->create_action(TTR("Add Scale Key"));
undo_redo->add_do_method(animation.ptr(), "scale_track_insert_key", p_track, p_ofs, base->get_scale());
+ undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
undo_redo->commit_action();
@@ -5169,6 +5263,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
undo_redo->create_action(TTR("Add Track Key"));
undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, arr);
+ undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
undo_redo->commit_action();
@@ -5181,6 +5276,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
undo_redo->create_action(TTR("Add Track Key"));
undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, ak);
+ undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
undo_redo->commit_action();
} break;
@@ -5189,6 +5285,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
undo_redo->create_action(TTR("Add Track Key"));
undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, anim);
+ undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
undo_redo->commit_action();
} break;
@@ -5229,6 +5326,7 @@ void AnimationTrackEditor::_add_method_key(const String &p_method) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Add Method Track Key"));
undo_redo->add_do_method(animation.ptr(), "track_insert_key", insert_key_from_track_call_track, insert_key_from_track_call_ofs, d);
+ undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", insert_key_from_track_call_track, insert_key_from_track_call_ofs);
undo_redo->commit_action();
@@ -5427,7 +5525,7 @@ void AnimationTrackEditor::_move_selection_commit() {
}
// 2 - Remove overlapped keys.
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
- float newtime = snap_time(E->get().pos + motion);
+ float newtime = E->get().pos + motion;
int idx = animation->track_find_key(E->key().track, newtime, Animation::FIND_MODE_APPROX);
if (idx == -1) {
continue;
@@ -5452,13 +5550,13 @@ void AnimationTrackEditor::_move_selection_commit() {
// 3 - Move the keys (Reinsert them).
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
- float newpos = snap_time(E->get().pos + motion);
+ float newpos = E->get().pos + motion;
undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->key().track, newpos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key));
}
// 4 - (Undo) Remove inserted keys.
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
- float newpos = snap_time(E->get().pos + motion);
+ float newpos = E->get().pos + motion;
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newpos);
}
@@ -5478,7 +5576,7 @@ void AnimationTrackEditor::_move_selection_commit() {
// 7 - Reselect.
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
float oldpos = E->get().pos;
- float newpos = snap_time(oldpos + motion);
+ float newpos = oldpos + motion;
undo_redo->add_do_method(this, "_select_at_anim", animation, E->key().track, newpos);
undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, oldpos);
@@ -5617,6 +5715,8 @@ void AnimationTrackEditor::_cancel_bezier_edit() {
bezier_edit->hide();
scroll->show();
bezier_edit_icon->set_pressed(false);
+ auto_fit->show();
+ auto_fit_bezier->hide();
}
void AnimationTrackEditor::_bezier_edit(int p_for_track) {
@@ -5625,6 +5725,8 @@ void AnimationTrackEditor::_bezier_edit(int p_for_track) {
bezier_edit->set_animation_and_track(animation, p_for_track, read_only);
scroll->hide();
bezier_edit->show();
+ auto_fit->hide();
+ auto_fit_bezier->show();
// Search everything within the track and curve - edit it.
}
@@ -5635,7 +5737,7 @@ void AnimationTrackEditor::_bezier_track_set_key_handle_mode(Animation *p_anim,
p_anim->bezier_track_set_key_handle_mode(p_track, p_index, p_mode, p_set_mode);
}
-void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, int p_track) {
+void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, bool p_ofs_valid, int p_track) {
if (selection.size() && animation.is_valid()) {
int top_track = 0x7FFFFFFF;
float top_time = 1e10;
@@ -5688,7 +5790,13 @@ void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, int p_track) {
const SelectedKey &sk = E->key();
float t = animation->track_get_key_time(sk.track, sk.key);
- float insert_pos = p_ofs >= 0 ? p_ofs : timeline->get_play_position();
+ float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
+
+ if (p_ofs_valid) {
+ if (snap->is_pressed() && step->get_value() != 0) {
+ insert_pos = snap_time(insert_pos);
+ }
+ }
float dst_time = t + (insert_pos - top_time);
int dst_track = sk.track + (start_track - top_track);
@@ -5795,7 +5903,7 @@ void AnimationTrackEditor::_set_key_clipboard(int p_top_track, float p_top_time,
}
}
-void AnimationTrackEditor::_anim_paste_keys(float p_ofs, int p_track) {
+void AnimationTrackEditor::_anim_paste_keys(float p_ofs, bool p_ofs_valid, int p_track) {
if (is_key_clipboard_active() && animation.is_valid()) {
int start_track = p_track;
if (p_track == -1) { // Pasting from shortcut or Edit menu.
@@ -5830,7 +5938,13 @@ void AnimationTrackEditor::_anim_paste_keys(float p_ofs, int p_track) {
for (int i = 0; i < key_clipboard.keys.size(); i++) {
const KeyClipboard::Key key = key_clipboard.keys[i];
- float insert_pos = p_ofs >= 0 ? p_ofs : timeline->get_play_position();
+ float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
+
+ if (p_ofs_valid) {
+ if (snap->is_pressed() && step->get_value() != 0) {
+ insert_pos = snap_time(insert_pos);
+ }
+ }
float dst_time = key.time + insert_pos;
int dst_track = key.track + start_track;
@@ -6427,10 +6541,10 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
case EDIT_DUPLICATE_SELECTED_KEYS: {
if (bezier_edit->is_visible()) {
- bezier_edit->duplicate_selected_keys(-1.0);
+ bezier_edit->duplicate_selected_keys(-1.0, false);
break;
}
- _anim_duplicate_keys(-1.0, -1.0);
+ _anim_duplicate_keys(-1.0, false, -1.0);
} break;
case EDIT_CUT_KEYS: {
if (bezier_edit->is_visible()) {
@@ -6447,7 +6561,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
_anim_copy_keys(false);
} break;
case EDIT_PASTE_KEYS: {
- _anim_paste_keys(-1.0, -1.0);
+ _anim_paste_keys(-1.0, false, -1.0);
} break;
case EDIT_MOVE_FIRST_SELECTED_KEY_TO_CURSOR: {
if (moving_selection || selection.is_empty()) {
@@ -6865,6 +6979,18 @@ bool AnimationTrackEditor::is_grouping_tracks() {
return !view_group->is_pressed();
}
+void AnimationTrackEditor::_auto_fit() {
+ timeline->auto_fit();
+}
+
+void AnimationTrackEditor::_auto_fit_bezier() {
+ timeline->auto_fit();
+
+ if (bezier_edit->is_visible()) {
+ bezier_edit->auto_fit_vertically();
+ }
+}
+
void AnimationTrackEditor::_selection_changed() {
if (selected_filter->is_pressed()) {
_update_tracks(); // Needs updatin.
@@ -7179,6 +7305,19 @@ AnimationTrackEditor::AnimationTrackEditor() {
bottom_hb->add_child(zoom);
timeline->set_zoom(zoom);
+ auto_fit = memnew(Button);
+ auto_fit->set_flat(true);
+ auto_fit->connect("pressed", callable_mp(this, &AnimationTrackEditor::_auto_fit));
+ auto_fit->set_shortcut(ED_SHORTCUT("animation_editor/auto_fit", TTR("Fit to panel"), KeyModifierMask::ALT | Key::F));
+ bottom_hb->add_child(auto_fit);
+
+ auto_fit_bezier = memnew(Button);
+ auto_fit_bezier->set_flat(true);
+ auto_fit_bezier->set_visible(false);
+ auto_fit_bezier->connect("pressed", callable_mp(this, &AnimationTrackEditor::_auto_fit_bezier));
+ auto_fit_bezier->set_shortcut(ED_SHORTCUT("animation_editor/auto_fit", TTR("Fit to panel"), KeyModifierMask::ALT | Key::F));
+ bottom_hb->add_child(auto_fit_bezier);
+
edit = memnew(MenuButton);
edit->set_shortcut_context(this);
edit->set_text(TTR("Edit"));
diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h
index d0da7b0062..f449b51b81 100644
--- a/editor/animation_track_editor.h
+++ b/editor/animation_track_editor.h
@@ -182,6 +182,9 @@ class AnimationTimelineEdit : public Range {
virtual void gui_input(const Ref<InputEvent> &p_event) override;
void _track_added(int p_track);
+ float _get_zoom_scale(double p_zoom_value) const;
+ void _scroll_to_start();
+
protected:
static void _bind_methods();
void _notification(int p_what);
@@ -197,6 +200,7 @@ public:
void set_track_edit(AnimationTrackEdit *p_track_edit);
void set_zoom(Range *p_zoom);
Range *get_zoom() const { return zoom; }
+ void auto_fit();
void set_play_position(float p_pos);
float get_play_position() const;
@@ -286,9 +290,11 @@ class AnimationTrackEdit : public Control {
mutable int dropping_at = 0;
float insert_at_pos = 0.0f;
bool moving_selection_attempt = false;
+ bool moving_selection_effective = false;
+ float moving_selection_pivot = 0.0f;
+ float moving_selection_mouse_begin_x = 0.0f;
int select_single_attempt = -1;
bool moving_selection = false;
- float moving_selection_from_ofs = 0.0f;
bool in_group = false;
AnimationTrackEditor *editor = nullptr;
@@ -404,6 +410,8 @@ class AnimationTrackEditor : public VBoxContainer {
Button *snap = nullptr;
Button *bezier_edit_icon = nullptr;
OptionButton *snap_mode = nullptr;
+ Button *auto_fit = nullptr;
+ Button *auto_fit_bezier = nullptr;
Button *imported_anim_warning = nullptr;
void _show_imported_anim_warning();
@@ -579,18 +587,21 @@ class AnimationTrackEditor : public VBoxContainer {
void _cleanup_animation(Ref<Animation> p_animation);
- void _anim_duplicate_keys(float p_ofs, int p_track);
+ void _anim_duplicate_keys(float p_ofs, bool p_ofs_valid, int p_track);
void _anim_copy_keys(bool p_cut);
bool _is_track_compatible(int p_target_track_idx, Variant::Type p_source_value_type, Animation::TrackType p_source_track_type);
- void _anim_paste_keys(float p_ofs, int p_track);
+ void _anim_paste_keys(float p_ofs, bool p_ofs_valid, int p_track);
void _view_group_toggle();
Button *view_group = nullptr;
Button *selected_filter = nullptr;
+ void _auto_fit();
+ void _auto_fit_bezier();
+
void _selection_changed();
ConfirmationDialog *track_copy_dialog = nullptr;
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 6c16951186..0e369c91e1 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -941,6 +941,10 @@ void CodeTextEditor::_complete_request() {
font_color = completion_string_color;
} else if (e.insert_text.begins_with("##") || e.insert_text.begins_with("///")) {
font_color = completion_doc_comment_color;
+ } else if (e.insert_text.begins_with("&")) {
+ font_color = completion_string_name_color;
+ } else if (e.insert_text.begins_with("^")) {
+ font_color = completion_node_path_color;
} else if (e.insert_text.begins_with("#") || e.insert_text.begins_with("//")) {
font_color = completion_comment_color;
}
@@ -997,6 +1001,8 @@ void CodeTextEditor::update_editor_settings() {
// Theme: Highlighting
completion_font_color = EDITOR_GET("text_editor/theme/highlighting/completion_font_color");
completion_string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
+ completion_string_name_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/string_name_color");
+ completion_node_path_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/node_path_color");
completion_comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
completion_doc_comment_color = EDITOR_GET("text_editor/theme/highlighting/doc_comment_color");
diff --git a/editor/code_editor.h b/editor/code_editor.h
index c888619ac0..87031b672c 100644
--- a/editor/code_editor.h
+++ b/editor/code_editor.h
@@ -184,6 +184,8 @@ class CodeTextEditor : public VBoxContainer {
Color completion_font_color;
Color completion_string_color;
+ Color completion_string_name_color;
+ Color completion_node_path_color;
Color completion_comment_color;
Color completion_doc_comment_color;
CodeTextEditorCodeCompleteFunc code_complete_func;
diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.cpp b/editor/debugger/debug_adapter/debug_adapter_parser.cpp
index 5009de4ac4..b75e5c1c0d 100644
--- a/editor/debugger/debug_adapter/debug_adapter_parser.cpp
+++ b/editor/debugger/debug_adapter/debug_adapter_parser.cpp
@@ -601,12 +601,12 @@ Dictionary DebugAdapterParser::ev_continued() const {
return event;
}
-Dictionary DebugAdapterParser::ev_output(const String &p_message) const {
+Dictionary DebugAdapterParser::ev_output(const String &p_message, RemoteDebugger::MessageType p_type) const {
Dictionary event = prepare_base_event(), body;
event["event"] = "output";
event["body"] = body;
- body["category"] = "stdout";
+ body["category"] = (p_type == RemoteDebugger::MessageType::MESSAGE_TYPE_ERROR) ? "stderr" : "stdout";
body["output"] = p_message + "\r\n";
return event;
diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.h b/editor/debugger/debug_adapter/debug_adapter_parser.h
index e5493a4b9f..9860e96498 100644
--- a/editor/debugger/debug_adapter/debug_adapter_parser.h
+++ b/editor/debugger/debug_adapter/debug_adapter_parser.h
@@ -32,6 +32,7 @@
#define DEBUG_ADAPTER_PARSER_H
#include "core/config/project_settings.h"
+#include "core/debugger/remote_debugger.h"
#include "debug_adapter_protocol.h"
#include "debug_adapter_types.h"
@@ -98,7 +99,7 @@ public:
Dictionary ev_stopped_breakpoint(const int &p_id) const;
Dictionary ev_stopped_step() const;
Dictionary ev_continued() const;
- Dictionary ev_output(const String &p_message) const;
+ Dictionary ev_output(const String &p_message, RemoteDebugger::MessageType p_type) const;
Dictionary ev_custom_data(const String &p_msg, const Array &p_data) const;
Dictionary ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const;
};
diff --git a/editor/debugger/debug_adapter/debug_adapter_protocol.cpp b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp
index 7417a3d8f7..d03df88b75 100644
--- a/editor/debugger/debug_adapter/debug_adapter_protocol.cpp
+++ b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp
@@ -763,8 +763,8 @@ void DebugAdapterProtocol::notify_continued() {
reset_stack_info();
}
-void DebugAdapterProtocol::notify_output(const String &p_message) {
- Dictionary event = parser->ev_output(p_message);
+void DebugAdapterProtocol::notify_output(const String &p_message, RemoteDebugger::MessageType p_type) {
+ Dictionary event = parser->ev_output(p_message, p_type);
for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) {
E->get()->res_queue.push_back(event);
}
@@ -828,8 +828,8 @@ void DebugAdapterProtocol::on_debug_stopped() {
notify_terminated();
}
-void DebugAdapterProtocol::on_debug_output(const String &p_message) {
- notify_output(p_message);
+void DebugAdapterProtocol::on_debug_output(const String &p_message, int p_type) {
+ notify_output(p_message, RemoteDebugger::MessageType(p_type));
}
void DebugAdapterProtocol::on_debug_breaked(const bool &p_reallydid, const bool &p_can_debug, const String &p_reason, const bool &p_has_stackdump) {
diff --git a/editor/debugger/debug_adapter/debug_adapter_protocol.h b/editor/debugger/debug_adapter/debug_adapter_protocol.h
index fb1c533bbb..caff0f9c7f 100644
--- a/editor/debugger/debug_adapter/debug_adapter_protocol.h
+++ b/editor/debugger/debug_adapter/debug_adapter_protocol.h
@@ -86,7 +86,7 @@ private:
void on_client_disconnected(const Ref<DAPeer> &p_peer);
void on_debug_paused();
void on_debug_stopped();
- void on_debug_output(const String &p_message);
+ void on_debug_output(const String &p_message, int p_type);
void on_debug_breaked(const bool &p_reallydid, const bool &p_can_debug, const String &p_reason, const bool &p_has_stackdump);
void on_debug_breakpoint_toggled(const String &p_path, const int &p_line, const bool &p_enabled);
void on_debug_stack_dump(const Array &p_stack_dump);
@@ -139,7 +139,7 @@ public:
void notify_stopped_breakpoint(const int &p_id);
void notify_stopped_step();
void notify_continued();
- void notify_output(const String &p_message);
+ void notify_output(const String &p_message, RemoteDebugger::MessageType p_type);
void notify_custom_data(const String &p_msg, const Array &p_data);
void notify_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled);
diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp
index 979834ebab..2b880274f8 100644
--- a/editor/debugger/script_editor_debugger.cpp
+++ b/editor/debugger/script_editor_debugger.cpp
@@ -495,7 +495,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, uint64_t p_thread
} break;
}
EditorNode::get_log()->add_message(output_strings[i], msg_type);
- emit_signal(SNAME("output"), output_strings[i]);
+ emit_signal(SNAME("output"), output_strings[i], msg_type);
}
} else if (p_msg == "performance:profile_frame") {
Vector<float> frame_data;
@@ -1757,7 +1757,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("output"));
+ 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")));
ADD_SIGNAL(MethodInfo("stack_frame_var", PropertyInfo(Variant::ARRAY, "data")));
diff --git a/editor/editor_atlas_packer.cpp b/editor/editor_atlas_packer.cpp
index 5de4869c88..a0f5f1bf11 100644
--- a/editor/editor_atlas_packer.cpp
+++ b/editor/editor_atlas_packer.cpp
@@ -72,8 +72,7 @@ void EditorAtlasPacker::chart_pack(Vector<Chart> &charts, int &r_width, int &r_h
Vector2 vtx = chart.vertices[chart.faces[j].vertex[k]];
vtx -= aabb.position;
vtx /= divide_by;
- vtx.x = MIN(vtx.x, w - 1);
- vtx.y = MIN(vtx.y, h - 1);
+ vtx = vtx.min(Vector2(w - 1, h - 1));
v[k] = vtx;
}
diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp
index 47642c1592..4b68a21cb9 100644
--- a/editor/editor_data.cpp
+++ b/editor/editor_data.cpp
@@ -721,8 +721,12 @@ bool EditorData::check_and_update_scene(int p_idx) {
}
new_scene->set_scene_file_path(edited_scene[p_idx].root->get_scene_file_path());
-
- memdelete(edited_scene[p_idx].root);
+ Node *old_root = edited_scene[p_idx].root;
+ for (int i = 0; i < old_root->get_child_count(); i++) {
+ memdelete(old_root->get_child(i));
+ }
+ old_root->replace_by(new_scene);
+ memdelete(old_root);
edited_scene.write[p_idx].root = new_scene;
if (!new_scene->get_scene_file_path().is_empty()) {
edited_scene.write[p_idx].path = new_scene->get_scene_file_path();
diff --git a/editor/editor_data.h b/editor/editor_data.h
index d71a2b3ed3..42b2d2ed0c 100644
--- a/editor/editor_data.h
+++ b/editor/editor_data.h
@@ -300,7 +300,7 @@ public:
void remove_node(Node *p_node);
bool is_selected(Node *p_node) const;
- template <class T>
+ template <typename T>
T *get_node_editor_data(Node *p_node) {
if (!selection.has(p_node)) {
return nullptr;
diff --git a/editor/editor_dock_manager.cpp b/editor/editor_dock_manager.cpp
index 08719d6bf0..3fba07f686 100644
--- a/editor/editor_dock_manager.cpp
+++ b/editor/editor_dock_manager.cpp
@@ -49,8 +49,6 @@
EditorDockManager *EditorDockManager::singleton = nullptr;
-static const char *META_TOGGLE_SHORTCUT = "_toggle_shortcut";
-
void DockSplitContainer::_update_visibility() {
if (is_updating) {
return;
@@ -106,359 +104,215 @@ void DockSplitContainer::remove_child_notify(Node *p_child) {
_update_visibility();
}
-void EditorDockManager::_dock_select_popup_theme_changed() {
- if (dock_float) {
- dock_float->set_icon(dock_select_popup->get_editor_theme_icon(SNAME("MakeFloating")));
- }
- if (dock_select_popup->is_layout_rtl()) {
- dock_tab_move_left->set_icon(dock_select_popup->get_editor_theme_icon(SNAME("Forward")));
- dock_tab_move_right->set_icon(dock_select_popup->get_editor_theme_icon(SNAME("Back")));
- } else {
- dock_tab_move_left->set_icon(dock_select_popup->get_editor_theme_icon(SNAME("Back")));
- dock_tab_move_right->set_icon(dock_select_popup->get_editor_theme_icon(SNAME("Forward")));
- }
-
- dock_to_bottom->set_icon(dock_select_popup->get_editor_theme_icon(SNAME("ControlAlignBottomWide")));
+void EditorDockManager::_dock_split_dragged(int p_offset) {
+ EditorNode::get_singleton()->save_editor_layout_delayed();
}
-void EditorDockManager::_dock_popup_exit() {
- dock_select_rect_over_idx = -1;
- dock_select->queue_redraw();
-}
+void EditorDockManager::_dock_container_gui_input(const Ref<InputEvent> &p_input, TabContainer *p_dock_container) {
+ Ref<InputEventMouseButton> mb = p_input;
-void EditorDockManager::_dock_pre_popup(int p_dock_slot) {
- dock_popup_selected_idx = p_dock_slot;
- dock_bottom_selected_idx = -1;
+ if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
+ int tab_id = p_dock_container->get_tab_bar()->get_hovered_tab();
+ if (tab_id < 0) {
+ return;
+ }
- if (bool(dock_slot[p_dock_slot]->get_current_tab_control()->call("_can_dock_horizontal"))) {
- dock_to_bottom->show();
- } else {
- dock_to_bottom->hide();
+ // Right click context menu.
+ dock_context_popup->set_dock(p_dock_container->get_tab_control(tab_id));
+ dock_context_popup->set_position(p_dock_container->get_screen_position() + mb->get_position());
+ dock_context_popup->popup();
}
+}
- if (dock_float) {
- dock_float->show();
+void EditorDockManager::_bottom_dock_button_gui_input(const Ref<InputEvent> &p_input, Control *p_dock, Button *p_bottom_button) {
+ Ref<InputEventMouseButton> mb = p_input;
+
+ if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
+ // Right click context menu.
+ dock_context_popup->set_dock(p_dock);
+ dock_context_popup->set_position(p_bottom_button->get_screen_position() + mb->get_position());
+ dock_context_popup->popup();
}
- dock_tab_move_right->show();
- dock_tab_move_left->show();
}
-void EditorDockManager::_dock_move_left() {
- if (dock_popup_selected_idx < 0 || dock_popup_selected_idx >= DOCK_SLOT_MAX) {
- return;
- }
- Control *current_ctl = dock_slot[dock_popup_selected_idx]->get_tab_control(dock_slot[dock_popup_selected_idx]->get_current_tab());
- Control *prev_ctl = dock_slot[dock_popup_selected_idx]->get_tab_control(dock_slot[dock_popup_selected_idx]->get_current_tab() - 1);
- if (!current_ctl || !prev_ctl) {
+void EditorDockManager::_dock_container_update_visibility(TabContainer *p_dock_container) {
+ if (!docks_visible) {
return;
}
- dock_slot[dock_popup_selected_idx]->move_child(current_ctl, prev_ctl->get_index(false));
- dock_select->queue_redraw();
- _edit_current();
- emit_signal(SNAME("layout_changed"));
+ // Hide the dock container if there are no tabs.
+ p_dock_container->set_visible(p_dock_container->get_tab_count() > 0);
}
-void EditorDockManager::_dock_move_right() {
- if (dock_popup_selected_idx < 0 || dock_popup_selected_idx >= DOCK_SLOT_MAX) {
+void EditorDockManager::_update_layout() {
+ if (!dock_context_popup->is_inside_tree() || EditorNode::get_singleton()->is_exiting()) {
return;
}
- Control *current_ctl = dock_slot[dock_popup_selected_idx]->get_tab_control(dock_slot[dock_popup_selected_idx]->get_current_tab());
- Control *next_ctl = dock_slot[dock_popup_selected_idx]->get_tab_control(dock_slot[dock_popup_selected_idx]->get_current_tab() + 1);
- if (!current_ctl || !next_ctl) {
- return;
- }
- dock_slot[dock_popup_selected_idx]->move_child(next_ctl, current_ctl->get_index(false));
- dock_select->queue_redraw();
- _edit_current();
- emit_signal(SNAME("layout_changed"));
+ EditorNode::get_singleton()->edit_current();
+ dock_context_popup->docks_updated();
+ EditorNode::get_singleton()->save_editor_layout_delayed();
}
-void EditorDockManager::_dock_select_input(const Ref<InputEvent> &p_input) {
- Ref<InputEventMouse> me = p_input;
-
- if (me.is_valid()) {
- Vector2 point = me->get_position();
-
- int nrect = -1;
- for (int i = 0; i < DOCK_SLOT_MAX; i++) {
- if (dock_select_rect[i].has_point(point)) {
- nrect = i;
- break;
- }
- }
-
- if (nrect != dock_select_rect_over_idx) {
- dock_select->queue_redraw();
- dock_select_rect_over_idx = nrect;
- }
-
- if (nrect == -1) {
- return;
- }
-
- Ref<InputEventMouseButton> mb = me;
-
- if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
- if (dock_bottom_selected_idx != -1) {
- EditorNode::get_bottom_panel()->remove_item(bottom_docks[dock_bottom_selected_idx]);
-
- bottom_docks[dock_bottom_selected_idx]->call("_set_dock_horizontal", false);
-
- dock_slot[nrect]->add_child(bottom_docks[dock_bottom_selected_idx]);
- dock_slot[nrect]->show();
- bottom_docks.remove_at(dock_bottom_selected_idx);
- dock_bottom_selected_idx = -1;
- dock_popup_selected_idx = nrect; // Move to dock popup selected.
- dock_select->queue_redraw();
+void EditorDockManager::_window_close_request(WindowWrapper *p_wrapper) {
+ // Give the dock back to the original owner.
+ Control *dock = _close_window(p_wrapper);
+ ERR_FAIL_COND(!all_docks.has(dock));
- update_dock_slots_visibility(true);
-
- _edit_current();
- emit_signal(SNAME("layout_changed"));
- }
-
- if (dock_popup_selected_idx != nrect) {
- dock_slot[nrect]->move_tab_from_tab_container(dock_slot[dock_popup_selected_idx], dock_slot[dock_popup_selected_idx]->get_current_tab(), dock_slot[nrect]->get_tab_count());
-
- if (dock_slot[dock_popup_selected_idx]->get_tab_count() == 0) {
- dock_slot[dock_popup_selected_idx]->hide();
- } else {
- dock_slot[dock_popup_selected_idx]->set_current_tab(0);
- }
-
- dock_popup_selected_idx = nrect;
- dock_slot[nrect]->show();
- dock_select->queue_redraw();
-
- update_dock_slots_visibility(true);
-
- _edit_current();
- emit_signal(SNAME("layout_changed"));
- }
- }
- }
+ all_docks[dock].open = false;
+ open_dock(dock);
+ focus_dock(dock);
}
-void EditorDockManager::_dock_select_draw() {
- Size2 s = dock_select->get_size();
- s.y /= 2.0;
- s.x /= 6.0;
+Control *EditorDockManager::_close_window(WindowWrapper *p_wrapper) {
+ p_wrapper->set_block_signals(true);
+ Control *dock = p_wrapper->release_wrapped_control();
+ p_wrapper->set_block_signals(false);
+ ERR_FAIL_COND_V(!all_docks.has(dock), nullptr);
- Color used = Color(0.6, 0.6, 0.6, 0.8);
- Color used_selected = Color(0.8, 0.8, 0.8, 0.8);
- Color tab_selected = dock_select->get_theme_color(SNAME("mono_color"), EditorStringName(Editor));
- Color unused = used;
- unused.a = 0.4;
- Color unusable = unused;
- unusable.a = 0.1;
+ all_docks[dock].dock_window = nullptr;
+ dock_windows.erase(p_wrapper);
+ p_wrapper->queue_free();
+ return dock;
+}
- Rect2 unr(s.x * 2, 0, s.x * 2, s.y * 2);
- unr.position += Vector2(2, 5);
- unr.size -= Vector2(4, 7);
+void EditorDockManager::_open_dock_in_window(Control *p_dock, bool p_show_window) {
+ ERR_FAIL_NULL(p_dock);
- dock_select->draw_rect(unr, unusable);
+ Size2 borders = Size2(4, 4) * EDSCALE;
+ // Remember size and position before removing it from the main window.
+ Size2 dock_size = p_dock->get_size() + borders * 2;
+ Point2 dock_screen_pos = p_dock->get_screen_position();
- dock_tab_move_left->set_disabled(true);
- dock_tab_move_right->set_disabled(true);
+ WindowWrapper *wrapper = memnew(WindowWrapper);
+ wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), all_docks[p_dock].title));
+ wrapper->set_margins_enabled(true);
- if (dock_popup_selected_idx != -1 && dock_slot[dock_popup_selected_idx]->get_tab_count()) {
- dock_tab_move_left->set_disabled(dock_slot[dock_popup_selected_idx]->get_current_tab() == 0);
- dock_tab_move_right->set_disabled(dock_slot[dock_popup_selected_idx]->get_current_tab() >= dock_slot[dock_popup_selected_idx]->get_tab_count() - 1);
- }
+ EditorNode::get_singleton()->get_gui_base()->add_child(wrapper);
- for (int i = 0; i < DOCK_SLOT_MAX; i++) {
- Vector2 ofs;
-
- switch (i) {
- case DOCK_SLOT_LEFT_UL: {
- } break;
- case DOCK_SLOT_LEFT_BL: {
- ofs.y += s.y;
- } break;
- case DOCK_SLOT_LEFT_UR: {
- ofs.x += s.x;
- } break;
- case DOCK_SLOT_LEFT_BR: {
- ofs += s;
- } break;
- case DOCK_SLOT_RIGHT_UL: {
- ofs.x += s.x * 4;
- } break;
- case DOCK_SLOT_RIGHT_BL: {
- ofs.x += s.x * 4;
- ofs.y += s.y;
-
- } break;
- case DOCK_SLOT_RIGHT_UR: {
- ofs.x += s.x * 4;
- ofs.x += s.x;
-
- } break;
- case DOCK_SLOT_RIGHT_BR: {
- ofs.x += s.x * 4;
- ofs += s;
-
- } break;
- }
+ _move_dock(p_dock, nullptr);
+ wrapper->set_wrapped_control(p_dock);
- Rect2 r(ofs, s);
- dock_select_rect[i] = r;
- r.position += Vector2(2, 5);
- r.size -= Vector2(4, 7);
+ all_docks[p_dock].dock_window = wrapper;
+ all_docks[p_dock].open = true;
+ p_dock->show();
- if (i == dock_select_rect_over_idx) {
- dock_select->draw_rect(r, used_selected);
- } else if (dock_slot[i]->get_tab_count() == 0) {
- dock_select->draw_rect(r, unused);
- } else {
- dock_select->draw_rect(r, used);
- }
+ wrapper->connect("window_close_requested", callable_mp(this, &EditorDockManager::_window_close_request).bind(wrapper));
+ dock_windows.push_back(wrapper);
- for (int j = 0; j < MIN(3, dock_slot[i]->get_tab_count()); j++) {
- int xofs = (r.size.width / 3) * j;
- Color c = used;
- if (i == dock_popup_selected_idx && (dock_slot[i]->get_current_tab() > 3 || dock_slot[i]->get_current_tab() == j)) {
- c = tab_selected;
- }
- dock_select->draw_rect(Rect2(2 + ofs.x + xofs, ofs.y, r.size.width / 3 - 1, 3), c);
- }
+ if (p_show_window) {
+ wrapper->restore_window(Rect2i(dock_screen_pos, dock_size), EditorNode::get_singleton()->get_gui_base()->get_window()->get_current_screen());
+ _update_layout();
+ p_dock->get_window()->grab_focus();
}
}
-void EditorDockManager::_dock_split_dragged(int p_offset) {
- EditorNode::get_singleton()->save_editor_layout_delayed();
-}
-
-void EditorDockManager::_dock_tab_changed(int p_tab) {
- // Update visibility but don't set current tab.
- update_dock_slots_visibility(true);
-}
+void EditorDockManager::_restore_dock_to_saved_window(Control *p_dock, const Dictionary &p_window_dump) {
+ if (!all_docks[p_dock].dock_window) {
+ _open_dock_in_window(p_dock, false);
+ }
-void EditorDockManager::_edit_current() {
- EditorNode::get_singleton()->edit_current();
+ all_docks[p_dock].dock_window->restore_window_from_saved_position(
+ p_window_dump.get("window_rect", Rect2i()),
+ p_window_dump.get("window_screen", -1),
+ p_window_dump.get("window_screen_rect", Rect2i()));
}
-void EditorDockManager::_dock_floating_close_request(WindowWrapper *p_wrapper) {
- int dock_slot_num = p_wrapper->get_meta("dock_slot");
- int dock_slot_index = p_wrapper->get_meta("dock_index");
-
- // Give back the dock to the original owner.
- Control *dock = p_wrapper->release_wrapped_control();
-
- int target_index = MIN(dock_slot_index, dock_slot[dock_slot_num]->get_tab_count());
- dock_slot[dock_slot_num]->add_child(dock);
- dock_slot[dock_slot_num]->move_child(dock, target_index);
- dock_slot[dock_slot_num]->set_current_tab(target_index);
+void EditorDockManager::_dock_move_to_bottom(Control *p_dock) {
+ _move_dock(p_dock, nullptr);
- floating_docks.erase(p_wrapper);
- p_wrapper->queue_free();
+ all_docks[p_dock].at_bottom = true;
+ all_docks[p_dock].previous_at_bottom = false;
- update_dock_slots_visibility(true);
+ p_dock->call("_set_dock_horizontal", true);
- _edit_current();
+ // Force docks moved to the bottom to appear first in the list, and give them their associated shortcut to toggle their bottom panel.
+ Button *bottom_button = EditorNode::get_bottom_panel()->add_item(all_docks[p_dock].title, p_dock, all_docks[p_dock].shortcut, true);
+ bottom_button->connect("gui_input", callable_mp(this, &EditorDockManager::_bottom_dock_button_gui_input).bind(bottom_button).bind(p_dock));
+ EditorNode::get_bottom_panel()->make_item_visible(p_dock);
}
-void EditorDockManager::_dock_make_selected_float() {
- Control *dock = dock_slot[dock_popup_selected_idx]->get_current_tab_control();
- _dock_make_float(dock, dock_popup_selected_idx);
+void EditorDockManager::_dock_remove_from_bottom(Control *p_dock) {
+ all_docks[p_dock].at_bottom = false;
+ all_docks[p_dock].previous_at_bottom = true;
- dock_select_popup->hide();
- _edit_current();
+ EditorNode::get_bottom_panel()->remove_item(p_dock);
+ p_dock->call("_set_dock_horizontal", false);
}
-void EditorDockManager::bottom_dock_show_placement_popup(const Rect2i &p_position, Control *p_dock) {
- dock_bottom_selected_idx = bottom_docks.find(p_dock);
- ERR_FAIL_COND(dock_bottom_selected_idx == -1);
- dock_popup_selected_idx = -1;
- dock_to_bottom->hide();
-
- Vector2 popup_pos = p_position.position;
- popup_pos.y += p_position.size.height;
-
- if (!EditorNode::get_singleton()->get_gui_base()->is_layout_rtl()) {
- popup_pos.x -= dock_select_popup->get_size().width;
- popup_pos.x += p_position.size.width;
- }
- dock_select_popup->set_position(popup_pos);
- dock_select_popup->popup();
- if (dock_float) {
- dock_float->hide();
- }
- dock_tab_move_right->hide();
- dock_tab_move_left->hide();
+bool EditorDockManager::_is_dock_at_bottom(Control *p_dock) {
+ ERR_FAIL_COND_V(!all_docks.has(p_dock), false);
+ return all_docks[p_dock].at_bottom;
}
-void EditorDockManager::_dock_move_selected_to_bottom() {
- Control *dock = dock_slot[dock_popup_selected_idx]->get_current_tab_control();
- dock_slot[dock_popup_selected_idx]->remove_child(dock);
-
- dock->call("_set_dock_horizontal", true);
-
- bottom_docks.push_back(dock);
-
- // Force docks moved to the bottom to appear first in the list, and give them their associated shortcut to toggle their bottom panel.
- EditorNode::get_bottom_panel()->add_item(dock->get_name(), dock, dock->get_meta(META_TOGGLE_SHORTCUT), true);
+void EditorDockManager::_move_dock_tab_index(Control *p_dock, int p_tab_index, bool p_set_current) {
+ TabContainer *dock_tab_container = Object::cast_to<TabContainer>(p_dock->get_parent());
+ if (!dock_tab_container) {
+ return;
+ }
- dock_select_popup->hide();
- update_dock_slots_visibility(true);
- _edit_current();
- emit_signal(SNAME("layout_changed"));
+ dock_tab_container->set_block_signals(true);
+ int target_index = CLAMP(p_tab_index, 0, dock_tab_container->get_tab_count() - 1);
+ dock_tab_container->move_child(p_dock, dock_tab_container->get_tab_control(target_index)->get_index(false));
+ all_docks[p_dock].previous_tab_index = target_index;
- EditorNode::get_bottom_panel()->make_item_visible(dock);
+ if (p_set_current) {
+ dock_tab_container->set_current_tab(target_index);
+ }
+ dock_tab_container->set_block_signals(false);
}
-void EditorDockManager::_dock_make_float(Control *p_dock, int p_slot_index, bool p_show_window) {
+void EditorDockManager::_move_dock(Control *p_dock, Control *p_target, int p_tab_index, bool p_set_current) {
ERR_FAIL_NULL(p_dock);
+ ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot move unknown dock '%s'.", p_dock->get_name()));
- Size2 borders = Size2(4, 4) * EDSCALE;
- // Remember size and position before removing it from the main window.
- Size2 dock_size = p_dock->get_size() + borders * 2;
- Point2 dock_screen_pos = p_dock->get_screen_position();
-
- int dock_index = p_dock->get_index() - 1;
- dock_slot[p_slot_index]->remove_child(p_dock);
-
- WindowWrapper *wrapper = memnew(WindowWrapper);
- wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), p_dock->get_name()));
- wrapper->set_margins_enabled(true);
-
- EditorNode::get_singleton()->get_gui_base()->add_child(wrapper);
-
- wrapper->set_wrapped_control(p_dock);
- wrapper->set_meta("dock_slot", p_slot_index);
- wrapper->set_meta("dock_index", dock_index);
- wrapper->set_meta("dock_name", p_dock->get_name().operator String());
- p_dock->show();
-
- wrapper->connect("window_close_requested", callable_mp(this, &EditorDockManager::_dock_floating_close_request).bind(wrapper));
-
- dock_select_popup->hide();
-
- if (p_show_window) {
- wrapper->restore_window(Rect2i(dock_screen_pos, dock_size), EditorNode::get_singleton()->get_gui_base()->get_window()->get_current_screen());
+ Node *parent = p_dock->get_parent();
+ if (parent == p_target) {
+ if (p_tab_index >= 0 && parent) {
+ // Only change the tab index.
+ _move_dock_tab_index(p_dock, p_tab_index, p_set_current);
+ }
+ return;
}
- update_dock_slots_visibility(true);
-
- floating_docks.push_back(wrapper);
-
- _edit_current();
-}
-
-void EditorDockManager::_restore_floating_dock(const Dictionary &p_dock_dump, Control *p_dock, int p_slot_index) {
- WindowWrapper *wrapper = Object::cast_to<WindowWrapper>(p_dock);
- if (!wrapper) {
- _dock_make_float(p_dock, p_slot_index, false);
- wrapper = floating_docks[floating_docks.size() - 1];
+ // Remove dock from its existing parent.
+ if (parent) {
+ if (all_docks[p_dock].dock_window) {
+ _close_window(all_docks[p_dock].dock_window);
+ } else if (all_docks[p_dock].at_bottom) {
+ _dock_remove_from_bottom(p_dock);
+ } else {
+ all_docks[p_dock].previous_at_bottom = false;
+ TabContainer *parent_tabs = Object::cast_to<TabContainer>(parent);
+ if (parent_tabs) {
+ all_docks[p_dock].previous_tab_index = parent_tabs->get_tab_idx_from_control(p_dock);
+ }
+ parent->set_block_signals(true);
+ parent->remove_child(p_dock);
+ parent->set_block_signals(false);
+ if (parent_tabs) {
+ _dock_container_update_visibility(parent_tabs);
+ }
+ }
}
- wrapper->restore_window_from_saved_position(
- p_dock_dump.get("window_rect", Rect2i()),
- p_dock_dump.get("window_screen", -1),
- p_dock_dump.get("window_screen_rect", Rect2i()));
+ // Add dock to its new parent, at the given tab index.
+ if (!p_target) {
+ return;
+ }
+ p_target->set_block_signals(true);
+ p_target->add_child(p_dock);
+ p_target->set_block_signals(false);
+ TabContainer *dock_tab_container = Object::cast_to<TabContainer>(p_target);
+ if (dock_tab_container) {
+ dock_tab_container->set_tab_title(dock_tab_container->get_tab_idx_from_control(p_dock), all_docks[p_dock].title);
+ if (p_tab_index >= 0) {
+ _move_dock_tab_index(p_dock, p_tab_index, p_set_current);
+ }
+ _dock_container_update_visibility(dock_tab_container);
+ }
}
void EditorDockManager::save_docks_to_config(Ref<ConfigFile> p_layout, const String &p_section) const {
+ // Save docks by dock slot.
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
String names;
for (int j = 0; j < dock_slot[i]->get_tab_count(); j++) {
@@ -485,22 +339,23 @@ void EditorDockManager::save_docks_to_config(Ref<ConfigFile> p_layout, const Str
}
}
+ // Save docks in windows.
Dictionary floating_docks_dump;
-
- for (WindowWrapper *wrapper : floating_docks) {
+ for (WindowWrapper *wrapper : dock_windows) {
Control *dock = wrapper->get_wrapped_control();
- Dictionary dock_dump;
- dock_dump["window_rect"] = wrapper->get_window_rect();
+ Dictionary window_dump;
+ window_dump["window_rect"] = wrapper->get_window_rect();
int screen = wrapper->get_window_screen();
- dock_dump["window_screen"] = wrapper->get_window_screen();
- dock_dump["window_screen_rect"] = DisplayServer::get_singleton()->screen_get_usable_rect(screen);
+ window_dump["window_screen"] = wrapper->get_window_screen();
+ window_dump["window_screen_rect"] = DisplayServer::get_singleton()->screen_get_usable_rect(screen);
String name = dock->get_name();
- floating_docks_dump[name] = dock_dump;
+ floating_docks_dump[name] = window_dump;
- int dock_slot_id = wrapper->get_meta("dock_slot");
+ // Append to regular dock section so we know where to restore it to.
+ int dock_slot_id = all_docks[dock].dock_slot_index;
String config_key = "dock_" + itos(dock_slot_id + 1);
String names = p_layout->get_value(p_section, config_key, "");
@@ -511,17 +366,39 @@ void EditorDockManager::save_docks_to_config(Ref<ConfigFile> p_layout, const Str
}
p_layout->set_value(p_section, config_key, names);
}
-
p_layout->set_value(p_section, "dock_floating", floating_docks_dump);
+ // Save closed and bottom docks.
Array bottom_docks_dump;
+ Array closed_docks_dump;
+ for (const KeyValue<Control *, DockInfo> &d : all_docks) {
+ if (!d.value.at_bottom && d.value.open && (!d.value.previous_at_bottom || !d.value.dock_window)) {
+ continue;
+ }
+ // Use the name of the Control since it isn't translated.
+ String name = d.key->get_name();
+ if (d.value.at_bottom || (d.value.previous_at_bottom && d.value.dock_window)) {
+ bottom_docks_dump.push_back(name);
+ }
+ if (!d.value.open) {
+ closed_docks_dump.push_back(name);
+ }
- for (Control *bdock : bottom_docks) {
- bottom_docks_dump.push_back(bdock->get_name());
- }
+ int dock_slot_id = all_docks[d.key].dock_slot_index;
+ String config_key = "dock_" + itos(dock_slot_id + 1);
+ String names = p_layout->get_value(p_section, config_key, "");
+ if (names.is_empty()) {
+ names = name;
+ } else {
+ names += "," + name;
+ }
+ p_layout->set_value(p_section, config_key, names);
+ }
p_layout->set_value(p_section, "dock_bottom", bottom_docks_dump);
+ p_layout->set_value(p_section, "dock_closed", closed_docks_dump);
+ // Save SplitContainer offsets.
for (int i = 0; i < vsplits.size(); i++) {
if (vsplits[i]->is_visible_in_tree()) {
p_layout->set_value(p_section, "dock_split_" + itos(i + 1), vsplits[i]->get_split_offset());
@@ -537,9 +414,18 @@ void EditorDockManager::save_docks_to_config(Ref<ConfigFile> p_layout, const Str
void EditorDockManager::load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section) {
Dictionary floating_docks_dump = p_layout->get_value(p_section, "dock_floating", Dictionary());
+ Array dock_bottom = p_layout->get_value(p_section, "dock_bottom", Array());
+ Array closed_docks = p_layout->get_value(p_section, "dock_closed", Array());
bool restore_window_on_load = EDITOR_GET("interface/multi_window/restore_windows_on_load");
+ // Store the docks by name for easy lookup.
+ HashMap<String, Control *> dock_map;
+ for (const KeyValue<Control *, DockInfo> &dock : all_docks) {
+ dock_map[dock.key->get_name()] = dock.key;
+ }
+
+ // Load docks by slot.
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
if (!p_layout->has_section_key(p_section, "dock_" + itos(i + 1))) {
continue;
@@ -550,135 +436,57 @@ void EditorDockManager::load_docks_from_config(Ref<ConfigFile> p_layout, const S
for (int j = names.size() - 1; j >= 0; j--) {
String name = names[j];
- // FIXME: Find it, in a horribly inefficient way.
- int atidx = -1;
- int bottom_idx = -1;
- Control *node = nullptr;
- for (int k = 0; k < DOCK_SLOT_MAX; k++) {
- if (!dock_slot[k]->has_node(name)) {
- continue;
- }
- node = Object::cast_to<Control>(dock_slot[k]->get_node(name));
- if (!node) {
- continue;
- }
- atidx = k;
- break;
- }
-
- if (atidx == -1) {
- // Try floating docks.
- for (WindowWrapper *wrapper : floating_docks) {
- if (wrapper->get_meta("dock_name") == name) {
- if (restore_window_on_load && floating_docks_dump.has(name)) {
- _restore_floating_dock(floating_docks_dump[name], wrapper, i);
- } else {
- atidx = wrapper->get_meta("dock_slot");
- node = wrapper->get_wrapped_control();
- wrapper->set_window_enabled(false);
- }
- break;
- }
- }
- }
-
- if (atidx == -1) {
- // Try bottom docks.
- for (Control *bdock : bottom_docks) {
- if (bdock->get_name() == name) {
- node = bdock;
- bottom_idx = bottom_docks.find(node);
- break;
- }
- }
+ if (!dock_map.has(name)) {
+ continue;
}
+ Control *dock = dock_map[name];
- if (!node) {
- // Well, it's not anywhere.
+ if (!all_docks[dock].enabled) {
+ // Don't open disabled docks.
continue;
}
-
- if (atidx == i) {
- dock_slot[i]->move_child(node, 0);
- } else if (atidx != -1) {
- dock_slot[i]->set_block_signals(true);
- dock_slot[atidx]->set_block_signals(true);
- dock_slot[i]->move_tab_from_tab_container(dock_slot[atidx], dock_slot[atidx]->get_tab_idx_from_control(node), 0);
- dock_slot[i]->set_block_signals(false);
- dock_slot[atidx]->set_block_signals(false);
- } else if (bottom_idx != -1) {
- bottom_docks.erase(node);
- EditorNode::get_bottom_panel()->remove_item(node);
- dock_slot[i]->add_child(node);
- node->call("_set_dock_horizontal", false);
+ if (restore_window_on_load && floating_docks_dump.has(name)) {
+ all_docks[dock].previous_at_bottom = dock_bottom.has(name);
+ _restore_dock_to_saved_window(dock, floating_docks_dump[name]);
+ } else if (dock_bottom.has(name)) {
+ _dock_move_to_bottom(dock);
+ } else {
+ _move_dock(dock, dock_slot[i], 0);
}
- WindowWrapper *wrapper = Object::cast_to<WindowWrapper>(node);
- if (restore_window_on_load && floating_docks_dump.has(name)) {
- if (!dock_slot[i]->is_tab_hidden(dock_slot[i]->get_tab_idx_from_control(node))) {
- _restore_floating_dock(floating_docks_dump[name], node, i);
- }
- } else if (wrapper) {
- wrapper->set_window_enabled(false);
+ if (closed_docks.has(name)) {
+ _move_dock(dock, closed_dock_parent);
+ all_docks[dock].open = false;
+ dock->hide();
+ } else {
+ // Make sure it is open.
+ all_docks[dock].open = true;
+ dock->show();
}
+
+ all_docks[dock].dock_slot_index = i;
+ all_docks[dock].previous_tab_index = j;
}
+ }
- if (!p_layout->has_section_key(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx")) {
+ // Set the selected tabs.
+ for (int i = 0; i < DOCK_SLOT_MAX; i++) {
+ if (dock_slot[i]->get_tab_count() == 0 || !p_layout->has_section_key(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx")) {
continue;
}
-
int selected_tab_idx = p_layout->get_value(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx");
if (selected_tab_idx >= 0 && selected_tab_idx < dock_slot[i]->get_tab_count()) {
- callable_mp(dock_slot[i], &TabContainer::set_current_tab).call_deferred(selected_tab_idx);
- }
- }
-
- Array dock_bottom = p_layout->get_value(p_section, "dock_bottom", Array());
- for (int i = 0; i < dock_bottom.size(); i++) {
- const String &name = dock_bottom[i];
- // FIXME: Find it, in a horribly inefficient way.
- int atidx = -1;
- Control *node = nullptr;
- for (int k = 0; k < DOCK_SLOT_MAX; k++) {
- if (!dock_slot[k]->has_node(name)) {
- continue;
- }
- node = Object::cast_to<Control>(dock_slot[k]->get_node(name));
- if (!node) {
- continue;
- }
- atidx = k;
- break;
- }
-
- if (atidx == -1) {
- // Try floating docks.
- for (WindowWrapper *wrapper : floating_docks) {
- if (wrapper->get_meta("dock_name") == name) {
- atidx = wrapper->get_meta("dock_slot");
- node = wrapper->get_wrapped_control();
- wrapper->set_window_enabled(false);
- break;
- }
- }
- }
-
- if (node) {
- dock_slot[atidx]->remove_child(node);
-
- node->call("_set_dock_horizontal", true);
-
- bottom_docks.push_back(node);
- // Force docks moved to the bottom to appear first in the list, and give them their associated shortcut to toggle their bottom panel.
- EditorNode::get_bottom_panel()->add_item(node->get_name(), node, node->get_meta(META_TOGGLE_SHORTCUT), true);
+ dock_slot[i]->set_block_signals(true);
+ dock_slot[i]->set_current_tab(selected_tab_idx);
+ dock_slot[i]->set_block_signals(false);
}
}
+ // Load SplitContainer offsets.
for (int i = 0; i < vsplits.size(); i++) {
if (!p_layout->has_section_key(p_section, "dock_split_" + itos(i + 1))) {
continue;
}
-
int ofs = p_layout->get_value(p_section, "dock_split_" + itos(i + 1));
vsplits[i]->set_split_offset(ofs);
}
@@ -691,87 +499,155 @@ void EditorDockManager::load_docks_from_config(Ref<ConfigFile> p_layout, const S
hsplits[i]->set_split_offset(ofs);
}
- update_dock_slots_visibility(false);
-
FileSystemDock::get_singleton()->load_layout_from_config(p_layout, p_section);
}
-void EditorDockManager::update_dock_slots_visibility(bool p_keep_selected_tabs) {
- if (!docks_visible) {
- for (int i = 0; i < DOCK_SLOT_MAX; i++) {
- dock_slot[i]->hide();
- }
- } else {
- for (int i = 0; i < DOCK_SLOT_MAX; i++) {
- int first_tab_visible = -1;
- for (int j = 0; j < dock_slot[i]->get_tab_count(); j++) {
- if (!dock_slot[i]->is_tab_hidden(j)) {
- first_tab_visible = j;
- break;
- }
- }
- if (first_tab_visible >= 0) {
- dock_slot[i]->show();
- if (p_keep_selected_tabs) {
- int current_tab = dock_slot[i]->get_current_tab();
- if (dock_slot[i]->is_tab_hidden(current_tab)) {
- dock_slot[i]->set_block_signals(true);
- dock_slot[i]->select_next_available();
- dock_slot[i]->set_block_signals(false);
- }
- } else {
- dock_slot[i]->set_block_signals(true);
- dock_slot[i]->set_current_tab(first_tab_visible);
- dock_slot[i]->set_block_signals(false);
- }
- } else {
- dock_slot[i]->hide();
- }
- }
+void EditorDockManager::bottom_dock_show_placement_popup(const Rect2i &p_position, Control *p_dock) {
+ ERR_FAIL_COND(!all_docks.has(p_dock));
+
+ dock_context_popup->set_dock(p_dock);
+
+ Vector2 popup_pos = p_position.position;
+ popup_pos.y += p_position.size.height;
+
+ if (!EditorNode::get_singleton()->get_gui_base()->is_layout_rtl()) {
+ popup_pos.x -= dock_context_popup->get_size().width;
+ popup_pos.x += p_position.size.width;
}
+ dock_context_popup->set_position(popup_pos);
+ dock_context_popup->popup();
}
-void EditorDockManager::close_all_floating_docks() {
- for (WindowWrapper *wrapper : floating_docks) {
- wrapper->set_window_enabled(false);
+void EditorDockManager::set_dock_enabled(Control *p_dock, bool p_enabled) {
+ ERR_FAIL_NULL(p_dock);
+ ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot set enabled unknown dock '%s'.", p_dock->get_name()));
+
+ if (all_docks[p_dock].enabled == p_enabled) {
+ return;
+ }
+
+ all_docks[p_dock].enabled = p_enabled;
+ if (p_enabled) {
+ open_dock(p_dock, false);
+ } else {
+ close_dock(p_dock);
}
}
-void EditorDockManager::add_control_to_dock(DockSlot p_slot, Control *p_control, const String &p_name, const Ref<Shortcut> &p_shortcut) {
- ERR_FAIL_INDEX(p_slot, DOCK_SLOT_MAX);
- p_control->set_meta(META_TOGGLE_SHORTCUT, p_shortcut);
- dock_slot[p_slot]->add_child(p_control);
- if (!p_name.is_empty()) {
- dock_slot[p_slot]->set_tab_title(dock_slot[p_slot]->get_tab_idx_from_control(p_control), p_name);
+void EditorDockManager::close_dock(Control *p_dock) {
+ ERR_FAIL_NULL(p_dock);
+ ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot close unknown dock '%s'.", p_dock->get_name()));
+
+ if (!all_docks[p_dock].open) {
+ return;
}
+
+ _move_dock(p_dock, closed_dock_parent);
+
+ all_docks[p_dock].open = false;
+ p_dock->hide();
+
+ _update_layout();
}
-void EditorDockManager::remove_control_from_dock(Control *p_control) {
- // If the dock is floating, close it first.
- for (WindowWrapper *wrapper : floating_docks) {
- if (p_control == wrapper->get_wrapped_control()) {
- wrapper->set_window_enabled(false);
- break;
- }
+void EditorDockManager::open_dock(Control *p_dock, bool p_set_current) {
+ ERR_FAIL_NULL(p_dock);
+ ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot open unknown dock '%s'.", p_dock->get_name()));
+
+ if (all_docks[p_dock].open) {
+ return;
}
- Control *dock = nullptr;
- for (int i = 0; i < DOCK_SLOT_MAX; i++) {
- if (p_control->get_parent() == dock_slot[i]) {
- dock = dock_slot[i];
- break;
+ all_docks[p_dock].open = true;
+ p_dock->show();
+
+ // Open dock to its previous location.
+ if (all_docks[p_dock].previous_at_bottom) {
+ _dock_move_to_bottom(p_dock);
+ } else {
+ TabContainer *slot = dock_slot[all_docks[p_dock].dock_slot_index];
+ int tab_index = all_docks[p_dock].previous_tab_index;
+ if (tab_index < 0) {
+ tab_index = slot->get_tab_count();
}
+ _move_dock(p_dock, slot, tab_index, p_set_current);
}
- ERR_FAIL_NULL_MSG(dock, "Control is not in a dock.");
+ _update_layout();
+}
- dock->remove_child(p_control);
- update_dock_slots_visibility();
+TabContainer *EditorDockManager::get_dock_tab_container(Control *p_dock) const {
+ return Object::cast_to<TabContainer>(p_dock->get_parent());
+}
+
+void EditorDockManager::focus_dock(Control *p_dock) {
+ ERR_FAIL_NULL(p_dock);
+ ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot focus unknown dock '%s'.", p_dock->get_name()));
+
+ if (!all_docks[p_dock].enabled) {
+ return;
+ }
+
+ if (!all_docks[p_dock].open) {
+ open_dock(p_dock);
+ }
+
+ if (all_docks[p_dock].dock_window) {
+ p_dock->get_window()->grab_focus();
+ return;
+ }
+
+ if (all_docks[p_dock].at_bottom) {
+ EditorNode::get_bottom_panel()->make_item_visible(p_dock);
+ return;
+ }
+
+ if (!docks_visible) {
+ return;
+ }
+
+ TabContainer *tab_container = get_dock_tab_container(p_dock);
+ if (!tab_container) {
+ return;
+ }
+ int tab_index = tab_container->get_tab_idx_from_control(p_dock);
+ tab_container->get_tab_bar()->grab_focus();
+ tab_container->set_current_tab(tab_index);
+}
+
+void EditorDockManager::add_control_to_dock(DockSlot p_slot, Control *p_dock, const String &p_title, const Ref<Shortcut> &p_shortcut) {
+ ERR_FAIL_NULL(p_dock);
+ ERR_FAIL_INDEX(p_slot, DOCK_SLOT_MAX);
+ ERR_FAIL_COND_MSG(all_docks.has(p_dock), vformat("Cannot add dock '%s', already added.", p_dock->get_name()));
+
+ DockInfo dock_info;
+ dock_info.title = !p_title.is_empty() ? p_title : String(p_dock->get_name());
+ dock_info.dock_slot_index = p_slot;
+ dock_info.shortcut = p_shortcut;
+ all_docks[p_dock] = dock_info;
+
+ open_dock(p_dock, false);
+}
+
+void EditorDockManager::remove_control_from_dock(Control *p_dock) {
+ ERR_FAIL_NULL(p_dock);
+ ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot remove unknown dock '%s'.", p_dock->get_name()));
+
+ _move_dock(p_dock, nullptr);
+
+ all_docks.erase(p_dock);
+ _update_layout();
}
void EditorDockManager::set_docks_visible(bool p_show) {
+ if (docks_visible == p_show) {
+ return;
+ }
docks_visible = p_show;
- update_dock_slots_visibility(true);
+ for (int i = 0; i < DOCK_SLOT_MAX; i++) {
+ dock_slot[i]->set_visible(docks_visible && dock_slot[i]->get_tab_count() > 0);
+ }
+ _update_layout();
}
bool EditorDockManager::are_docks_visible() const {
@@ -796,80 +672,307 @@ void EditorDockManager::register_dock_slot(DockSlot p_dock_slot, TabContainer *p
p_tab_container->set_custom_minimum_size(Size2(170, 0) * EDSCALE);
p_tab_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- p_tab_container->set_popup(dock_select_popup);
- p_tab_container->connect("pre_popup_pressed", callable_mp(this, &EditorDockManager::_dock_pre_popup).bind(p_dock_slot));
+ p_tab_container->set_popup(dock_context_popup);
+ p_tab_container->connect("pre_popup_pressed", callable_mp(dock_context_popup, &DockContextPopup::select_current_dock_in_dock_slot).bind(p_dock_slot));
p_tab_container->set_drag_to_rearrange_enabled(true);
p_tab_container->set_tabs_rearrange_group(1);
- p_tab_container->connect("tab_changed", callable_mp(this, &EditorDockManager::_dock_tab_changed));
+ p_tab_container->connect("tab_changed", callable_mp(this, &EditorDockManager::_update_layout).unbind(1));
+ p_tab_container->connect("active_tab_rearranged", callable_mp(this, &EditorDockManager::_update_layout).unbind(1));
+ p_tab_container->connect("child_order_changed", callable_mp(this, &EditorDockManager::_dock_container_update_visibility).bind(p_tab_container));
p_tab_container->set_use_hidden_tabs_for_min_size(true);
+ p_tab_container->get_tab_bar()->connect("gui_input", callable_mp(this, &EditorDockManager::_dock_container_gui_input).bind(p_tab_container));
+ p_tab_container->hide();
}
int EditorDockManager::get_vsplit_count() const {
return vsplits.size();
}
-void EditorDockManager::_bind_methods() {
- ADD_SIGNAL(MethodInfo("layout_changed"));
-}
-
EditorDockManager::EditorDockManager() {
singleton = this;
- dock_select_popup = memnew(PopupPanel);
- EditorNode::get_singleton()->get_gui_base()->add_child(dock_select_popup);
- VBoxContainer *dock_vb = memnew(VBoxContainer);
- dock_select_popup->add_child(dock_vb);
- dock_select_popup->connect("theme_changed", callable_mp(this, &EditorDockManager::_dock_select_popup_theme_changed));
-
- HBoxContainer *dock_hb = memnew(HBoxContainer);
- dock_tab_move_left = memnew(Button);
- dock_tab_move_left->set_flat(true);
- dock_tab_move_left->set_focus_mode(Control::FOCUS_NONE);
- dock_tab_move_left->connect("pressed", callable_mp(this, &EditorDockManager::_dock_move_left));
- dock_hb->add_child(dock_tab_move_left);
-
- Label *dock_label = memnew(Label);
- dock_label->set_text(TTR("Dock Position"));
- dock_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- dock_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
- dock_hb->add_child(dock_label);
-
- dock_tab_move_right = memnew(Button);
- dock_tab_move_right->set_flat(true);
- dock_tab_move_right->set_focus_mode(Control::FOCUS_NONE);
- dock_tab_move_right->connect("pressed", callable_mp(this, &EditorDockManager::_dock_move_right));
-
- dock_hb->add_child(dock_tab_move_right);
- dock_vb->add_child(dock_hb);
+ closed_dock_parent = EditorNode::get_singleton()->get_gui_base();
+
+ dock_context_popup = memnew(DockContextPopup);
+ EditorNode::get_singleton()->get_gui_base()->add_child(dock_context_popup);
+}
+
+void DockContextPopup::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_THEME_CHANGED: {
+ if (make_float_button) {
+ make_float_button->set_icon(get_editor_theme_icon(SNAME("MakeFloating")));
+ }
+ if (is_layout_rtl()) {
+ tab_move_left_button->set_icon(get_editor_theme_icon(SNAME("Forward")));
+ tab_move_right_button->set_icon(get_editor_theme_icon(SNAME("Back")));
+ tab_move_left_button->set_tooltip_text(TTR("Move this dock right one tab."));
+ tab_move_right_button->set_tooltip_text(TTR("Move this dock left one tab."));
+ } else {
+ tab_move_left_button->set_icon(get_editor_theme_icon(SNAME("Back")));
+ tab_move_right_button->set_icon(get_editor_theme_icon(SNAME("Forward")));
+ tab_move_left_button->set_tooltip_text(TTR("Move this dock left one tab."));
+ tab_move_right_button->set_tooltip_text(TTR("Move this dock right one tab."));
+ }
+ dock_to_bottom_button->set_icon(get_editor_theme_icon(SNAME("ControlAlignBottomWide")));
+ } break;
+ }
+}
+
+void DockContextPopup::_tab_move_left() {
+ TabContainer *tab_container = dock_manager->get_dock_tab_container(context_dock);
+ if (!tab_container) {
+ return;
+ }
+ int new_index = tab_container->get_tab_idx_from_control(context_dock) - 1;
+ dock_manager->_move_dock(context_dock, tab_container, new_index);
+ dock_manager->_update_layout();
+ dock_select->queue_redraw();
+}
+
+void DockContextPopup::_tab_move_right() {
+ TabContainer *tab_container = dock_manager->get_dock_tab_container(context_dock);
+ if (!tab_container) {
+ return;
+ }
+ int new_index = tab_container->get_tab_idx_from_control(context_dock) + 1;
+ dock_manager->_move_dock(context_dock, tab_container, new_index);
+ dock_manager->_update_layout();
+ dock_select->queue_redraw();
+}
+
+void DockContextPopup::_float_dock() {
+ hide();
+ dock_manager->_open_dock_in_window(context_dock);
+}
+
+void DockContextPopup::_move_dock_to_bottom() {
+ hide();
+ dock_manager->_dock_move_to_bottom(context_dock);
+ dock_manager->_update_layout();
+}
+
+void DockContextPopup::_dock_select_input(const Ref<InputEvent> &p_input) {
+ Ref<InputEventMouse> me = p_input;
+
+ if (me.is_valid()) {
+ Vector2 point = me->get_position();
+
+ int over_dock_slot = -1;
+ for (int i = 0; i < EditorDockManager::DOCK_SLOT_MAX; i++) {
+ if (dock_select_rects[i].has_point(point)) {
+ over_dock_slot = i;
+ break;
+ }
+ }
+
+ if (over_dock_slot != dock_select_rect_over_idx) {
+ dock_select->queue_redraw();
+ dock_select_rect_over_idx = over_dock_slot;
+ }
+
+ if (over_dock_slot == -1) {
+ return;
+ }
+
+ Ref<InputEventMouseButton> mb = me;
+ TabContainer *target_tab_container = dock_manager->dock_slot[over_dock_slot];
+
+ if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
+ if (dock_manager->get_dock_tab_container(context_dock) != target_tab_container) {
+ dock_manager->_move_dock(context_dock, target_tab_container, target_tab_container->get_tab_count());
+ dock_manager->all_docks[context_dock].dock_slot_index = over_dock_slot;
+ dock_manager->_update_layout();
+ hide();
+ }
+ }
+ }
+}
+
+void DockContextPopup::_dock_select_mouse_exited() {
+ dock_select_rect_over_idx = -1;
+ dock_select->queue_redraw();
+}
+
+void DockContextPopup::_dock_select_draw() {
+ Color used_dock_color = Color(0.6, 0.6, 0.6, 0.8);
+ Color hovered_dock_color = Color(0.8, 0.8, 0.8, 0.8);
+ Color tab_selected_color = dock_select->get_theme_color(SNAME("mono_color"), EditorStringName(Editor));
+ Color tab_unselected_color = used_dock_color;
+ Color unused_dock_color = used_dock_color;
+ unused_dock_color.a = 0.4;
+ Color unusable_dock_color = unused_dock_color;
+ unusable_dock_color.a = 0.1;
+
+ // Update sizes.
+ Size2 dock_size = dock_select->get_size();
+ dock_size.x /= 6.0;
+ dock_size.y /= 2.0;
+
+ Size2 center_panel_size = dock_size * 2.0;
+ Rect2 center_panel_rect(center_panel_size.x, 0, center_panel_size.x, center_panel_size.y);
+
+ if (dock_select->is_layout_rtl()) {
+ dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_UR] = Rect2(Point2(), dock_size);
+ dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_BR] = Rect2(Point2(0, dock_size.y), dock_size);
+ dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_UL] = Rect2(Point2(dock_size.x, 0), dock_size);
+ dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_BL] = Rect2(dock_size, dock_size);
+ dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_UR] = Rect2(Point2(dock_size.x * 4, 0), dock_size);
+ dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_BR] = Rect2(Point2(dock_size.x * 4, dock_size.y), dock_size);
+ dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_UL] = Rect2(Point2(dock_size.x * 5, 0), dock_size);
+ dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_BL] = Rect2(Point2(dock_size.x * 5, dock_size.y), dock_size);
+ } else {
+ dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_UL] = Rect2(Point2(), dock_size);
+ dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_BL] = Rect2(Point2(0, dock_size.y), dock_size);
+ dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_UR] = Rect2(Point2(dock_size.x, 0), dock_size);
+ dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_BR] = Rect2(dock_size, dock_size);
+ dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_UL] = Rect2(Point2(dock_size.x * 4, 0), dock_size);
+ dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_BL] = Rect2(Point2(dock_size.x * 4, dock_size.y), dock_size);
+ dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_UR] = Rect2(Point2(dock_size.x * 5, 0), dock_size);
+ dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_BR] = Rect2(Point2(dock_size.x * 5, dock_size.y), dock_size);
+ }
+
+ int max_tabs = 3;
+ int rtl_dir = dock_select->is_layout_rtl() ? -1 : 1;
+ real_t tab_height = 3.0 * EDSCALE;
+ real_t tab_spacing = 1.0 * EDSCALE;
+ real_t dock_spacing = 2.0 * EDSCALE;
+ real_t dock_top_spacing = tab_height + dock_spacing;
+
+ TabContainer *context_tab_container = dock_manager->get_dock_tab_container(context_dock);
+ int context_tab_index = -1;
+ if (context_tab_container && context_tab_container->get_tab_count() > 0) {
+ context_tab_index = context_tab_container->get_tab_idx_from_control(context_dock);
+ }
+
+ // Draw center panel.
+ Rect2 center_panel_draw_rect = center_panel_rect.grow_individual(-dock_spacing, -dock_top_spacing, -dock_spacing, -dock_spacing);
+ dock_select->draw_rect(center_panel_draw_rect, unusable_dock_color);
+
+ // Draw all dock slots.
+ for (int i = 0; i < EditorDockManager::DOCK_SLOT_MAX; i++) {
+ Rect2 dock_slot_draw_rect = dock_select_rects[i].grow_individual(-dock_spacing, -dock_top_spacing, -dock_spacing, -dock_spacing);
+ real_t tab_width = Math::round(dock_slot_draw_rect.size.width / max_tabs);
+ Rect2 tab_draw_rect = Rect2(dock_slot_draw_rect.position.x, dock_select_rects[i].position.y, tab_width - tab_spacing, tab_height);
+ if (dock_select->is_layout_rtl()) {
+ tab_draw_rect.position.x += dock_slot_draw_rect.size.x - tab_draw_rect.size.x;
+ }
+ bool is_context_dock = context_tab_container == dock_manager->dock_slot[i];
+ int tabs_to_draw = MIN(max_tabs, dock_manager->dock_slot[i]->get_tab_count());
+
+ if (i == dock_select_rect_over_idx) {
+ dock_select->draw_rect(dock_slot_draw_rect, hovered_dock_color);
+ } else if (tabs_to_draw == 0) {
+ dock_select->draw_rect(dock_slot_draw_rect, unused_dock_color);
+ } else {
+ dock_select->draw_rect(dock_slot_draw_rect, used_dock_color);
+ }
+
+ // Draw tabs above each used dock slot.
+ for (int j = 0; j < tabs_to_draw; j++) {
+ Color tab_color = tab_unselected_color;
+ if (is_context_dock && context_tab_index == j) {
+ tab_color = tab_selected_color;
+ }
+ Rect2 tabj_draw_rect = tab_draw_rect;
+ tabj_draw_rect.position.x += tab_width * j * rtl_dir;
+ dock_select->draw_rect(tabj_draw_rect, tab_color);
+ }
+ }
+}
+
+void DockContextPopup::_update_buttons() {
+ TabContainer *context_tab_container = dock_manager->get_dock_tab_container(context_dock);
+ bool dock_at_bottom = dock_manager->_is_dock_at_bottom(context_dock);
+
+ // Update tab move buttons.
+ tab_move_left_button->set_disabled(true);
+ tab_move_right_button->set_disabled(true);
+ if (!dock_at_bottom && context_tab_container && context_tab_container->get_tab_count() > 0) {
+ int context_tab_index = context_tab_container->get_tab_idx_from_control(context_dock);
+ tab_move_left_button->set_disabled(context_tab_index == 0);
+ tab_move_right_button->set_disabled(context_tab_index >= context_tab_container->get_tab_count() - 1);
+ }
+
+ dock_to_bottom_button->set_visible(!dock_at_bottom && bool(context_dock->call("_can_dock_horizontal")));
+ reset_size();
+}
+
+void DockContextPopup::select_current_dock_in_dock_slot(int p_dock_slot) {
+ context_dock = dock_manager->dock_slot[p_dock_slot]->get_current_tab_control();
+ _update_buttons();
+}
+
+void DockContextPopup::set_dock(Control *p_dock) {
+ context_dock = p_dock;
+ _update_buttons();
+}
+
+Control *DockContextPopup::get_dock() const {
+ return context_dock;
+}
+
+void DockContextPopup::docks_updated() {
+ if (!is_visible()) {
+ return;
+ }
+ _update_buttons();
+}
+
+DockContextPopup::DockContextPopup() {
+ dock_manager = EditorDockManager::get_singleton();
+
+ dock_select_popup_vb = memnew(VBoxContainer);
+ add_child(dock_select_popup_vb);
+
+ HBoxContainer *header_hb = memnew(HBoxContainer);
+ tab_move_left_button = memnew(Button);
+ tab_move_left_button->set_flat(true);
+ tab_move_left_button->set_focus_mode(Control::FOCUS_NONE);
+ tab_move_left_button->connect("pressed", callable_mp(this, &DockContextPopup::_tab_move_left));
+ header_hb->add_child(tab_move_left_button);
+
+ Label *position_label = memnew(Label);
+ position_label->set_text(TTR("Dock Position"));
+ position_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ position_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
+ header_hb->add_child(position_label);
+
+ tab_move_right_button = memnew(Button);
+ tab_move_right_button->set_flat(true);
+ tab_move_right_button->set_focus_mode(Control::FOCUS_NONE);
+ tab_move_right_button->connect("pressed", callable_mp(this, &DockContextPopup::_tab_move_right));
+
+ header_hb->add_child(tab_move_right_button);
+ dock_select_popup_vb->add_child(header_hb);
dock_select = memnew(Control);
dock_select->set_custom_minimum_size(Size2(128, 64) * EDSCALE);
- dock_select->connect("gui_input", callable_mp(this, &EditorDockManager::_dock_select_input));
- dock_select->connect("draw", callable_mp(this, &EditorDockManager::_dock_select_draw));
- dock_select->connect("mouse_exited", callable_mp(this, &EditorDockManager::_dock_popup_exit));
+ dock_select->connect("gui_input", callable_mp(this, &DockContextPopup::_dock_select_input));
+ dock_select->connect("draw", callable_mp(this, &DockContextPopup::_dock_select_draw));
+ dock_select->connect("mouse_exited", callable_mp(this, &DockContextPopup::_dock_select_mouse_exited));
dock_select->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- dock_vb->add_child(dock_select);
+ dock_select_popup_vb->add_child(dock_select);
- dock_float = memnew(Button);
- dock_float->set_text(TTR("Make Floating"));
+ make_float_button = memnew(Button);
+ make_float_button->set_text(TTR("Make Floating"));
if (!EditorNode::get_singleton()->is_multi_window_enabled()) {
- dock_float->set_disabled(true);
- dock_float->set_tooltip_text(EditorNode::get_singleton()->get_multiwindow_support_tooltip_text());
+ make_float_button->set_disabled(true);
+ make_float_button->set_tooltip_text(EditorNode::get_singleton()->get_multiwindow_support_tooltip_text());
} else {
- dock_float->set_tooltip_text(TTR("Make this dock floating."));
- }
- dock_float->set_focus_mode(Control::FOCUS_NONE);
- dock_float->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- dock_float->connect("pressed", callable_mp(this, &EditorDockManager::_dock_make_selected_float));
- dock_vb->add_child(dock_float);
-
- dock_to_bottom = memnew(Button);
- dock_to_bottom->set_text(TTR("Move to Bottom"));
- dock_to_bottom->set_focus_mode(Control::FOCUS_NONE);
- dock_to_bottom->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- dock_to_bottom->connect("pressed", callable_mp(this, &EditorDockManager::_dock_move_selected_to_bottom));
- dock_to_bottom->hide();
- dock_vb->add_child(dock_to_bottom);
-
- dock_select_popup->reset_size();
+ make_float_button->set_tooltip_text(TTR("Make this dock floating."));
+ }
+ make_float_button->set_focus_mode(Control::FOCUS_NONE);
+ make_float_button->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ make_float_button->connect("pressed", callable_mp(this, &DockContextPopup::_float_dock));
+ dock_select_popup_vb->add_child(make_float_button);
+
+ dock_to_bottom_button = memnew(Button);
+ dock_to_bottom_button->set_text(TTR("Move to Bottom"));
+ dock_to_bottom_button->set_tooltip_text(TTR("Move this dock to the bottom panel."));
+ dock_to_bottom_button->set_focus_mode(Control::FOCUS_NONE);
+ dock_to_bottom_button->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ dock_to_bottom_button->connect("pressed", callable_mp(this, &DockContextPopup::_move_dock_to_bottom));
+ dock_to_bottom_button->hide();
+ dock_select_popup_vb->add_child(dock_to_bottom_button);
}
diff --git a/editor/editor_dock_manager.h b/editor/editor_dock_manager.h
index 193ccd6541..370c149967 100644
--- a/editor/editor_dock_manager.h
+++ b/editor/editor_dock_manager.h
@@ -31,13 +31,15 @@
#ifndef EDITOR_DOCK_MANAGER_H
#define EDITOR_DOCK_MANAGER_H
+#include "scene/gui/popup.h"
#include "scene/gui/split_container.h"
class Button;
class ConfigFile;
class Control;
-class PopupPanel;
+class PopupMenu;
class TabContainer;
+class VBoxContainer;
class WindowWrapper;
class DockSplitContainer : public SplitContainer {
@@ -53,6 +55,8 @@ protected:
virtual void remove_child_notify(Node *p_child) override;
};
+class DockContextPopup;
+
class EditorDockManager : public Object {
GDCLASS(EditorDockManager, Object);
@@ -70,49 +74,51 @@ public:
};
private:
+ friend class DockContextPopup;
+
+ struct DockInfo {
+ String title;
+ bool open = false;
+ bool enabled = true;
+ bool at_bottom = false;
+ int previous_tab_index = -1;
+ bool previous_at_bottom = false;
+ WindowWrapper *dock_window = nullptr;
+ int dock_slot_index = DOCK_SLOT_LEFT_UL;
+ Ref<Shortcut> shortcut;
+ };
+
static EditorDockManager *singleton;
// To access splits easily by index.
Vector<DockSplitContainer *> vsplits;
Vector<DockSplitContainer *> hsplits;
- Vector<WindowWrapper *> floating_docks;
- Vector<Control *> bottom_docks;
+ Vector<WindowWrapper *> dock_windows;
TabContainer *dock_slot[DOCK_SLOT_MAX];
+ HashMap<Control *, DockInfo> all_docks;
bool docks_visible = true;
- PopupPanel *dock_select_popup = nullptr;
- Button *dock_float = nullptr;
- Button *dock_to_bottom = nullptr;
- Button *dock_tab_move_left = nullptr;
- Button *dock_tab_move_right = nullptr;
- Control *dock_select = nullptr;
- Rect2 dock_select_rect[DOCK_SLOT_MAX];
- int dock_select_rect_over_idx = -1;
- int dock_popup_selected_idx = -1;
- int dock_bottom_selected_idx = -1;
-
- void _dock_select_popup_theme_changed();
- void _dock_popup_exit();
- void _dock_pre_popup(int p_dock_slot);
- void _dock_move_left();
- void _dock_move_right();
- void _dock_select_input(const Ref<InputEvent> &p_input);
- void _dock_select_draw();
- void _dock_split_dragged(int p_offset);
+ DockContextPopup *dock_context_popup = nullptr;
+ Control *closed_dock_parent = nullptr;
- void _dock_tab_changed(int p_tab);
- void _edit_current();
+ void _dock_split_dragged(int p_offset);
+ void _dock_container_gui_input(const Ref<InputEvent> &p_input, TabContainer *p_dock_container);
+ void _bottom_dock_button_gui_input(const Ref<InputEvent> &p_input, Control *p_dock, Button *p_bottom_button);
+ void _dock_container_update_visibility(TabContainer *p_dock_container);
+ void _update_layout();
- void _dock_floating_close_request(WindowWrapper *p_wrapper);
- void _dock_make_selected_float();
- void _dock_make_float(Control *p_control, int p_slot_index, bool p_show_window = true);
- void _restore_floating_dock(const Dictionary &p_dock_dump, Control *p_wrapper, int p_slot_index);
+ void _window_close_request(WindowWrapper *p_wrapper);
+ Control *_close_window(WindowWrapper *p_wrapper);
+ void _open_dock_in_window(Control *p_dock, bool p_show_window = true);
+ void _restore_dock_to_saved_window(Control *p_dock, const Dictionary &p_window_dump);
- void _dock_move_selected_to_bottom();
+ void _dock_move_to_bottom(Control *p_dock);
+ void _dock_remove_from_bottom(Control *p_dock);
+ bool _is_dock_at_bottom(Control *p_dock);
-protected:
- static void _bind_methods();
+ void _move_dock_tab_index(Control *p_dock, int p_tab_index, bool p_set_current);
+ void _move_dock(Control *p_dock, Control *p_target, int p_tab_index = -1, bool p_set_current = true);
public:
static EditorDockManager *get_singleton() { return singleton; }
@@ -124,19 +130,64 @@ public:
void save_docks_to_config(Ref<ConfigFile> p_layout, const String &p_section) const;
void load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section);
- void update_dock_slots_visibility(bool p_keep_selected_tabs = false);
- void bottom_dock_show_placement_popup(const Rect2i &p_position, Control *p_dock);
+ void set_dock_enabled(Control *p_dock, bool p_enabled);
+ void close_dock(Control *p_dock);
+ void open_dock(Control *p_dock, bool p_set_current = true);
+ void focus_dock(Control *p_dock);
+
+ TabContainer *get_dock_tab_container(Control *p_dock) const;
- void close_all_floating_docks();
+ void bottom_dock_show_placement_popup(const Rect2i &p_position, Control *p_dock);
void set_docks_visible(bool p_show);
bool are_docks_visible() const;
- void add_control_to_dock(DockSlot p_slot, Control *p_control, const String &p_name = "", const Ref<Shortcut> &p_shortcut = nullptr);
- void remove_control_from_dock(Control *p_control);
+ void add_control_to_dock(DockSlot p_slot, Control *p_dock, const String &p_title = "", const Ref<Shortcut> &p_shortcut = nullptr);
+ void remove_control_from_dock(Control *p_dock);
EditorDockManager();
};
+class DockContextPopup : public PopupPanel {
+ GDCLASS(DockContextPopup, PopupPanel);
+
+ VBoxContainer *dock_select_popup_vb = nullptr;
+
+ Button *make_float_button = nullptr;
+ Button *tab_move_left_button = nullptr;
+ Button *tab_move_right_button = nullptr;
+ Button *dock_to_bottom_button = nullptr;
+
+ Control *dock_select = nullptr;
+ Rect2 dock_select_rects[EditorDockManager::DOCK_SLOT_MAX];
+ int dock_select_rect_over_idx = -1;
+
+ Control *context_dock = nullptr;
+
+ EditorDockManager *dock_manager = nullptr;
+
+ void _tab_move_left();
+ void _tab_move_right();
+ void _float_dock();
+ void _move_dock_to_bottom();
+
+ void _dock_select_input(const Ref<InputEvent> &p_input);
+ void _dock_select_mouse_exited();
+ void _dock_select_draw();
+
+ void _update_buttons();
+
+protected:
+ void _notification(int p_what);
+
+public:
+ void select_current_dock_in_dock_slot(int p_dock_slot);
+ void set_dock(Control *p_dock);
+ Control *get_dock() const;
+ void docks_updated();
+
+ DockContextPopup();
+};
+
#endif // EDITOR_DOCK_MANAGER_H
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index ff42b82435..516b8f3d74 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -50,10 +50,6 @@ EditorFileSystem *EditorFileSystem::singleton = nullptr;
//the name is the version, to keep compatibility with different versions of Godot
#define CACHE_FILE_NAME "filesystem_cache8"
-void EditorFileSystemDirectory::sort_files() {
- files.sort_custom<FileInfoSort>();
-}
-
int EditorFileSystemDirectory::find_file_index(const String &p_file) const {
for (int i = 0; i < files.size(); i++) {
if (files[i]->file == p_file) {
@@ -578,7 +574,7 @@ bool EditorFileSystem::_update_scan_actions() {
case ItemAction::ACTION_DIR_ADD: {
int idx = 0;
for (int i = 0; i < ia.dir->subdirs.size(); i++) {
- if (ia.new_dir->name.naturalnocasecmp_to(ia.dir->subdirs[i]->name) < 0) {
+ if (ia.new_dir->name.filenocasecmp_to(ia.dir->subdirs[i]->name) < 0) {
break;
}
idx++;
@@ -600,7 +596,7 @@ bool EditorFileSystem::_update_scan_actions() {
case ItemAction::ACTION_FILE_ADD: {
int idx = 0;
for (int i = 0; i < ia.dir->files.size(); i++) {
- if (ia.new_file->file.naturalnocasecmp_to(ia.dir->files[i]->file) < 0) {
+ if (ia.new_file->file.filenocasecmp_to(ia.dir->files[i]->file) < 0) {
break;
}
idx++;
@@ -810,8 +806,8 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc
da->list_dir_end();
- dirs.sort_custom<NaturalNoCaseComparator>();
- files.sort_custom<NaturalNoCaseComparator>();
+ dirs.sort_custom<FileNoCaseComparator>();
+ files.sort_custom<FileNoCaseComparator>();
int total = dirs.size() + files.size();
int idx = 0;
@@ -832,7 +828,7 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc
int idx2 = 0;
for (int i = 0; i < p_dir->subdirs.size(); i++) {
- if (efd->name.naturalnocasecmp_to(p_dir->subdirs[i]->name) < 0) {
+ if (efd->name.filenocasecmp_to(p_dir->subdirs[i]->name) < 0) {
break;
}
idx2++;
@@ -1409,7 +1405,7 @@ bool EditorFileSystem::_find_file(const String &p_file, EditorFileSystemDirector
int idx2 = 0;
for (int j = 0; j < fs->get_subdir_count(); j++) {
- if (efsd->name.naturalnocasecmp_to(fs->get_subdir(j)->get_name()) < 0) {
+ if (efsd->name.filenocasecmp_to(fs->get_subdir(j)->get_name()) < 0) {
break;
}
idx2++;
@@ -1766,7 +1762,7 @@ void EditorFileSystem::update_file(const String &p_file) {
String file_name = p_file.get_file();
for (int i = 0; i < fs->files.size(); i++) {
- if (p_file.naturalnocasecmp_to(fs->files[i]->file) < 0) {
+ if (p_file.filenocasecmp_to(fs->files[i]->file) < 0) {
break;
}
idx++;
diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h
index 818d725281..e45d6f8be3 100644
--- a/editor/editor_file_system.h
+++ b/editor/editor_file_system.h
@@ -68,14 +68,6 @@ class EditorFileSystemDirectory : public Object {
String script_class_icon_path;
};
- struct FileInfoSort {
- bool operator()(const FileInfo *p_a, const FileInfo *p_b) const {
- return p_a->file < p_b->file;
- }
- };
-
- void sort_files();
-
Vector<FileInfo *> files;
static void _bind_methods();
diff --git a/editor/editor_help_search.h b/editor/editor_help_search.h
index 003b98adf0..39ffc2f71b 100644
--- a/editor/editor_help_search.h
+++ b/editor/editor_help_search.h
@@ -117,7 +117,7 @@ class EditorHelpSearch::Runner : public RefCounted {
};
int phase = 0;
- template <class T>
+ template <typename T>
struct MemberMatch {
T *doc = nullptr;
bool name = false;
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index a14c6e8462..ff2b305a46 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -86,8 +86,7 @@ Size2 EditorProperty::get_minimum_size() const {
}
Size2 minsize = c->get_combined_minimum_size();
- ms.width = MAX(ms.width, minsize.width);
- ms.height = MAX(ms.height, minsize.height);
+ ms = ms.max(minsize);
}
if (keying) {
@@ -1463,8 +1462,7 @@ Size2 EditorInspectorSection::get_minimum_size() const {
continue;
}
Size2 minsize = c->get_combined_minimum_size();
- ms.width = MAX(ms.width, minsize.width);
- ms.height = MAX(ms.height, minsize.height);
+ ms = ms.max(minsize);
}
Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Tree"));
diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp
index c0c26bbfeb..667405ff10 100644
--- a/editor/editor_log.cpp
+++ b/editor/editor_log.cpp
@@ -435,8 +435,7 @@ EditorLog::EditorLog() {
clear_button = memnew(Button);
clear_button->set_theme_type_variation("FlatButton");
clear_button->set_focus_mode(FOCUS_NONE);
- clear_button->set_shortcut(ED_SHORTCUT("editor/clear_output", TTR("Clear Output"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::K));
- clear_button->set_shortcut_context(this);
+ clear_button->set_shortcut(ED_SHORTCUT("editor/clear_output", TTR("Clear Output"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::K));
clear_button->connect("pressed", callable_mp(this, &EditorLog::_clear_request));
hb_tools->add_child(clear_button);
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 89e5e3f3b0..edb6fee75c 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -517,9 +517,8 @@ void EditorNode::_update_theme(bool p_skip_creation) {
scene_root_parent->add_theme_style_override("panel", theme->get_stylebox(SNAME("Content"), EditorStringName(EditorStyles)));
bottom_panel->add_theme_style_override("panel", theme->get_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
- main_menu->add_theme_style_override("pressed", theme->get_stylebox(SNAME("MenuTransparent"), EditorStringName(EditorStyles)));
distraction_free->set_icon(theme->get_icon(SNAME("DistractionFree"), EditorStringName(EditorIcons)));
- distraction_free->add_theme_style_override("pressed", theme->get_stylebox(SNAME("MenuTransparent"), EditorStringName(EditorStyles)));
+ distraction_free->add_theme_style_override("pressed", theme->get_stylebox("normal", "FlatMenuButton"));
help_menu->set_item_icon(help_menu->get_item_index(HELP_SEARCH), theme->get_icon(SNAME("HelpSearch"), EditorStringName(EditorIcons)));
help_menu->set_item_icon(help_menu->get_item_index(HELP_COPY_SYSTEM_INFO), theme->get_icon(SNAME("ActionCopy"), EditorStringName(EditorIcons)));
@@ -3790,14 +3789,6 @@ void EditorNode::_set_current_scene_nocheck(int p_idx) {
editor_folding.save_scene_folding(editor_data.get_edited_scene_root(p_idx), editor_data.get_scene_path(p_idx));
}
- if (editor_data.check_and_update_scene(p_idx)) {
- if (editor_data.get_scene_path(p_idx) != "") {
- editor_folding.load_scene_folding(editor_data.get_edited_scene_root(p_idx), editor_data.get_scene_path(p_idx));
- }
-
- EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_scene_history_id(p_idx));
- }
-
changing_scene = true;
editor_data.save_edited_scene_state(editor_selection, &editor_history, _get_main_scene_state());
@@ -3836,6 +3827,14 @@ void EditorNode::_set_current_scene_nocheck(int p_idx) {
if (tabs_to_close.is_empty()) {
callable_mp(this, &EditorNode::_set_main_scene_state).call_deferred(state, get_edited_scene()); // Do after everything else is done setting up.
}
+
+ if (editor_data.check_and_update_scene(p_idx)) {
+ if (!editor_data.get_scene_path(p_idx).is_empty()) {
+ editor_folding.load_scene_folding(editor_data.get_edited_scene_root(p_idx), editor_data.get_scene_path(p_idx));
+ }
+
+ EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_scene_history_id(p_idx));
+ }
}
void EditorNode::setup_color_picker(ColorPicker *p_picker) {
@@ -6063,19 +6062,13 @@ void EditorNode::_resource_loaded(Ref<Resource> p_resource, const String &p_path
void EditorNode::_feature_profile_changed() {
Ref<EditorFeatureProfile> profile = feature_profile_manager->get_current_profile();
- // FIXME: Close all floating docks to avoid crash.
- editor_dock_manager->close_all_floating_docks();
- TabContainer *import_tabs = cast_to<TabContainer>(ImportDock::get_singleton()->get_parent());
- TabContainer *node_tabs = cast_to<TabContainer>(NodeDock::get_singleton()->get_parent());
- TabContainer *fs_tabs = cast_to<TabContainer>(FileSystemDock::get_singleton()->get_parent());
- TabContainer *history_tabs = cast_to<TabContainer>(history_dock->get_parent());
if (profile.is_valid()) {
- node_tabs->set_tab_hidden(node_tabs->get_tab_idx_from_control(NodeDock::get_singleton()), profile->is_feature_disabled(EditorFeatureProfile::FEATURE_NODE_DOCK));
+ editor_dock_manager->set_dock_enabled(NodeDock::get_singleton(), !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_NODE_DOCK));
// The Import dock is useless without the FileSystem dock. Ensure the configuration is valid.
bool fs_dock_disabled = profile->is_feature_disabled(EditorFeatureProfile::FEATURE_FILESYSTEM_DOCK);
- fs_tabs->set_tab_hidden(fs_tabs->get_tab_idx_from_control(FileSystemDock::get_singleton()), fs_dock_disabled);
- import_tabs->set_tab_hidden(import_tabs->get_tab_idx_from_control(ImportDock::get_singleton()), fs_dock_disabled || profile->is_feature_disabled(EditorFeatureProfile::FEATURE_IMPORT_DOCK));
- history_tabs->set_tab_hidden(history_tabs->get_tab_idx_from_control(history_dock), profile->is_feature_disabled(EditorFeatureProfile::FEATURE_HISTORY_DOCK));
+ editor_dock_manager->set_dock_enabled(FileSystemDock::get_singleton(), !fs_dock_disabled);
+ editor_dock_manager->set_dock_enabled(ImportDock::get_singleton(), !fs_dock_disabled && !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_IMPORT_DOCK));
+ editor_dock_manager->set_dock_enabled(history_dock, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_HISTORY_DOCK));
main_editor_buttons[EDITOR_3D]->set_visible(!profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D));
main_editor_buttons[EDITOR_SCRIPT]->set_visible(!profile->is_feature_disabled(EditorFeatureProfile::FEATURE_SCRIPT));
@@ -6088,18 +6081,16 @@ void EditorNode::_feature_profile_changed() {
editor_select(EDITOR_2D);
}
} else {
- import_tabs->set_tab_hidden(import_tabs->get_tab_idx_from_control(ImportDock::get_singleton()), false);
- node_tabs->set_tab_hidden(node_tabs->get_tab_idx_from_control(NodeDock::get_singleton()), false);
- fs_tabs->set_tab_hidden(fs_tabs->get_tab_idx_from_control(FileSystemDock::get_singleton()), false);
- history_tabs->set_tab_hidden(history_tabs->get_tab_idx_from_control(history_dock), false);
+ editor_dock_manager->set_dock_enabled(ImportDock::get_singleton(), true);
+ editor_dock_manager->set_dock_enabled(NodeDock::get_singleton(), true);
+ editor_dock_manager->set_dock_enabled(FileSystemDock::get_singleton(), true);
+ editor_dock_manager->set_dock_enabled(history_dock, true);
main_editor_buttons[EDITOR_3D]->set_visible(true);
main_editor_buttons[EDITOR_SCRIPT]->set_visible(true);
if (AssetLibraryEditorPlugin::is_available()) {
main_editor_buttons[EDITOR_ASSETLIB]->set_visible(true);
}
}
-
- editor_dock_manager->update_dock_slots_visibility();
}
void EditorNode::_bind_methods() {
@@ -6558,7 +6549,6 @@ EditorNode::EditorNode() {
right_r_vsplit->add_child(dock_slot[EditorDockManager::DOCK_SLOT_RIGHT_BR]);
editor_dock_manager = memnew(EditorDockManager);
- editor_dock_manager->connect("layout_changed", callable_mp(this, &EditorNode::_save_editor_layout));
// Save the splits for easier access.
editor_dock_manager->add_vsplit(left_l_vsplit);
@@ -6638,7 +6628,7 @@ EditorNode::EditorNode() {
main_menu = memnew(MenuBar);
title_bar->add_child(main_menu);
- main_menu->set_theme_type_variation("FlatMenuButton");
+ main_menu->set_theme_type_variation("MainMenuBar");
main_menu->set_start_index(0); // Main menu, add to the start of global menu.
main_menu->set_prefer_global_menu(global_menu);
main_menu->set_switch_on_hover(true);
@@ -6755,7 +6745,7 @@ EditorNode::EditorNode() {
ED_SHORTCUT_AND_COMMAND("editor/editor_settings", TTR("Editor Settings..."));
ED_SHORTCUT_OVERRIDE("editor/editor_settings", "macos", KeyModifierMask::META + Key::COMMA);
#ifdef MACOS_ENABLED
- if (global_menu) {
+ if (global_menu && NativeMenu::get_singleton()->has_system_menu(NativeMenu::APPLICATION_MENU_ID)) {
apple_menu = memnew(PopupMenu);
apple_menu->set_system_menu(NativeMenu::APPLICATION_MENU_ID);
main_menu->add_child(apple_menu);
@@ -6880,7 +6870,9 @@ EditorNode::EditorNode() {
help_menu = memnew(PopupMenu);
help_menu->set_name(TTR("Help"));
- help_menu->set_system_menu(NativeMenu::HELP_MENU_ID);
+ if (global_menu && NativeMenu::get_singleton()->has_system_menu(NativeMenu::HELP_MENU_ID)) {
+ help_menu->set_system_menu(NativeMenu::HELP_MENU_ID);
+ }
main_menu->add_child(help_menu);
help_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h
index 7c28818b2f..f45a512b89 100644
--- a/editor/editor_plugin.h
+++ b/editor/editor_plugin.h
@@ -264,7 +264,7 @@ class EditorPlugins {
static EditorPluginCreateFunc creation_funcs[MAX_CREATE_FUNCS];
static int creation_func_count;
- template <class T>
+ template <typename T>
static EditorPlugin *creator() {
return memnew(T);
}
@@ -276,7 +276,7 @@ public:
return creation_funcs[p_idx]();
}
- template <class T>
+ template <typename T>
static void add_by_type() {
add_create_func(creator<T>);
}
diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp
index 8a15f4912a..a6c7d6b617 100644
--- a/editor/editor_properties_array_dict.cpp
+++ b/editor/editor_properties_array_dict.cpp
@@ -111,6 +111,7 @@ bool EditorPropertyDictionaryObject::_set(const StringName &p_name, const Varian
}
if (name.begins_with("indices")) {
+ dict = dict.duplicate();
int index = name.get_slicec('/', 1).to_int();
Variant key = dict.get_key_at_index(index);
dict[key] = p_value;
@@ -171,6 +172,31 @@ Variant EditorPropertyDictionaryObject::get_new_item_value() {
return new_item_value;
}
+String EditorPropertyDictionaryObject::get_property_name_for_index(int p_index) {
+ switch (p_index) {
+ case NEW_KEY_INDEX:
+ return "new_item_key";
+ case NEW_VALUE_INDEX:
+ return "new_item_value";
+ default:
+ return "indices/" + itos(p_index);
+ }
+}
+
+String EditorPropertyDictionaryObject::get_label_for_index(int p_index) {
+ switch (p_index) {
+ case NEW_KEY_INDEX:
+ return TTR("New Key:");
+ break;
+ case NEW_VALUE_INDEX:
+ return TTR("New Value:");
+ break;
+ default:
+ return dict.get_key_at_index(p_index).get_construct_string();
+ break;
+ }
+}
+
EditorPropertyDictionaryObject::EditorPropertyDictionaryObject() {
}
@@ -230,15 +256,14 @@ void EditorPropertyArray::_change_type_menu(int p_index) {
Variant array = object->get_array().duplicate();
array.set(slots[changing_type_index].index, value);
- emit_changed(get_edited_property(), array, "", true);
- update_property();
+ emit_changed(get_edited_property(), array);
}
void EditorPropertyArray::_object_id_selected(const StringName &p_property, ObjectID p_id) {
emit_signal(SNAME("object_id_selected"), p_property, p_id);
}
-void EditorPropertyArray::create_new_property_slot() {
+void EditorPropertyArray::_create_new_property_slot() {
int idx = slots.size();
HBoxContainer *hbox = memnew(HBoxContainer);
@@ -365,7 +390,7 @@ void EditorPropertyArray::update_property() {
vbox->add_child(paginator);
for (int i = 0; i < page_length; i++) {
- create_new_property_slot();
+ _create_new_property_slot();
}
}
@@ -434,8 +459,7 @@ void EditorPropertyArray::_remove_pressed(int p_slot_index) {
Variant array = object->get_array().duplicate();
array.call("remove_at", slots[p_slot_index].index);
- emit_changed(get_edited_property(), array, "", false);
- update_property();
+ emit_changed(get_edited_property(), array);
}
void EditorPropertyArray::_button_draw() {
@@ -514,8 +538,7 @@ void EditorPropertyArray::drop_data_fw(const Point2 &p_point, const Variant &p_d
}
}
- emit_changed(get_edited_property(), array, "", false);
- update_property();
+ emit_changed(get_edited_property(), array);
}
}
@@ -607,8 +630,7 @@ void EditorPropertyArray::_length_changed(double p_page) {
Variant array = object->get_array().duplicate();
array.call("resize", int(p_page));
- emit_changed(get_edited_property(), array, "", false);
- update_property();
+ emit_changed(get_edited_property(), array);
}
void EditorPropertyArray::_add_element() {
@@ -700,8 +722,7 @@ void EditorPropertyArray::_reorder_button_up() {
array.call("remove_at", reorder_slot.index);
array.call("insert", reorder_to_index, value_to_move);
- reorder_slot.index = reorder_slot.index % page_length + page_index * page_length;
- emit_changed(get_edited_property(), array, "", false);
+ emit_changed(get_edited_property(), array);
}
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
@@ -748,31 +769,19 @@ void EditorPropertyDictionary::_property_changed(const String &p_property, Varia
p_value = Variant(); // `EditorResourcePicker` resets to `Ref<Resource>()`. See GH-82716.
}
- if (p_property == "new_item_key") {
- object->set_new_item_key(p_value);
- } else if (p_property == "new_item_value") {
- object->set_new_item_value(p_value);
- } else if (p_property.begins_with("indices")) {
- int index = p_property.get_slice("/", 1).to_int();
-
- Dictionary dict = object->get_dict().duplicate();
- Variant key = dict.get_key_at_index(index);
- dict[key] = p_value;
-
- object->set_dict(dict);
- emit_changed(get_edited_property(), dict, "", true);
- }
+ object->set(p_property, p_value);
+ emit_changed(get_edited_property(), object->get_dict(), p_name, p_changing);
}
-void EditorPropertyDictionary::_change_type(Object *p_button, int p_index) {
+void EditorPropertyDictionary::_change_type(Object *p_button, int p_slot_index) {
Button *button = Object::cast_to<Button>(p_button);
-
+ int index = slots[p_slot_index].index;
Rect2 rect = button->get_screen_rect();
- change_type->set_item_disabled(change_type->get_item_index(Variant::VARIANT_MAX), p_index < 0);
+ change_type->set_item_disabled(change_type->get_item_index(Variant::VARIANT_MAX), index < 0);
change_type->reset_size();
change_type->set_position(rect.get_end() - Vector2(change_type->get_contents_minimum_size().x, 0));
change_type->popup();
- changing_type_index = p_index;
+ changing_type_index = index;
}
void EditorPropertyDictionary::_add_key_value() {
@@ -796,36 +805,59 @@ void EditorPropertyDictionary::_add_key_value() {
VariantInternal::initialize(&new_value, type);
object->set_new_item_value(new_value);
- emit_changed(get_edited_property(), dict, "", false);
- update_property();
+ emit_changed(get_edited_property(), dict);
}
-void EditorPropertyDictionary::_change_type_menu(int p_index) {
- if (changing_type_index < 0) {
- Variant value;
- VariantInternal::initialize(&value, Variant::Type(p_index));
- if (changing_type_index == -1) {
- object->set_new_item_key(value);
- } else {
- object->set_new_item_value(value);
- }
- update_property();
- return;
- }
+void EditorPropertyDictionary::_create_new_property_slot(int p_idx) {
+ HBoxContainer *hbox = memnew(HBoxContainer);
+ EditorProperty *prop = memnew(EditorPropertyNil);
+ hbox->add_child(prop);
- Dictionary dict = object->get_dict().duplicate();
- if (p_index < Variant::VARIANT_MAX) {
- Variant value;
- VariantInternal::initialize(&value, Variant::Type(p_index));
- Variant key = dict.get_key_at_index(changing_type_index);
- dict[key] = value;
+ Button *edit_btn = memnew(Button);
+ edit_btn->set_icon(get_editor_theme_icon(SNAME("Edit")));
+ edit_btn->set_disabled(is_read_only());
+ edit_btn->connect("pressed", callable_mp(this, &EditorPropertyDictionary::_change_type).bind(edit_btn, slots.size()));
+ hbox->add_child(edit_btn);
+ if (add_panel) {
+ add_panel->get_child(0)->add_child(hbox);
} else {
- Variant key = dict.get_key_at_index(changing_type_index);
- dict.erase(key);
+ property_vbox->add_child(hbox);
}
+ Slot slot;
+ slot.prop = prop;
+ slot.object = object;
+ slot.container = hbox;
+ int index = p_idx + (p_idx >= 0 ? page_index * page_length : 0);
+ slot.set_index(index);
+ slots.push_back(slot);
+}
- emit_changed(get_edited_property(), dict, "", false);
- update_property();
+void EditorPropertyDictionary::_change_type_menu(int p_index) {
+ Variant value;
+ switch (changing_type_index) {
+ case EditorPropertyDictionaryObject::NEW_KEY_INDEX:
+ case EditorPropertyDictionaryObject::NEW_VALUE_INDEX:
+ VariantInternal::initialize(&value, Variant::Type(p_index));
+ if (changing_type_index == EditorPropertyDictionaryObject::NEW_KEY_INDEX) {
+ object->set_new_item_key(value);
+ } else {
+ object->set_new_item_value(value);
+ }
+ update_property();
+ break;
+
+ default:
+ Dictionary dict = object->get_dict().duplicate();
+ Variant key = dict.get_key_at_index(changing_type_index);
+ if (p_index < Variant::VARIANT_MAX) {
+ VariantInternal::initialize(&value, Variant::Type(p_index));
+ dict[key] = value;
+ } else {
+ dict.erase(key);
+ }
+
+ emit_changed(get_edited_property(), dict);
+ }
}
void EditorPropertyDictionary::setup(PropertyHint p_hint) {
@@ -877,331 +909,72 @@ void EditorPropertyDictionary::update_property() {
paginator = memnew(EditorPaginator);
paginator->connect("page_changed", callable_mp(this, &EditorPropertyDictionary::_page_changed));
vbox->add_child(paginator);
- } else {
- // Queue children for deletion, deleting immediately might cause errors.
- for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) {
- property_vbox->get_child(i)->queue_free();
+
+ for (int i = 0; i < page_length; i++) {
+ _create_new_property_slot(slots.size());
}
+
+ add_panel = memnew(PanelContainer);
+ property_vbox->add_child(add_panel);
+ add_panel->add_theme_style_override(SNAME("panel"), get_theme_stylebox(SNAME("DictionaryAddItem"), EditorStringName(EditorStyles)));
+ VBoxContainer *add_vbox = memnew(VBoxContainer);
+ add_panel->add_child(add_vbox);
+
+ _create_new_property_slot(EditorPropertyDictionaryObject::NEW_KEY_INDEX);
+ _create_new_property_slot(EditorPropertyDictionaryObject::NEW_VALUE_INDEX);
+
+ button_add_item = EditorInspector::create_inspector_action_button(TTR("Add Key/Value Pair"));
+ button_add_item->set_icon(get_theme_icon(SNAME("Add"), EditorStringName(EditorIcons)));
+ button_add_item->set_disabled(is_read_only());
+ button_add_item->connect("pressed", callable_mp(this, &EditorPropertyDictionary::_add_key_value));
+ add_vbox->add_child(button_add_item);
}
int size = dict.size();
int max_page = MAX(0, size - 1) / page_length;
- page_index = MIN(page_index, max_page);
+ if (page_index > max_page) {
+ _page_changed(max_page);
+ }
paginator->update(page_index, max_page);
paginator->set_visible(max_page > 0);
- int offset = page_index * page_length;
-
- int amount = MIN(size - offset, page_length);
- int total_amount = page_index == max_page ? amount + 2 : amount; // For the "Add Key/Value Pair" box on last page.
-
- VBoxContainer *add_vbox = nullptr;
- double default_float_step = EDITOR_GET("interface/inspector/default_float_step");
-
- for (int i = 0; i < total_amount; i++) {
- String prop_name;
- Variant key;
- Variant value;
+ add_panel->set_visible(page_index == max_page);
- if (i < amount) {
- prop_name = "indices/" + itos(i + offset);
- key = dict.get_key_at_index(i + offset);
- value = dict.get_value_at_index(i + offset);
- } else if (i == amount) {
- prop_name = "new_item_key";
- value = object->get_new_item_key();
- } else if (i == amount + 1) {
- prop_name = "new_item_value";
- value = object->get_new_item_value();
+ for (Slot &slot : slots) {
+ bool slot_visible = slot.index < size;
+ slot.container->set_visible(slot_visible);
+ // If not visible no need to update it.
+ if (!slot_visible) {
+ continue;
}
+ Variant value = object->get(slot.prop_name);
+ Variant::Type value_type = value.get_type();
- EditorProperty *prop = nullptr;
-
- switch (value.get_type()) {
- case Variant::NIL: {
- prop = memnew(EditorPropertyNil);
-
- } break;
-
- // Atomic types.
- case Variant::BOOL: {
- prop = memnew(EditorPropertyCheck);
-
- } break;
- case Variant::INT: {
- EditorPropertyInteger *editor = memnew(EditorPropertyInteger);
- editor->setup(-100000, 100000, 1, false, true, true);
- prop = editor;
-
- } break;
- case Variant::FLOAT: {
- EditorPropertyFloat *editor = memnew(EditorPropertyFloat);
- editor->setup(-100000, 100000, default_float_step, true, false, true, true);
- prop = editor;
- } break;
- case Variant::STRING: {
- if (i != amount && property_hint == PROPERTY_HINT_MULTILINE_TEXT) {
- // If this is NOT the new key field and there's a multiline hint,
- // show the field as multiline
- prop = memnew(EditorPropertyMultilineText);
- } else {
- prop = memnew(EditorPropertyText);
- }
-
- } break;
-
- // Math types.
- case Variant::VECTOR2: {
- EditorPropertyVector2 *editor = memnew(EditorPropertyVector2);
- editor->setup(-100000, 100000, default_float_step, true);
- prop = editor;
-
- } break;
- case Variant::VECTOR2I: {
- EditorPropertyVector2i *editor = memnew(EditorPropertyVector2i);
- editor->setup(-100000, 100000);
- prop = editor;
-
- } break;
- case Variant::RECT2: {
- EditorPropertyRect2 *editor = memnew(EditorPropertyRect2);
- editor->setup(-100000, 100000, default_float_step, true);
- prop = editor;
-
- } break;
- case Variant::RECT2I: {
- EditorPropertyRect2i *editor = memnew(EditorPropertyRect2i);
- editor->setup(-100000, 100000);
- prop = editor;
-
- } break;
- case Variant::VECTOR3: {
- EditorPropertyVector3 *editor = memnew(EditorPropertyVector3);
- editor->setup(-100000, 100000, default_float_step, true);
- prop = editor;
-
- } break;
- case Variant::VECTOR3I: {
- EditorPropertyVector3i *editor = memnew(EditorPropertyVector3i);
- editor->setup(-100000, 100000);
- prop = editor;
-
- } break;
- case Variant::VECTOR4: {
- EditorPropertyVector4 *editor = memnew(EditorPropertyVector4);
- editor->setup(-100000, 100000, default_float_step, true);
- prop = editor;
-
- } break;
- case Variant::VECTOR4I: {
- EditorPropertyVector4i *editor = memnew(EditorPropertyVector4i);
- editor->setup(-100000, 100000);
- prop = editor;
-
- } break;
- case Variant::TRANSFORM2D: {
- EditorPropertyTransform2D *editor = memnew(EditorPropertyTransform2D);
- editor->setup(-100000, 100000, default_float_step, true);
- prop = editor;
-
- } break;
- case Variant::PLANE: {
- EditorPropertyPlane *editor = memnew(EditorPropertyPlane);
- editor->setup(-100000, 100000, default_float_step, true);
- prop = editor;
-
- } break;
- case Variant::QUATERNION: {
- EditorPropertyQuaternion *editor = memnew(EditorPropertyQuaternion);
- editor->setup(-100000, 100000, default_float_step, true);
- prop = editor;
-
- } break;
- case Variant::AABB: {
- EditorPropertyAABB *editor = memnew(EditorPropertyAABB);
- editor->setup(-100000, 100000, default_float_step, true);
- prop = editor;
-
- } break;
- case Variant::BASIS: {
- EditorPropertyBasis *editor = memnew(EditorPropertyBasis);
- editor->setup(-100000, 100000, default_float_step, true);
- prop = editor;
-
- } break;
- case Variant::TRANSFORM3D: {
- EditorPropertyTransform3D *editor = memnew(EditorPropertyTransform3D);
- editor->setup(-100000, 100000, default_float_step, true);
- prop = editor;
-
- } break;
- case Variant::PROJECTION: {
- EditorPropertyProjection *editor = memnew(EditorPropertyProjection);
- editor->setup(-100000, 100000, default_float_step, true);
- prop = editor;
-
- } break;
-
- // Miscellaneous types.
- case Variant::COLOR: {
- prop = memnew(EditorPropertyColor);
-
- } break;
- case Variant::STRING_NAME: {
- EditorPropertyText *ept = memnew(EditorPropertyText);
- ept->set_string_name(true);
- prop = ept;
-
- } break;
- case Variant::NODE_PATH: {
- prop = memnew(EditorPropertyNodePath);
-
- } break;
- case Variant::RID: {
- prop = memnew(EditorPropertyRID);
-
- } break;
- case Variant::SIGNAL: {
- prop = memnew(EditorPropertySignal);
-
- } break;
- case Variant::CALLABLE: {
- prop = memnew(EditorPropertyCallable);
-
- } break;
- case Variant::OBJECT: {
- if (Object::cast_to<EncodedObjectAsID>(value)) {
- EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID);
- editor->setup("Object");
- prop = editor;
-
- } else {
- EditorPropertyResource *editor = memnew(EditorPropertyResource);
- editor->setup(object.ptr(), prop_name, "Resource");
- editor->set_use_folding(is_using_folding());
- prop = editor;
- }
-
- } break;
- case Variant::DICTIONARY: {
- prop = memnew(EditorPropertyDictionary);
-
- } break;
- case Variant::ARRAY: {
- EditorPropertyArray *editor = memnew(EditorPropertyArray);
- editor->setup(Variant::ARRAY);
- prop = editor;
- } break;
-
- // Arrays.
- case Variant::PACKED_BYTE_ARRAY: {
- EditorPropertyArray *editor = memnew(EditorPropertyArray);
- editor->setup(Variant::PACKED_BYTE_ARRAY);
- prop = editor;
- } break;
- case Variant::PACKED_INT32_ARRAY: {
- EditorPropertyArray *editor = memnew(EditorPropertyArray);
- editor->setup(Variant::PACKED_INT32_ARRAY);
- prop = editor;
- } break;
- case Variant::PACKED_FLOAT32_ARRAY: {
- EditorPropertyArray *editor = memnew(EditorPropertyArray);
- editor->setup(Variant::PACKED_FLOAT32_ARRAY);
- prop = editor;
- } break;
- case Variant::PACKED_INT64_ARRAY: {
- EditorPropertyArray *editor = memnew(EditorPropertyArray);
- editor->setup(Variant::PACKED_INT64_ARRAY);
- prop = editor;
- } break;
- case Variant::PACKED_FLOAT64_ARRAY: {
- EditorPropertyArray *editor = memnew(EditorPropertyArray);
- editor->setup(Variant::PACKED_FLOAT64_ARRAY);
- prop = editor;
- } break;
- case Variant::PACKED_STRING_ARRAY: {
- EditorPropertyArray *editor = memnew(EditorPropertyArray);
- editor->setup(Variant::PACKED_STRING_ARRAY);
- prop = editor;
- } break;
- case Variant::PACKED_VECTOR2_ARRAY: {
- EditorPropertyArray *editor = memnew(EditorPropertyArray);
- editor->setup(Variant::PACKED_VECTOR2_ARRAY);
- prop = editor;
- } break;
- case Variant::PACKED_VECTOR3_ARRAY: {
- EditorPropertyArray *editor = memnew(EditorPropertyArray);
- editor->setup(Variant::PACKED_VECTOR3_ARRAY);
- prop = editor;
- } break;
- case Variant::PACKED_COLOR_ARRAY: {
- EditorPropertyArray *editor = memnew(EditorPropertyArray);
- editor->setup(Variant::PACKED_COLOR_ARRAY);
- prop = editor;
- } break;
- default: {
+ // Check if the editor property needs to be updated.
+ bool value_as_id = Object::cast_to<EncodedObjectAsID>(value);
+ if (value_type != slot.type || (value_type == Variant::OBJECT && value_as_id != slot.as_id)) {
+ slot.as_id = value_as_id;
+ slot.type = value_type;
+ EditorProperty *new_prop = nullptr;
+ if (value_type == Variant::OBJECT && value_as_id) {
+ EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID);
+ editor->setup("Object");
+ new_prop = editor;
+ } else {
+ new_prop = EditorInspector::instantiate_property_editor(nullptr, value_type, "", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE);
}
+ new_prop->set_selectable(false);
+ new_prop->set_use_folding(is_using_folding());
+ new_prop->connect(SNAME("property_changed"), callable_mp(this, &EditorPropertyDictionary::_property_changed));
+ new_prop->connect(SNAME("object_id_selected"), callable_mp(this, &EditorPropertyDictionary::_object_id_selected));
+ new_prop->set_h_size_flags(SIZE_EXPAND_FILL);
+ new_prop->set_read_only(is_read_only());
+ slot.set_prop(new_prop);
}
-
- ERR_FAIL_NULL(prop);
-
- prop->set_read_only(is_read_only());
-
- if (i == amount) {
- PanelContainer *pc = memnew(PanelContainer);
- property_vbox->add_child(pc);
- pc->add_theme_style_override(SNAME("panel"), get_theme_stylebox(SNAME("DictionaryAddItem"), EditorStringName(EditorStyles)));
-
- add_vbox = memnew(VBoxContainer);
- pc->add_child(add_vbox);
- }
- prop->set_object_and_property(object.ptr(), prop_name);
- int change_index = 0;
-
- if (i < amount) {
- String cs = key.get_construct_string();
- prop->set_label(key.get_construct_string());
- prop->set_tooltip_text(cs);
- change_index = i + offset;
- } else if (i == amount) {
- prop->set_label(TTR("New Key:"));
- change_index = -1;
- } else if (i == amount + 1) {
- prop->set_label(TTR("New Value:"));
- change_index = -2;
- }
-
- prop->set_selectable(false);
- prop->connect("property_changed", callable_mp(this, &EditorPropertyDictionary::_property_changed));
- prop->connect("object_id_selected", callable_mp(this, &EditorPropertyDictionary::_object_id_selected));
-
- HBoxContainer *hbox = memnew(HBoxContainer);
- if (add_vbox) {
- add_vbox->add_child(hbox);
- } else {
- property_vbox->add_child(hbox);
- }
- hbox->add_child(prop);
- prop->set_h_size_flags(SIZE_EXPAND_FILL);
- Button *edit_btn = memnew(Button);
- edit_btn->set_icon(get_editor_theme_icon(SNAME("Edit")));
- edit_btn->set_disabled(is_read_only());
- hbox->add_child(edit_btn);
- edit_btn->connect("pressed", callable_mp(this, &EditorPropertyDictionary::_change_type).bind(edit_btn, change_index));
-
- prop->update_property();
-
- if (i == amount + 1) {
- button_add_item = EditorInspector::create_inspector_action_button(TTR("Add Key/Value Pair"));
- button_add_item->set_icon(get_editor_theme_icon(SNAME("Add")));
- button_add_item->set_disabled(is_read_only());
- button_add_item->connect("pressed", callable_mp(this, &EditorPropertyDictionary::_add_key_value));
- add_vbox->add_child(button_add_item);
- }
+ slot.prop->update_property();
}
-
updating = false;
} else {
@@ -1210,6 +983,8 @@ void EditorPropertyDictionary::update_property() {
memdelete(container);
button_add_item = nullptr;
container = nullptr;
+ add_panel = nullptr;
+ slots.clear();
}
}
}
@@ -1254,10 +1029,17 @@ void EditorPropertyDictionary::_edit_pressed() {
}
void EditorPropertyDictionary::_page_changed(int p_page) {
+ page_index = p_page;
+ int i = p_page * page_length;
+ for (Slot &slot : slots) {
+ if (slot.index > -1) {
+ slot.set_index(i);
+ i++;
+ }
+ }
if (updating) {
return;
}
- page_index = p_page;
update_property();
}
diff --git a/editor/editor_properties_array_dict.h b/editor/editor_properties_array_dict.h
index 0e81a0fae3..b1bf45f1b7 100644
--- a/editor/editor_properties_array_dict.h
+++ b/editor/editor_properties_array_dict.h
@@ -66,6 +66,11 @@ protected:
bool _get(const StringName &p_name, Variant &r_ret) const;
public:
+ enum {
+ NEW_KEY_INDEX = -2,
+ NEW_VALUE_INDEX,
+ };
+
void set_dict(const Dictionary &p_dict);
Dictionary get_dict();
@@ -75,6 +80,9 @@ public:
void set_new_item_value(const Variant &p_new_item);
Variant get_new_item_value();
+ String get_label_for_index(int p_index);
+ String get_property_name_for_index(int p_index);
+
EditorPropertyDictionaryObject();
};
@@ -125,7 +133,7 @@ class EditorPropertyArray : public EditorProperty {
void _reorder_button_gui_input(const Ref<InputEvent> &p_event);
void _reorder_button_down(int p_index);
void _reorder_button_up();
- void create_new_property_slot();
+ void _create_new_property_slot();
protected:
Ref<EditorPropertyArrayObject> object;
@@ -160,6 +168,34 @@ public:
class EditorPropertyDictionary : public EditorProperty {
GDCLASS(EditorPropertyDictionary, EditorProperty);
+ struct Slot {
+ Ref<EditorPropertyDictionaryObject> object;
+ HBoxContainer *container = nullptr;
+ int index = -1;
+ Variant::Type type = Variant::VARIANT_MAX;
+ bool as_id = false;
+ EditorProperty *prop = nullptr;
+ String prop_name;
+
+ void set_index(int p_idx) {
+ index = p_idx;
+ prop_name = object->get_property_name_for_index(p_idx);
+ update_prop_or_index();
+ }
+
+ void set_prop(EditorProperty *p_prop) {
+ prop->add_sibling(p_prop);
+ prop->queue_free();
+ prop = p_prop;
+ update_prop_or_index();
+ }
+
+ void update_prop_or_index() {
+ prop->set_object_and_property(object.ptr(), prop_name);
+ prop->set_label(object->get_label_for_index(index));
+ }
+ };
+
PopupMenu *change_type = nullptr;
bool updating = false;
@@ -170,15 +206,18 @@ class EditorPropertyDictionary : public EditorProperty {
Button *edit = nullptr;
MarginContainer *container = nullptr;
VBoxContainer *property_vbox = nullptr;
+ PanelContainer *add_panel = nullptr;
EditorSpinSlider *size_sliderv = nullptr;
Button *button_add_item = nullptr;
EditorPaginator *paginator = nullptr;
PropertyHint property_hint;
+ LocalVector<Slot> slots;
+ void _create_new_property_slot(int p_idx);
void _page_changed(int p_page);
void _edit_pressed();
void _property_changed(const String &p_property, Variant p_value, const String &p_name = "", bool p_changing = false);
- void _change_type(Object *p_button, int p_index);
+ void _change_type(Object *p_button, int p_slot_index);
void _change_type_menu(int p_index);
void _add_key_value();
diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp
index a9225a3057..5450040bea 100644
--- a/editor/editor_resource_picker.cpp
+++ b/editor/editor_resource_picker.cpp
@@ -112,7 +112,7 @@ void EditorResourcePicker::_update_resource_preview(const String &p_path, const
preview_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
int thumbnail_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size");
thumbnail_size *= EDSCALE;
- assign_button->set_custom_minimum_size(Size2(MAX(1, assign_button_min_size.x), MAX(thumbnail_size, assign_button_min_size.y)));
+ assign_button->set_custom_minimum_size(assign_button_min_size.max(Size2(1, thumbnail_size)));
}
preview_rect->set_texture(p_preview);
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index 7436cd8629..ee33e171e3 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -653,6 +653,8 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
_initial_set("text_editor/completion/put_callhint_tooltip_below_current_line", true);
_initial_set("text_editor/completion/complete_file_paths", true);
_initial_set("text_editor/completion/add_type_hints", true);
+ _initial_set("text_editor/completion/add_string_name_literals", false);
+ _initial_set("text_editor/completion/add_node_path_literals", false);
_initial_set("text_editor/completion/use_single_quotes", false);
_initial_set("text_editor/completion/colorize_suggestions", true);
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index fc170d606a..8bfad72fe1 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -173,14 +173,48 @@ FileSystemList::FileSystemList() {
FileSystemDock *FileSystemDock::singleton = nullptr;
-Ref<Texture2D> FileSystemDock::_get_tree_item_icon(bool p_is_valid, const String &p_file_type) {
- Ref<Texture2D> file_icon;
+Ref<Texture2D> FileSystemDock::_get_tree_item_icon(bool p_is_valid, const String &p_file_type, const String &p_icon_path) {
+ if (!p_icon_path.is_empty()) {
+ Ref<Texture2D> icon = ResourceLoader::load(p_icon_path);
+ if (icon.is_valid()) {
+ return icon;
+ }
+ }
+
if (!p_is_valid) {
- file_icon = get_editor_theme_icon(SNAME("ImportFail"));
+ return get_editor_theme_icon(SNAME("ImportFail"));
+ } else if (has_theme_icon(p_file_type, EditorStringName(EditorIcons))) {
+ return get_editor_theme_icon(p_file_type);
} else {
- file_icon = (has_theme_icon(p_file_type, EditorStringName(EditorIcons))) ? get_editor_theme_icon(p_file_type) : get_editor_theme_icon(SNAME("File"));
+ return get_editor_theme_icon(SNAME("File"));
}
- return file_icon;
+}
+
+String FileSystemDock::_get_entry_script_icon(const EditorFileSystemDirectory *p_dir, int p_file) {
+ const PackedStringArray &deps = p_dir->get_file_deps(p_file);
+ if (deps.is_empty()) {
+ return String();
+ }
+
+ const String &script_path = deps[0]; // Assuming the first dependency is a script.
+ if (script_path.is_empty() || !ClassDB::is_parent_class(ResourceLoader::get_resource_type(script_path), SNAME("Script"))) {
+ return String();
+ }
+
+ String *cached = icon_cache.getptr(script_path);
+ if (cached) {
+ return *cached;
+ }
+
+ HashMap<String, String>::Iterator I;
+ int script_file;
+ EditorFileSystemDirectory *efsd = EditorFileSystem::get_singleton()->find_file(script_path, &script_file);
+ if (efsd) {
+ I = icon_cache.insert(script_path, efsd->get_file_script_class_icon_path(script_file));
+ } else {
+ I = icon_cache.insert(script_path, String());
+ }
+ return I->value;
}
bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory *p_dir, Vector<String> &uncollapsed_paths, bool p_select_in_favorites, bool p_unfold_path) {
@@ -272,6 +306,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory
FileInfo fi;
fi.name = p_dir->get_file(i);
fi.type = p_dir->get_file_type(i);
+ fi.icon_path = _get_entry_script_icon(p_dir, i);
fi.import_broken = !p_dir->get_file_import_is_valid(i);
fi.modified_time = p_dir->get_file_modified_time(i);
@@ -282,18 +317,21 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory
_sort_file_info_list(file_list);
// Build the tree.
+ const int icon_size = get_theme_constant(SNAME("class_icon_size"), SNAME("Editor"));
+
for (const FileInfo &fi : file_list) {
TreeItem *file_item = tree->create_item(subdirectory_item);
+ const String file_metadata = lpath.path_join(fi.name);
file_item->set_text(0, fi.name);
file_item->set_structured_text_bidi_override(0, TextServer::STRUCTURED_TEXT_FILE);
- file_item->set_icon(0, _get_tree_item_icon(!fi.import_broken, fi.type));
+ file_item->set_icon(0, _get_tree_item_icon(!fi.import_broken, fi.type, fi.icon_path));
+ file_item->set_icon_max_width(0, icon_size);
Color parent_bg_color = subdirectory_item->get_custom_bg_color(0);
if (has_custom_color) {
file_item->set_custom_bg_color(0, parent_bg_color.darkened(0.3));
} else if (parent_bg_color != Color()) {
file_item->set_custom_bg_color(0, parent_bg_color);
}
- String file_metadata = lpath.path_join(fi.name);
file_item->set_metadata(0, file_metadata);
if (!p_select_in_favorites && current_path == file_metadata) {
file_item->select(0);
@@ -366,6 +404,8 @@ void FileSystemDock::_update_tree(const Vector<String> &p_uncollapsed_paths, boo
updating_tree = true;
TreeItem *root = tree->create_item();
+ icon_cache.clear();
+
// Handles the favorites.
TreeItem *favorites_item = tree->create_item(root);
favorites_item->set_icon(0, get_editor_theme_icon(SNAME("Favorites")));
@@ -413,7 +453,7 @@ void FileSystemDock::_update_tree(const Vector<String> &p_uncollapsed_paths, boo
int index;
EditorFileSystemDirectory *dir = EditorFileSystem::get_singleton()->find_file(favorite, &index);
if (dir) {
- icon = _get_tree_item_icon(dir->get_file_import_is_valid(index), dir->get_file_type(index));
+ icon = _get_tree_item_icon(dir->get_file_import_is_valid(index), dir->get_file_path(index), dir->get_file_type(index));
} else {
icon = get_editor_theme_icon(SNAME("File"));
}
@@ -736,12 +776,7 @@ void FileSystemDock::navigate_to_path(const String &p_path) {
_navigate_to_path(p_path);
// Ensure that the FileSystem dock is visible.
- if (get_window() == get_tree()->get_root()) {
- TabContainer *tab_container = (TabContainer *)get_parent_control();
- tab_container->set_current_tab(tab_container->get_tab_idx_from_control((Control *)this));
- } else {
- get_window()->grab_focus();
- }
+ EditorDockManager::get_singleton()->focus_dock(this);
}
void FileSystemDock::_file_list_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata) {
@@ -845,7 +880,7 @@ void FileSystemDock::_search(EditorFileSystemDirectory *p_path, List<FileInfo> *
struct FileSystemDock::FileInfoTypeComparator {
bool operator()(const FileInfo &p_a, const FileInfo &p_b) const {
- return NaturalNoCaseComparator()(p_a.name.get_extension() + p_a.type + p_a.name.get_basename(), p_b.name.get_extension() + p_b.type + p_b.name.get_basename());
+ return FileNoCaseComparator()(p_a.name.get_extension() + p_a.type + p_a.name.get_basename(), p_b.name.get_extension() + p_b.type + p_b.name.get_basename());
}
};
@@ -2003,7 +2038,7 @@ Vector<String> FileSystemDock::_tree_get_selected(bool remove_self_inclusion, bo
Vector<String> FileSystemDock::_remove_self_included_paths(Vector<String> selected_strings) {
// Remove paths or files that are included into another.
if (selected_strings.size() > 1) {
- selected_strings.sort_custom<NaturalNoCaseComparator>();
+ selected_strings.sort_custom<FileNoCaseComparator>();
String last_path = "";
for (int i = 0; i < selected_strings.size(); i++) {
if (!last_path.is_empty() && selected_strings[i].begins_with(last_path)) {
diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h
index acb7ca017b..b950075928 100644
--- a/editor/filesystem_dock.h
+++ b/editor/filesystem_dock.h
@@ -139,6 +139,7 @@ private:
FILE_NEW_SCENE,
};
+ HashMap<String, String> icon_cache;
HashMap<String, Color> folder_colors;
Dictionary assigned_folder_colors;
@@ -245,7 +246,8 @@ private:
void _tree_mouse_exited();
void _reselect_items_selected_on_drag_begin(bool reset = false);
- Ref<Texture2D> _get_tree_item_icon(bool p_is_valid, const String &p_file_type);
+ Ref<Texture2D> _get_tree_item_icon(bool p_is_valid, const String &p_file_type, const String &p_icon_path);
+ String _get_entry_script_icon(const EditorFileSystemDirectory *p_dir, int p_file);
bool _create_tree(TreeItem *p_parent, EditorFileSystemDirectory *p_dir, Vector<String> &uncollapsed_paths, bool p_select_in_favorites, bool p_unfold_path = false);
void _update_tree(const Vector<String> &p_uncollapsed_paths = Vector<String>(), bool p_uncollapse_root = false, bool p_select_in_favorites = false, bool p_unfold_path = false);
void _navigate_to_path(const String &p_path, bool p_select_in_favorites = false);
@@ -323,13 +325,14 @@ private:
struct FileInfo {
String name;
String path;
+ String icon_path;
StringName type;
Vector<String> sources;
bool import_broken = false;
uint64_t modified_time = 0;
bool operator<(const FileInfo &fi) const {
- return NaturalNoCaseComparator()(name, fi.name);
+ return FileNoCaseComparator()(name, fi.name);
}
};
diff --git a/editor/gui/editor_bottom_panel.cpp b/editor/gui/editor_bottom_panel.cpp
index e567e42bdb..1c95b546f4 100644
--- a/editor/gui/editor_bottom_panel.cpp
+++ b/editor/gui/editor_bottom_panel.cpp
@@ -50,10 +50,6 @@ void EditorBottomPanel::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
expand_button->set_icon(get_editor_theme_icon(SNAME("ExpandBottomDock")));
- for (int i = 0; i < items.size(); i++) {
- items.write[i].button->add_theme_style_override("pressed", get_theme_stylebox(SNAME("MenuTransparent"), EditorStringName(EditorStyles)));
- items.write[i].button->add_theme_style_override("hover_pressed", get_theme_stylebox(SNAME("MenuHover"), EditorStringName(EditorStyles)));
- }
} break;
}
}
@@ -160,7 +156,7 @@ void EditorBottomPanel::load_layout_from_config(Ref<ConfigFile> p_config_file, c
Button *EditorBottomPanel::add_item(String p_text, Control *p_item, const Ref<Shortcut> &p_shortcut, bool p_at_front) {
Button *tb = memnew(Button);
- tb->set_theme_type_variation("FlatMenuButton");
+ tb->set_theme_type_variation("BottomPanelButton");
tb->connect("toggled", callable_mp(this, &EditorBottomPanel::_switch_by_control).bind(p_item));
tb->set_drag_forwarding(Callable(), callable_mp(this, &EditorBottomPanel::_button_drag_hover).bind(tb, p_item), Callable());
tb->set_text(p_text);
diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp
index fe86ac442b..df1f026f78 100644
--- a/editor/gui/editor_file_dialog.cpp
+++ b/editor/gui/editor_file_dialog.cpp
@@ -874,8 +874,8 @@ void EditorFileDialog::update_file_list() {
item = dir_access->get_next();
}
- dirs.sort_custom<NaturalNoCaseComparator>();
- files.sort_custom<NaturalNoCaseComparator>();
+ dirs.sort_custom<FileNoCaseComparator>();
+ files.sort_custom<FileNoCaseComparator>();
while (!dirs.is_empty()) {
const String &dir_name = dirs.front()->get();
diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp
index fab5784f16..c0a704105c 100644
--- a/editor/gui/editor_spin_slider.cpp
+++ b/editor/gui/editor_spin_slider.cpp
@@ -238,28 +238,28 @@ void EditorSpinSlider::_grabber_gui_input(const Ref<InputEvent> &p_event) {
void EditorSpinSlider::_value_input_gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventKey> k = p_event;
if (k.is_valid() && k->is_pressed() && !is_read_only()) {
- double step = get_step();
- if (step < 1) {
- double divisor = 1.0 / get_step();
-
- if (trunc(divisor) == divisor) {
- step = 1.0;
- }
- }
-
- if (k->is_command_or_control_pressed()) {
- step *= 100.0;
- } else if (k->is_shift_pressed()) {
- step *= 10.0;
- } else if (k->is_alt_pressed()) {
- step *= 0.1;
- }
-
Key code = k->get_keycode();
switch (code) {
case Key::UP:
case Key::DOWN: {
+ double step = get_step();
+ if (step < 1) {
+ double divisor = 1.0 / step;
+
+ if (trunc(divisor) == divisor) {
+ step = 1.0;
+ }
+ }
+
+ if (k->is_command_or_control_pressed()) {
+ step *= 100.0;
+ } else if (k->is_shift_pressed()) {
+ step *= 10.0;
+ } else if (k->is_alt_pressed()) {
+ step *= 0.1;
+ }
+
_evaluate_input_text();
double last_value = get_value();
@@ -267,12 +267,6 @@ void EditorSpinSlider::_value_input_gui_input(const Ref<InputEvent> &p_event) {
step *= -1;
}
set_value(last_value + step);
- double new_value = get_value();
-
- double clamp_value = CLAMP(new_value, get_min(), get_max());
- if (new_value != clamp_value) {
- set_value(clamp_value);
- }
value_input_dirty = true;
set_process_internal(true);
diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp
index 248678662c..e87ddd6915 100644
--- a/editor/gui/scene_tree_editor.cpp
+++ b/editor/gui/scene_tree_editor.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "core/object/script_language.h"
+#include "editor/editor_dock_manager.h"
#include "editor/editor_file_system.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
@@ -163,18 +164,15 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i
set_selected(n);
- TabContainer *tab_container = Object::cast_to<TabContainer>(NodeDock::get_singleton()->get_parent());
- NodeDock::get_singleton()->get_parent()->call("set_current_tab", tab_container->get_tab_idx_from_control(NodeDock::get_singleton()));
+ EditorDockManager::get_singleton()->focus_dock(NodeDock::get_singleton());
NodeDock::get_singleton()->show_connections();
-
} else if (p_id == BUTTON_GROUPS) {
editor_selection->clear();
editor_selection->add_node(n);
set_selected(n);
- TabContainer *tab_container = Object::cast_to<TabContainer>(NodeDock::get_singleton()->get_parent());
- NodeDock::get_singleton()->get_parent()->call("set_current_tab", tab_container->get_tab_idx_from_control(NodeDock::get_singleton()));
+ EditorDockManager::get_singleton()->focus_dock(NodeDock::get_singleton());
NodeDock::get_singleton()->show_groups();
} else if (p_id == BUTTON_UNIQUE) {
undo_redo->create_action(TTR("Disable Scene Unique Name"));
diff --git a/editor/icons/AnimationAutoFit.svg b/editor/icons/AnimationAutoFit.svg
new file mode 100644
index 0000000000..fdde20d464
--- /dev/null
+++ b/editor/icons/AnimationAutoFit.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="m15.477 0.99862v14h-2.1017v-14zm-14.954 14.003v-14h2.1017v14zm11.946-6.823-3.5-3v6zm-8.9343 0.023985 3.5 3v-2h2.1464l0.00376-2h-2.1501v-2zm4.6005-7.0028c8.9077 15.09 8.9077 15.09 0 0zm-0.23085 14.003c-8.9077-15.09-8.9077-15.09 0 0z" fill="#e0e0e0" stroke-width="1.0251"/></svg>
diff --git a/editor/icons/AnimationAutoFitBezier.svg b/editor/icons/AnimationAutoFitBezier.svg
new file mode 100644
index 0000000000..1a79255c19
--- /dev/null
+++ b/editor/icons/AnimationAutoFitBezier.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="m12.469 8.1784-3.5-3v6zm-8.9343 0.023985 3.5 3v-2h2.1464l0.00376-2h-2.1501v-2zm9.4532 0.53338c-10.763 8.9077-10.763 8.9077 0 0zm0 7h-9.9859v-2h9.9859zm-9.9806-8.5564c10.763-8.9077 10.763-8.9077 0 0zm0-7h9.9859v2h-9.9859zm5.4684 2.8277c8.9077 10.763 8.9077 10.763 0 0zm7 0v9.9859h-2v-9.9859zm-7.8862 9.9859c-8.9077-10.763-8.9077-10.763 0 0zm-7 0v-9.9859h2v9.9859z" fill="#e0e0e0"/></svg>
diff --git a/editor/import/3d/resource_importer_scene.h b/editor/import/3d/resource_importer_scene.h
index 2e682350bc..17fa9ef0e2 100644
--- a/editor/import/3d/resource_importer_scene.h
+++ b/editor/import/3d/resource_importer_scene.h
@@ -303,10 +303,10 @@ public:
ResourceImporterScene(bool p_animation_import = false, bool p_singleton = false);
~ResourceImporterScene();
- template <class M>
+ template <typename M>
static Vector<Ref<Shape3D>> get_collision_shapes(const Ref<ImporterMesh> &p_mesh, const M &p_options, float p_applied_root_scale);
- template <class M>
+ template <typename M>
static Transform3D get_collision_shapes_transform(const M &p_options);
};
@@ -319,7 +319,7 @@ public:
virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err = nullptr) override;
};
-template <class M>
+template <typename M>
Vector<Ref<Shape3D>> ResourceImporterScene::get_collision_shapes(const Ref<ImporterMesh> &p_mesh, const M &p_options, float p_applied_root_scale) {
ERR_FAIL_COND_V(p_mesh.is_null(), Vector<Ref<Shape3D>>());
ShapeType generate_shape_type = SHAPE_TYPE_DECOMPOSE_CONVEX;
@@ -476,7 +476,7 @@ Vector<Ref<Shape3D>> ResourceImporterScene::get_collision_shapes(const Ref<Impor
return Vector<Ref<Shape3D>>();
}
-template <class M>
+template <typename M>
Transform3D ResourceImporterScene::get_collision_shapes_transform(const M &p_options) {
Transform3D transform;
diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp
index 0412141775..bda2cb666b 100644
--- a/editor/plugins/animation_blend_tree_editor_plugin.cpp
+++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp
@@ -256,10 +256,6 @@ void AnimationNodeBlendTreeEditor::update_graph() {
options.push_back(F);
}
- if (tree->has_animation(anim->get_animation())) {
- pb->set_max(tree->get_animation(anim->get_animation())->get_length());
- }
-
pb->set_show_percentage(false);
pb->set_custom_minimum_size(Vector2(0, 14) * EDSCALE);
animations[E] = pb;
@@ -994,9 +990,10 @@ void AnimationNodeBlendTreeEditor::_notification(int p_what) {
if (tree->has_animation(an->get_animation())) {
Ref<Animation> anim = tree->get_animation(an->get_animation());
if (anim.is_valid()) {
- E.value->set_max(anim->get_length());
//StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node;
- StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/time";
+ StringName length_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/current_length";
+ StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/current_position";
+ E.value->set_max(tree->get(length_path));
E.value->set_value(tree->get(time_path));
}
}
diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp
index dbfb143b22..db6e3dd69f 100644
--- a/editor/plugins/animation_state_machine_editor.cpp
+++ b/editor/plugins/animation_state_machine_editor.cpp
@@ -337,10 +337,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && tool_select->is_pressed()) {
box_selecting = true;
box_selecting_from = box_selecting_to = state_machine_draw->get_local_mouse_position();
- box_selecting_rect = Rect2(MIN(box_selecting_from.x, box_selecting_to.x),
- MIN(box_selecting_from.y, box_selecting_to.y),
- ABS(box_selecting_from.x - box_selecting_to.x),
- ABS(box_selecting_from.y - box_selecting_to.y));
+ box_selecting_rect = Rect2(box_selecting_from.min(box_selecting_to), (box_selecting_from - box_selecting_to).abs());
if (mb->is_command_or_control_pressed() || mb->is_shift_pressed()) {
previous_selected = selected_nodes;
@@ -423,10 +420,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
if (mm.is_valid() && box_selecting) {
box_selecting_to = state_machine_draw->get_local_mouse_position();
- box_selecting_rect = Rect2(MIN(box_selecting_from.x, box_selecting_to.x),
- MIN(box_selecting_from.y, box_selecting_to.y),
- ABS(box_selecting_from.x - box_selecting_to.x),
- ABS(box_selecting_from.y - box_selecting_to.y));
+ box_selecting_rect = Rect2(box_selecting_from.min(box_selecting_to), (box_selecting_from - box_selecting_to).abs());
for (int i = 0; i < node_rects.size(); i++) {
bool in_box = node_rects[i].node.intersects(box_selecting_rect);
@@ -1247,14 +1241,14 @@ void AnimationNodeStateMachineEditor::_state_machine_pos_draw_all() {
{
float len = MAX(0.0001, current_length);
float pos = CLAMP(current_play_pos, 0, len);
- float c = current_length == HUGE_LENGTH ? 1 : (pos / len);
+ float c = pos / len;
_state_machine_pos_draw_individual(playback->get_current_node(), c);
}
{
float len = MAX(0.0001, fade_from_length);
float pos = CLAMP(fade_from_current_play_pos, 0, len);
- float c = fade_from_length == HUGE_LENGTH ? 1 : (pos / len);
+ float c = pos / len;
_state_machine_pos_draw_individual(playback->get_fading_from_node(), c);
}
}
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 894eef74ec..41e5eee486 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -2643,6 +2643,7 @@ void CanvasItemEditor::_update_cursor() {
void CanvasItemEditor::_update_lock_and_group_button() {
bool all_locked = true;
bool all_group = true;
+ bool has_canvas_item = false;
List<Node *> selection = editor_selection->get_selected_node_list();
if (selection.is_empty()) {
all_locked = false;
@@ -2657,6 +2658,7 @@ void CanvasItemEditor::_update_lock_and_group_button() {
if (all_group && !item->has_meta("_edit_group_")) {
all_group = false;
}
+ has_canvas_item = true;
}
if (!all_locked && !all_group) {
break;
@@ -2664,12 +2666,17 @@ void CanvasItemEditor::_update_lock_and_group_button() {
}
}
+ all_locked = all_locked && has_canvas_item;
+ all_group = all_group && has_canvas_item;
+
lock_button->set_visible(!all_locked);
- lock_button->set_disabled(selection.is_empty());
+ lock_button->set_disabled(!has_canvas_item);
unlock_button->set_visible(all_locked);
+ unlock_button->set_disabled(!has_canvas_item);
group_button->set_visible(!all_group);
- group_button->set_disabled(selection.is_empty());
+ group_button->set_disabled(!has_canvas_item);
ungroup_button->set_visible(all_group);
+ ungroup_button->set_disabled(!has_canvas_item);
}
Control::CursorShape CanvasItemEditor::get_cursor_shape(const Point2 &p_pos) const {
@@ -4011,6 +4018,9 @@ void CanvasItemEditor::_notification(int p_what) {
AnimationPlayerEditor::get_singleton()->connect("animation_selected", callable_mp(this, &CanvasItemEditor::_keying_changed).unbind(1));
_keying_changed();
_update_editor_settings();
+
+ connect("item_lock_status_changed", callable_mp(this, &CanvasItemEditor::_update_lock_and_group_button));
+ connect("item_group_status_changed", callable_mp(this, &CanvasItemEditor::_update_lock_and_group_button));
} break;
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp
index e8804fdf12..70213b280c 100644
--- a/editor/plugins/editor_preview_plugins.cpp
+++ b/editor/plugins/editor_preview_plugins.cpp
@@ -130,7 +130,7 @@ Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from,
if (new_size.y > p_size.y) {
new_size = Vector2(new_size.x * p_size.y / new_size.y, p_size.y);
}
- Vector2i new_size_i(MAX(1, (int)new_size.x), MAX(1, (int)new_size.y));
+ Vector2i new_size_i = Vector2i(new_size).max(Vector2i(1, 1));
img->resize(new_size_i.x, new_size_i.y, Image::INTERPOLATE_CUBIC);
post_process_preview(img);
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index f38d42f681..7a4a7ddaec 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -7376,6 +7376,7 @@ void Node3DEditor::_selection_changed() {
void Node3DEditor::_refresh_menu_icons() {
bool all_locked = true;
bool all_grouped = true;
+ bool has_node3d_item = false;
List<Node *> &selection = editor_selection->get_selected_node_list();
@@ -7384,26 +7385,34 @@ void Node3DEditor::_refresh_menu_icons() {
all_grouped = false;
} else {
for (Node *E : selection) {
- if (Object::cast_to<Node3D>(E) && !Object::cast_to<Node3D>(E)->has_meta("_edit_lock_")) {
- all_locked = false;
- break;
+ Node3D *node = Object::cast_to<Node3D>(E);
+ if (node) {
+ if (all_locked && !node->has_meta("_edit_lock_")) {
+ all_locked = false;
+ }
+ if (all_grouped && !node->has_meta("_edit_group_")) {
+ all_grouped = false;
+ }
+ has_node3d_item = true;
}
- }
- for (Node *E : selection) {
- if (Object::cast_to<Node3D>(E) && !Object::cast_to<Node3D>(E)->has_meta("_edit_group_")) {
- all_grouped = false;
+ if (!all_locked && !all_grouped) {
break;
}
}
}
+ all_locked = all_locked && has_node3d_item;
+ all_grouped = all_grouped && has_node3d_item;
+
tool_button[TOOL_LOCK_SELECTED]->set_visible(!all_locked);
- tool_button[TOOL_LOCK_SELECTED]->set_disabled(selection.is_empty());
+ tool_button[TOOL_LOCK_SELECTED]->set_disabled(!has_node3d_item);
tool_button[TOOL_UNLOCK_SELECTED]->set_visible(all_locked);
+ tool_button[TOOL_UNLOCK_SELECTED]->set_disabled(!has_node3d_item);
tool_button[TOOL_GROUP_SELECTED]->set_visible(!all_grouped);
- tool_button[TOOL_GROUP_SELECTED]->set_disabled(selection.is_empty());
+ tool_button[TOOL_GROUP_SELECTED]->set_disabled(!has_node3d_item);
tool_button[TOOL_UNGROUP_SELECTED]->set_visible(all_grouped);
+ tool_button[TOOL_UNGROUP_SELECTED]->set_disabled(!has_node3d_item);
}
template <typename T>
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index edec4af094..43afc6f7d5 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -1892,7 +1892,7 @@ struct _ScriptEditorItemData {
if (sort_key == id.sort_key) {
return index < id.index;
} else {
- return sort_key.naturalnocasecmp_to(id.sort_key) < 0;
+ return sort_key.filenocasecmp_to(id.sort_key) < 0;
}
} else {
return category < id.category;
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 13b1c4b6ac..750e4d263f 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -1946,9 +1946,12 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
if (d.has("type") && String(d["type"]) == "obj_property") {
te->remove_secondary_carets();
+
+ bool add_literal = EDITOR_GET("text_editor/completion/add_node_path_literals");
+ String text_to_drop = add_literal ? "^" : "";
// It is unclear whether properties may contain single or double quotes.
// Assume here that double-quotes may not exist. We are escaping single-quotes if necessary.
- const String text_to_drop = _quote_drop_data(String(d["property"]));
+ text_to_drop += _quote_drop_data(String(d["property"]));
te->set_caret_line(row);
te->set_caret_column(col);
diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp
index ffc59a3429..6a66a984c0 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.cpp
+++ b/editor/plugins/skeleton_3d_editor_plugin.cpp
@@ -505,10 +505,8 @@ void Skeleton3DEditor::_file_selected(const String &p_file) {
position_max = Vector2(grest.origin.x, grest.origin.y);
position_min = Vector2(grest.origin.x, grest.origin.y);
} else {
- position_max.x = MAX(grest.origin.x, position_max.x);
- position_max.y = MAX(grest.origin.y, position_max.y);
- position_min.x = MIN(grest.origin.x, position_min.x);
- position_min.y = MIN(grest.origin.y, position_min.y);
+ position_max = position_max.max(Vector2(grest.origin.x, grest.origin.y));
+ position_min = position_min.min(Vector2(grest.origin.x, grest.origin.y));
}
}
diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp
index 8c2cb68413..c3900b8235 100644
--- a/editor/plugins/tiles/tile_atlas_view.cpp
+++ b/editor/plugins/tiles/tile_atlas_view.cpp
@@ -501,8 +501,7 @@ Vector2i TileAtlasView::get_atlas_tile_coords_at_pos(const Vector2 p_pos, bool p
// Clamp.
if (p_clamp) {
Vector2i size = tile_set_atlas_source->get_atlas_grid_size();
- ret.x = CLAMP(ret.x, 0, size.x - 1);
- ret.y = CLAMP(ret.y, 0, size.y - 1);
+ ret = ret.clamp(Vector2i(), size - Vector2i(1, 1));
}
return ret;
diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp
index 9460f9fef2..1a602568fe 100644
--- a/editor/plugins/version_control_editor_plugin.cpp
+++ b/editor/plugins/version_control_editor_plugin.cpp
@@ -93,8 +93,7 @@ void VersionControlEditorPlugin::popup_vcs_set_up_dialog(const Control *p_gui_ba
if (!available_plugins.is_empty()) {
Size2 popup_size = Size2(400, 100);
Size2 window_size = p_gui_base->get_viewport_rect().size;
- popup_size.x = MIN(window_size.x * 0.5, popup_size.x);
- popup_size.y = MIN(window_size.y * 0.5, popup_size.y);
+ popup_size = popup_size.min(window_size * 0.5);
_populate_available_vcs_names();
diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp
index 1f451487cb..bb4dce808c 100644
--- a/editor/themes/editor_theme_manager.cpp
+++ b/editor/themes/editor_theme_manager.cpp
@@ -1742,29 +1742,40 @@ 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));
- // Bottom panel.
- Ref<StyleBoxFlat> style_bottom_panel = p_config.content_panel_style->duplicate();
- style_bottom_panel->set_corner_radius_all(p_config.corner_radius * EDSCALE);
- p_theme->set_stylebox("BottomPanel", EditorStringName(EditorStyles), style_bottom_panel);
-
// Main menu.
Ref<StyleBoxFlat> menu_transparent_style = p_config.button_style->duplicate();
menu_transparent_style->set_bg_color(Color(1, 1, 1, 0));
menu_transparent_style->set_border_width_all(0);
- Ref<StyleBoxFlat> main_screen_button_transparent = menu_transparent_style->duplicate();
+ Ref<StyleBoxFlat> main_screen_button_hover = p_config.button_style_hover->duplicate();
for (int i = 0; i < 4; i++) {
- menu_transparent_style->set_content_margin((Side)i, p_config.button_style->get_margin((Side)i) + p_config.button_style->get_border_width((Side)i));
+ menu_transparent_style->set_content_margin((Side)i, p_config.button_style->get_content_margin((Side)i));
+ main_screen_button_hover->set_content_margin((Side)i, p_config.button_style_hover->get_content_margin((Side)i));
}
- p_theme->set_stylebox("MenuTransparent", EditorStringName(EditorStyles), menu_transparent_style);
- p_theme->set_stylebox("MenuHover", EditorStringName(EditorStyles), p_config.button_style_hover);
- p_theme->set_stylebox("normal", "MainScreenButton", main_screen_button_transparent);
- p_theme->set_stylebox("pressed", "MainScreenButton", main_screen_button_transparent);
- p_theme->set_stylebox("hover_pressed", "MainScreenButton", p_config.button_style_hover);
+ p_theme->set_stylebox("normal", "MainScreenButton", menu_transparent_style);
+ p_theme->set_stylebox("pressed", "MainScreenButton", menu_transparent_style);
+ p_theme->set_stylebox("hover", "MainScreenButton", main_screen_button_hover);
+ p_theme->set_stylebox("hover_pressed", "MainScreenButton", main_screen_button_hover);
+
+ p_theme->set_type_variation("MainMenuBar", "FlatMenuButton");
+ p_theme->set_stylebox("normal", "MainMenuBar", menu_transparent_style);
+ p_theme->set_stylebox("pressed", "MainMenuBar", main_screen_button_hover);
+ p_theme->set_stylebox("hover", "MainMenuBar", main_screen_button_hover);
+ p_theme->set_stylebox("hover_pressed", "MainMenuBar", main_screen_button_hover);
// Run bar.
p_theme->set_type_variation("RunBarButton", "FlatMenuButton");
p_theme->set_stylebox("disabled", "RunBarButton", menu_transparent_style);
p_theme->set_stylebox("pressed", "RunBarButton", menu_transparent_style);
+
+ // Bottom panel.
+ Ref<StyleBoxFlat> style_bottom_panel = p_config.content_panel_style->duplicate();
+ style_bottom_panel->set_corner_radius_all(p_config.corner_radius * EDSCALE);
+ p_theme->set_stylebox("BottomPanel", EditorStringName(EditorStyles), style_bottom_panel);
+ p_theme->set_type_variation("BottomPanelButton", "FlatMenuButton");
+ p_theme->set_stylebox("normal", "BottomPanelButton", menu_transparent_style);
+ p_theme->set_stylebox("pressed", "BottomPanelButton", menu_transparent_style);
+ p_theme->set_stylebox("hover_pressed", "BottomPanelButton", main_screen_button_hover);
+ p_theme->set_stylebox("hover", "BottomPanelButton", main_screen_button_hover);
}
// Editor GUI widgets.
@@ -1821,9 +1832,9 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme
Ref<StyleBoxFlat> style_flat_button_pressed = p_config.button_style_pressed->duplicate();
for (int i = 0; i < 4; i++) {
- style_flat_button->set_content_margin((Side)i, p_config.button_style->get_margin((Side)i) + p_config.button_style->get_border_width((Side)i));
- style_flat_button_hover->set_content_margin((Side)i, p_config.button_style->get_margin((Side)i) + p_config.button_style->get_border_width((Side)i));
- style_flat_button_pressed->set_content_margin((Side)i, p_config.button_style->get_margin((Side)i) + p_config.button_style->get_border_width((Side)i));
+ style_flat_button->set_content_margin((Side)i, p_config.button_style->get_content_margin((Side)i));
+ style_flat_button_hover->set_content_margin((Side)i, p_config.button_style->get_content_margin((Side)i));
+ style_flat_button_pressed->set_content_margin((Side)i, p_config.button_style->get_content_margin((Side)i));
}
Color flat_pressed_color = p_config.dark_color_1.lightened(0.24).lerp(p_config.accent_color, 0.2) * Color(0.8, 0.8, 0.8, 0.85);
if (p_config.dark_theme) {
diff --git a/main/main.cpp b/main/main.cpp
index 9215f2e848..41f82177bc 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -767,6 +767,7 @@ Error Main::test_setup() {
return OK;
}
+
// The order is the same as in `Main::cleanup()`.
void Main::test_cleanup() {
ERR_FAIL_COND(!_start_success);
@@ -978,8 +979,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
packed_data->add_pack_source(zip_packed_data);
#endif
- // Default exit code, can be modified for certain errors.
- Error exit_code = ERR_INVALID_PARAMETER;
+ // Exit error code used in the `goto error` conditions.
+ // It's returned as the program exit code. ERR_HELP is special cased and handled as success (0).
+ Error exit_err = ERR_INVALID_PARAMETER;
I = args.front();
while (I) {
@@ -1029,12 +1031,12 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
} else if (I->get() == "-h" || I->get() == "--help" || I->get() == "/?") { // display help
show_help = true;
- exit_code = ERR_HELP; // Hack to force an early exit in `main()` with a success code.
+ exit_err = ERR_HELP; // Hack to force an early exit in `main()` with a success code.
goto error;
} else if (I->get() == "--version") {
print_line(get_full_version_string());
- exit_code = ERR_HELP; // Hack to force an early exit in `main()` with a success code.
+ exit_err = ERR_HELP; // Hack to force an early exit in `main()` with a success code.
goto error;
} else if (I->get() == "-v" || I->get() == "--verbose") { // verbose output
@@ -2461,7 +2463,7 @@ error:
OS::get_singleton()->finalize_core();
locale = String();
- return exit_code;
+ return exit_err;
}
Error _parse_resource_dummy(void *p_data, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) {
@@ -3143,7 +3145,10 @@ String Main::get_rendering_driver_name() {
// everything the main loop needs to know about frame timings
static MainTimerSync main_timer_sync;
-bool Main::start() {
+// Return value should be EXIT_SUCCESS if we start successfully
+// and should move on to `OS::run`, and EXIT_FAILURE otherwise for
+// an early exit with that error code.
+int Main::start() {
ERR_FAIL_COND_V(!_start_success, false);
bool has_icon = false;
@@ -3280,7 +3285,7 @@ bool Main::start() {
{
Ref<DirAccess> da = DirAccess::open(doc_tool_path);
- ERR_FAIL_COND_V_MSG(da.is_null(), false, "Argument supplied to --doctool must be a valid directory path.");
+ ERR_FAIL_COND_V_MSG(da.is_null(), EXIT_FAILURE, "Argument supplied to --doctool must be a valid directory path.");
}
#ifndef MODULE_MONO_ENABLED
@@ -3315,11 +3320,11 @@ bool Main::start() {
// Create the module documentation directory if it doesn't exist
Ref<DirAccess> da = DirAccess::create_for_path(path);
err = da->make_dir_recursive(path);
- ERR_FAIL_COND_V_MSG(err != OK, false, "Error: Can't create directory: " + path + ": " + itos(err));
+ ERR_FAIL_COND_V_MSG(err != OK, EXIT_FAILURE, "Error: Can't create directory: " + path + ": " + itos(err));
print_line("Loading docs from: " + path);
err = docsrc.load_classes(path);
- ERR_FAIL_COND_V_MSG(err != OK, false, "Error loading docs from: " + path + ": " + itos(err));
+ ERR_FAIL_COND_V_MSG(err != OK, EXIT_FAILURE, "Error loading docs from: " + path + ": " + itos(err));
}
}
@@ -3327,11 +3332,11 @@ bool Main::start() {
// Create the main documentation directory if it doesn't exist
Ref<DirAccess> da = DirAccess::create_for_path(index_path);
err = da->make_dir_recursive(index_path);
- ERR_FAIL_COND_V_MSG(err != OK, false, "Error: Can't create index directory: " + index_path + ": " + itos(err));
+ ERR_FAIL_COND_V_MSG(err != OK, EXIT_FAILURE, "Error: Can't create index directory: " + index_path + ": " + itos(err));
print_line("Loading classes from: " + index_path);
err = docsrc.load_classes(index_path);
- ERR_FAIL_COND_V_MSG(err != OK, false, "Error loading classes from: " + index_path + ": " + itos(err));
+ ERR_FAIL_COND_V_MSG(err != OK, EXIT_FAILURE, "Error loading classes from: " + index_path + ": " + itos(err));
checked_paths.insert(index_path);
print_line("Merging docs...");
@@ -3340,20 +3345,19 @@ bool Main::start() {
for (const String &E : checked_paths) {
print_line("Erasing old docs at: " + E);
err = DocTools::erase_classes(E);
- ERR_FAIL_COND_V_MSG(err != OK, false, "Error erasing old docs at: " + E + ": " + itos(err));
+ ERR_FAIL_COND_V_MSG(err != OK, EXIT_FAILURE, "Error erasing old docs at: " + E + ": " + itos(err));
}
print_line("Generating new docs...");
err = doc.save_classes(index_path, doc_data_classes);
- ERR_FAIL_COND_V_MSG(err != OK, false, "Error saving new docs:" + itos(err));
+ ERR_FAIL_COND_V_MSG(err != OK, EXIT_FAILURE, "Error saving new docs:" + itos(err));
print_line("Deleting docs cache...");
if (FileAccess::exists(EditorHelp::get_cache_full_path())) {
DirAccess::remove_file_or_error(EditorHelp::get_cache_full_path());
}
- OS::get_singleton()->set_exit_code(EXIT_SUCCESS);
- return false;
+ return EXIT_SUCCESS;
}
// GDExtension API and interface.
@@ -3368,32 +3372,24 @@ bool Main::start() {
}
if (dump_gdextension_interface || dump_extension_api) {
- OS::get_singleton()->set_exit_code(EXIT_SUCCESS);
- return false;
+ return EXIT_SUCCESS;
}
if (validate_extension_api) {
Engine::get_singleton()->set_editor_hint(true); // "extension_api.json" should always contains editor singletons.
bool valid = GDExtensionAPIDump::validate_extension_json_file(validate_extension_api_file) == OK;
- OS::get_singleton()->set_exit_code(valid ? EXIT_SUCCESS : EXIT_FAILURE);
- return false;
+ return valid ? EXIT_SUCCESS : EXIT_FAILURE;
}
}
#ifndef DISABLE_DEPRECATED
if (converting_project) {
int ret = ProjectConverter3To4(converter_max_kb_file, converter_max_line_length).convert();
- if (ret) {
- OS::get_singleton()->set_exit_code(EXIT_SUCCESS);
- }
- return false;
+ return ret ? EXIT_SUCCESS : EXIT_FAILURE;
}
if (validating_converting_project) {
bool ret = ProjectConverter3To4(converter_max_kb_file, converter_max_line_length).validate_conversion();
- if (ret) {
- OS::get_singleton()->set_exit_code(EXIT_SUCCESS);
- }
- return false;
+ return ret ? EXIT_SUCCESS : EXIT_FAILURE;
}
#endif // DISABLE_DEPRECATED
@@ -3410,7 +3406,7 @@ bool Main::start() {
// this might end up triggered by valid usage, in which case we'll have to
// fine-tune further.
OS::get_singleton()->alert("Couldn't detect whether to run the editor, the project manager or a specific project. Aborting.");
- ERR_FAIL_V_MSG(false, "Couldn't detect whether to run the editor, the project manager or a specific project. Aborting.");
+ ERR_FAIL_V_MSG(EXIT_FAILURE, "Couldn't detect whether to run the editor, the project manager or a specific project. Aborting.");
}
#endif
@@ -3424,15 +3420,10 @@ bool Main::start() {
if (!script.is_empty()) {
Ref<Script> script_res = ResourceLoader::load(script);
- ERR_FAIL_COND_V_MSG(script_res.is_null(), false, "Can't load script: " + script);
+ ERR_FAIL_COND_V_MSG(script_res.is_null(), EXIT_FAILURE, "Can't load script: " + script);
if (check_only) {
- if (!script_res->is_valid()) {
- OS::get_singleton()->set_exit_code(EXIT_FAILURE);
- } else {
- OS::get_singleton()->set_exit_code(EXIT_SUCCESS);
- }
- return false;
+ return script_res->is_valid() ? EXIT_SUCCESS : EXIT_FAILURE;
}
if (script_res->can_instantiate()) {
@@ -3444,13 +3435,13 @@ bool Main::start() {
memdelete(obj);
}
OS::get_singleton()->alert(vformat("Can't load the script \"%s\" as it doesn't inherit from SceneTree or MainLoop.", script));
- ERR_FAIL_V_MSG(false, vformat("Can't load the script \"%s\" as it doesn't inherit from SceneTree or MainLoop.", script));
+ ERR_FAIL_V_MSG(EXIT_FAILURE, vformat("Can't load the script \"%s\" as it doesn't inherit from SceneTree or MainLoop.", script));
}
script_loop->set_script(script_res);
main_loop = script_loop;
} else {
- return false;
+ return EXIT_FAILURE;
}
} else { // Not based on script path.
if (!editor && !ClassDB::class_exists(main_loop_type) && ScriptServer::is_global_class(main_loop_type)) {
@@ -3458,7 +3449,7 @@ bool Main::start() {
Ref<Script> script_res = ResourceLoader::load(script_path);
if (script_res.is_null()) {
OS::get_singleton()->alert("Error: Could not load MainLoop script type: " + main_loop_type);
- ERR_FAIL_V_MSG(false, vformat("Could not load global class %s.", main_loop_type));
+ ERR_FAIL_V_MSG(EXIT_FAILURE, vformat("Could not load global class %s.", main_loop_type));
}
StringName script_base = script_res->get_instance_base_type();
Object *obj = ClassDB::instantiate(script_base);
@@ -3468,7 +3459,7 @@ bool Main::start() {
memdelete(obj);
}
OS::get_singleton()->alert("Error: Invalid MainLoop script base type: " + script_base);
- ERR_FAIL_V_MSG(false, vformat("The global class %s does not inherit from SceneTree or MainLoop.", main_loop_type));
+ ERR_FAIL_V_MSG(EXIT_FAILURE, vformat("The global class %s does not inherit from SceneTree or MainLoop.", main_loop_type));
}
script_loop->set_script(script_res);
main_loop = script_loop;
@@ -3482,15 +3473,15 @@ bool Main::start() {
if (!main_loop) {
if (!ClassDB::class_exists(main_loop_type)) {
OS::get_singleton()->alert("Error: MainLoop type doesn't exist: " + main_loop_type);
- return false;
+ return EXIT_FAILURE;
} else {
Object *ml = ClassDB::instantiate(main_loop_type);
- ERR_FAIL_NULL_V_MSG(ml, false, "Can't instance MainLoop type.");
+ ERR_FAIL_NULL_V_MSG(ml, EXIT_FAILURE, "Can't instance MainLoop type.");
main_loop = Object::cast_to<MainLoop>(ml);
if (!main_loop) {
memdelete(ml);
- ERR_FAIL_V_MSG(false, "Invalid MainLoop type.");
+ ERR_FAIL_V_MSG(EXIT_FAILURE, "Invalid MainLoop type.");
}
}
}
@@ -3614,7 +3605,7 @@ bool Main::start() {
Error err;
Vector<String> paths = get_files_with_extension(gdscript_docs_path, "gd");
- ERR_FAIL_COND_V_MSG(paths.is_empty(), false, "Couldn't find any GDScript files under the given directory: " + gdscript_docs_path);
+ ERR_FAIL_COND_V_MSG(paths.is_empty(), EXIT_FAILURE, "Couldn't find any GDScript files under the given directory: " + gdscript_docs_path);
for (const String &path : paths) {
Ref<GDScript> gdscript = ResourceLoader::load(path);
@@ -3629,14 +3620,13 @@ bool Main::start() {
Ref<DirAccess> da = DirAccess::create_for_path(doc_tool_path);
err = da->make_dir_recursive(doc_tool_path);
- ERR_FAIL_COND_V_MSG(err != OK, false, "Error: Can't create GDScript docs directory: " + doc_tool_path + ": " + itos(err));
+ ERR_FAIL_COND_V_MSG(err != OK, EXIT_FAILURE, "Error: Can't create GDScript docs directory: " + doc_tool_path + ": " + itos(err));
HashMap<String, String> doc_data_classes;
err = docs.save_classes(doc_tool_path, doc_data_classes, false);
- ERR_FAIL_COND_V_MSG(err != OK, false, "Error saving GDScript docs:" + itos(err));
+ ERR_FAIL_COND_V_MSG(err != OK, EXIT_FAILURE, "Error saving GDScript docs:" + itos(err));
- OS::get_singleton()->set_exit_code(EXIT_SUCCESS);
- return false;
+ return EXIT_SUCCESS;
}
#endif // MODULE_GDSCRIPT_ENABLED
@@ -3751,7 +3741,7 @@ bool Main::start() {
if (sep == -1) {
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- ERR_FAIL_COND_V(da.is_null(), false);
+ ERR_FAIL_COND_V(da.is_null(), EXIT_FAILURE);
local_game_path = da->get_current_dir().path_join(local_game_path);
} else {
@@ -3801,7 +3791,7 @@ bool Main::start() {
scene = scenedata->instantiate();
}
- ERR_FAIL_NULL_V_MSG(scene, false, "Failed loading scene: " + local_game_path + ".");
+ ERR_FAIL_NULL_V_MSG(scene, EXIT_FAILURE, "Failed loading scene: " + local_game_path + ".");
sml->add_current_scene(scene);
#ifdef MACOS_ENABLED
@@ -3874,7 +3864,7 @@ bool Main::start() {
OS::get_singleton()->benchmark_end_measure("Startup", "Total");
OS::get_singleton()->benchmark_dump();
- return true;
+ return EXIT_SUCCESS;
}
/* Main iteration
@@ -3904,10 +3894,10 @@ static uint64_t physics_process_max = 0;
static uint64_t process_max = 0;
static uint64_t navigation_process_max = 0;
+// Return false means iterating further, returning true means `OS::run`
+// will terminate the program. In case of failure, the OS exit code needs
+// to be set explicitly here (defaults to EXIT_SUCCESS).
bool Main::iteration() {
- //for now do not error on this
- //ERR_FAIL_COND_V(iterating, false);
-
iterating++;
const uint64_t ticks = OS::get_singleton()->get_ticks_usec();
@@ -3967,6 +3957,11 @@ bool Main::iteration() {
PhysicsServer3D::get_singleton()->flush_queries();
#endif // _3D_DISABLED
+ // Prepare the fixed timestep interpolated nodes BEFORE they are updated
+ // by the physics server, otherwise the current and previous transforms
+ // may be the same, and no interpolation takes place.
+ OS::get_singleton()->get_main_loop()->iteration_prepare();
+
PhysicsServer2D::get_singleton()->sync();
PhysicsServer2D::get_singleton()->flush_queries();
diff --git a/main/main.h b/main/main.h
index 062af73d57..ff0fba6b51 100644
--- a/main/main.h
+++ b/main/main.h
@@ -35,7 +35,7 @@
#include "core/os/thread.h"
#include "core/typedefs.h"
-template <class T>
+template <typename T>
class Vector;
class Main {
@@ -78,7 +78,7 @@ public:
static Error test_setup();
static void test_cleanup();
#endif
- static bool start();
+ static int start();
static bool iteration();
static void force_redraw();
diff --git a/main/main_timer_sync.cpp b/main/main_timer_sync.cpp
index 569930d427..d358d9fa93 100644
--- a/main/main_timer_sync.cpp
+++ b/main/main_timer_sync.cpp
@@ -299,6 +299,17 @@ int64_t MainTimerSync::DeltaSmoother::smooth_delta(int64_t p_delta) {
// before advance_core considers changing the physics_steps return from
// the typical values as defined by typical_physics_steps
double MainTimerSync::get_physics_jitter_fix() {
+ // Turn off jitter fix when using fixed timestep interpolation.
+ // Note this shouldn't be on UNTIL 3d interpolation is implemented,
+ // otherwise we will get people making 3d games with the physics_interpolation
+ // set to on getting jitter fix disabled unexpectedly.
+#if 0
+ if (Engine::get_singleton()->is_physics_interpolation_enabled()) {
+ // Would be better to write a simple bypass for jitter fix but this will do to get started.
+ return 0.0;
+ }
+#endif
+
return Engine::get_singleton()->get_physics_jitter_fix();
}
diff --git a/misc/scripts/copyright_headers.py b/misc/scripts/copyright_headers.py
index b60eb32289..82a4477cc0 100755
--- a/misc/scripts/copyright_headers.py
+++ b/misc/scripts/copyright_headers.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
+import os
import sys
header = """\
@@ -35,58 +36,61 @@ header = """\
/**************************************************************************/
"""
-fname = sys.argv[1]
+if len(sys.argv) < 2:
+ print("Invalid usage of copyright_headers.py, it should be called with a path to one or multiple files.")
+ sys.exit(1)
-# Handle replacing $filename with actual filename and keep alignment
-fsingle = fname.strip()
-if fsingle.find("/") != -1:
- fsingle = fsingle[fsingle.rfind("/") + 1 :]
-rep_fl = "$filename"
-rep_fi = fsingle
-len_fl = len(rep_fl)
-len_fi = len(rep_fi)
-# Pad with spaces to keep alignment
-if len_fi < len_fl:
- for x in range(len_fl - len_fi):
- rep_fi += " "
-elif len_fl < len_fi:
- for x in range(len_fi - len_fl):
- rep_fl += " "
-if header.find(rep_fl) != -1:
- text = header.replace(rep_fl, rep_fi)
-else:
- text = header.replace("$filename", fsingle)
-text += "\n"
+for f in sys.argv[1:]:
+ fname = f
-# We now have the proper header, so we want to ignore the one in the original file
-# and potentially empty lines and badly formatted lines, while keeping comments that
-# come after the header, and then keep everything non-header unchanged.
-# To do so, we skip empty lines that may be at the top in a first pass.
-# In a second pass, we skip all consecutive comment lines starting with "/*",
-# then we can append the rest (step 2).
+ # Handle replacing $filename with actual filename and keep alignment
+ fsingle = os.path.basename(fname.strip())
+ rep_fl = "$filename"
+ rep_fi = fsingle
+ len_fl = len(rep_fl)
+ len_fi = len(rep_fi)
+ # Pad with spaces to keep alignment
+ if len_fi < len_fl:
+ for x in range(len_fl - len_fi):
+ rep_fi += " "
+ elif len_fl < len_fi:
+ for x in range(len_fi - len_fl):
+ rep_fl += " "
+ if header.find(rep_fl) != -1:
+ text = header.replace(rep_fl, rep_fi)
+ else:
+ text = header.replace("$filename", fsingle)
+ text += "\n"
-with open(fname.strip(), "r") as fileread:
- line = fileread.readline()
- header_done = False
+ # We now have the proper header, so we want to ignore the one in the original file
+ # and potentially empty lines and badly formatted lines, while keeping comments that
+ # come after the header, and then keep everything non-header unchanged.
+ # To do so, we skip empty lines that may be at the top in a first pass.
+ # In a second pass, we skip all consecutive comment lines starting with "/*",
+ # then we can append the rest (step 2).
- while line.strip() == "": # Skip empty lines at the top
+ with open(fname.strip(), "r") as fileread:
line = fileread.readline()
+ header_done = False
- if line.find("/**********") == -1: # Godot header starts this way
- # Maybe starting with a non-Godot comment, abort header magic
- header_done = True
+ while line.strip() == "": # Skip empty lines at the top
+ line = fileread.readline()
- while not header_done: # Handle header now
- if line.find("/*") != 0: # No more starting with a comment
+ if line.find("/**********") == -1: # Godot header starts this way
+ # Maybe starting with a non-Godot comment, abort header magic
header_done = True
- if line.strip() != "":
- text += line
- line = fileread.readline()
- while line != "": # Dump everything until EOF
- text += line
- line = fileread.readline()
+ while not header_done: # Handle header now
+ if line.find("/*") != 0: # No more starting with a comment
+ header_done = True
+ if line.strip() != "":
+ text += line
+ line = fileread.readline()
+
+ while line != "": # Dump everything until EOF
+ text += line
+ line = fileread.readline()
-# Write
-with open(fname.strip(), "w", encoding="utf-8", newline="\n") as filewrite:
- filewrite.write(text)
+ # Write
+ with open(fname.strip(), "w", encoding="utf-8", newline="\n") as filewrite:
+ filewrite.write(text)
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index d98580b771..4869573972 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -43,6 +43,7 @@
assert(speed &gt;= 0 and speed &lt; 20) # You can also combine the two conditional statements in one check.
assert(speed &lt; 20, "the speed limit is 20") # Show a message.
[/codeblock]
+ [b]Note:[/b] [method assert] is a keyword, not a function. So you cannot access it as a [Callable] or use it inside expressions.
</description>
</method>
<method name="char">
@@ -131,7 +132,7 @@
- A constant from the [enum Variant.Type] enumeration, for example [constant TYPE_INT].
- An [Object]-derived class which exists in [ClassDB], for example [Node].
- A [Script] (you can use any class, including inner one).
- Unlike the right operand of the [code]is[/code] operator, [param type] can be a non-constant value. The [code]is[/code] operator supports more features (such as typed arrays) and is more performant. Use the operator instead of this method if you do not need dynamic type checking.
+ Unlike the right operand of the [code]is[/code] operator, [param type] can be a non-constant value. The [code]is[/code] operator supports more features (such as typed arrays). Use the operator instead of this method if you do not need dynamic type checking.
Examples:
[codeblock]
print(is_instance_of(a, TYPE_INT))
@@ -183,6 +184,7 @@
# Create instance of a scene.
var diamond = preload("res://diamond.tscn").instantiate()
[/codeblock]
+ [b]Note:[/b] [method preload] is a keyword, not a function. So you cannot access it as a [Callable].
</description>
</method>
<method name="print_debug" qualifiers="vararg">
@@ -717,6 +719,8 @@
<return type="void" />
<description>
Make a script with static variables to not persist after all references are lost. If the script is loaded again the static variables will revert to their default values.
+ [b]Note:[/b] As annotations describe their subject, the [annotation @static_unload] annotation must be placed before the class definition and inheritance.
+ [b]Warning:[/b] Currently, due to a bug, scripts are never freed, even if [annotation @static_unload] annotation is used.
</description>
</annotation>
<annotation name="@tool">
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 12ff22c878..854c944e14 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -2665,6 +2665,8 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
GDScriptParser::DataType base_type = p_base.type;
const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\"";
+ const bool use_string_names = EDITOR_GET("text_editor/completion/add_string_name_literals");
+ const bool use_node_paths = EDITOR_GET("text_editor/completion/add_node_path_literals");
while (base_type.is_set() && !base_type.is_variant()) {
switch (base_type.kind) {
@@ -2698,8 +2700,14 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
List<String> options;
obj->get_argument_options(p_method, p_argidx, &options);
for (String &opt : options) {
+ // Handle user preference.
if (opt.is_quoted()) {
- opt = opt.unquote().quote(quote_style); // Handle user preference.
+ opt = opt.unquote().quote(quote_style);
+ if (use_string_names && info.arguments[p_argidx].type == Variant::STRING_NAME) {
+ opt = opt.indent("&");
+ } else if (use_node_paths && info.arguments[p_argidx].type == Variant::NODE_PATH) {
+ opt = opt.indent("^");
+ }
}
ScriptLanguage::CodeCompletionOption option(opt, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION);
r_result.insert(option.display, option);
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 8d98c0b11c..d706f4e9a3 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -856,7 +856,7 @@ void GDScriptParser::parse_extends() {
}
}
-template <class T>
+template <typename T>
void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static) {
advance();
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 583b60bf16..ea67f1eaff 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -774,7 +774,7 @@ public:
bool has_function(const StringName &p_name) const {
return has_member(p_name) && members[members_indices[p_name]].type == Member::FUNCTION;
}
- template <class T>
+ template <typename T>
void add_member(T *p_member_node) {
members_indices[p_member_node->identifier->name] = members.size();
members.push_back(Member(p_member_node));
@@ -1167,7 +1167,7 @@ public:
bool has_local(const StringName &p_name) const;
const Local &get_local(const StringName &p_name) const;
- template <class T>
+ template <typename T>
void add_local(T *p_local, FunctionNode *p_source_function) {
locals_indices[p_local->identifier->name] = locals.size();
locals.push_back(Local(p_local, p_source_function));
@@ -1426,7 +1426,7 @@ private:
void reset_extents(Node *p_node, GDScriptTokenizer::Token p_token);
void reset_extents(Node *p_node, Node *p_from);
- template <class T>
+ template <typename T>
T *alloc_node() {
T *node = memnew(T);
@@ -1473,7 +1473,7 @@ private:
void parse_class_name();
void parse_extends();
void parse_class_body(bool p_is_multiline);
- template <class T>
+ template <typename T>
void parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static = false);
SignalNode *parse_signal(bool p_is_static);
EnumNode *parse_enum(bool p_is_static);
diff --git a/modules/gdscript/tests/scripts/runtime/features/emit_after_await.gd b/modules/gdscript/tests/scripts/runtime/features/emit_after_await.gd
new file mode 100644
index 0000000000..21fd526acc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/emit_after_await.gd
@@ -0,0 +1,12 @@
+# https://github.com/godotengine/godot/issues/89439
+extends Node
+
+signal my_signal
+
+func async_func():
+ await my_signal
+ my_signal.emit()
+
+func test():
+ async_func()
+ my_signal.emit()
diff --git a/modules/gdscript/tests/scripts/runtime/features/emit_after_await.out b/modules/gdscript/tests/scripts/runtime/features/emit_after_await.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/emit_after_await.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/runtime/features/emit_one_shot_is_non_recursive.gd b/modules/gdscript/tests/scripts/runtime/features/emit_one_shot_is_non_recursive.gd
new file mode 100644
index 0000000000..5c328dcfcd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/emit_one_shot_is_non_recursive.gd
@@ -0,0 +1,22 @@
+# https://github.com/godotengine/godot/issues/89439
+
+signal my_signal
+
+func foo():
+ print("Foo")
+ my_signal.emit()
+
+func bar():
+ print("Bar")
+
+func baz():
+ print("Baz")
+
+func test():
+ @warning_ignore("return_value_discarded")
+ my_signal.connect(foo, CONNECT_ONE_SHOT)
+ @warning_ignore("return_value_discarded")
+ my_signal.connect(bar, CONNECT_ONE_SHOT)
+ @warning_ignore("return_value_discarded")
+ my_signal.connect(baz)
+ my_signal.emit()
diff --git a/modules/gdscript/tests/scripts/runtime/features/emit_one_shot_is_non_recursive.out b/modules/gdscript/tests/scripts/runtime/features/emit_one_shot_is_non_recursive.out
new file mode 100644
index 0000000000..3399e08878
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/emit_one_shot_is_non_recursive.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+Foo
+Baz
+Bar
+Baz
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index b53be7f855..6e7ca370dd 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -884,42 +884,40 @@ Error GLTFDocument::_encode_accessors(Ref<GLTFState> p_state) {
d["componentType"] = accessor->component_type;
d["count"] = accessor->count;
d["type"] = _get_accessor_type_name(accessor->type);
- d["byteOffset"] = accessor->byte_offset;
d["normalized"] = accessor->normalized;
d["max"] = accessor->max;
d["min"] = accessor->min;
- d["bufferView"] = accessor->buffer_view; //optional because it may be sparse...
-
- // Dictionary s;
- // s["count"] = accessor->sparse_count;
- // ERR_FAIL_COND_V(!s.has("count"), ERR_PARSE_ERROR);
-
- // s["indices"] = accessor->sparse_accessors;
- // ERR_FAIL_COND_V(!s.has("indices"), ERR_PARSE_ERROR);
-
- // Dictionary si;
+ if (accessor->buffer_view != -1) {
+ // bufferView may be omitted to zero-initialize the buffer. When this happens, byteOffset MUST also be omitted.
+ d["byteOffset"] = accessor->byte_offset;
+ d["bufferView"] = accessor->buffer_view;
+ }
- // si["bufferView"] = accessor->sparse_indices_buffer_view;
+ if (accessor->sparse_count > 0) {
+ Dictionary s;
+ s["count"] = accessor->sparse_count;
- // ERR_FAIL_COND_V(!si.has("bufferView"), ERR_PARSE_ERROR);
- // si["componentType"] = accessor->sparse_indices_component_type;
+ Dictionary si;
+ si["bufferView"] = accessor->sparse_indices_buffer_view;
+ si["componentType"] = accessor->sparse_indices_component_type;
+ if (accessor->sparse_indices_byte_offset != -1) {
+ si["byteOffset"] = accessor->sparse_indices_byte_offset;
+ }
+ ERR_FAIL_COND_V(!si.has("bufferView") || !si.has("componentType"), ERR_PARSE_ERROR);
+ s["indices"] = si;
- // if (si.has("byteOffset")) {
- // si["byteOffset"] = accessor->sparse_indices_byte_offset;
- // }
+ Dictionary sv;
+ sv["bufferView"] = accessor->sparse_values_buffer_view;
+ if (accessor->sparse_values_byte_offset != -1) {
+ sv["byteOffset"] = accessor->sparse_values_byte_offset;
+ }
+ ERR_FAIL_COND_V(!sv.has("bufferView"), ERR_PARSE_ERROR);
+ s["values"] = sv;
- // ERR_FAIL_COND_V(!si.has("componentType"), ERR_PARSE_ERROR);
- // s["indices"] = si;
- // Dictionary sv;
+ ERR_FAIL_COND_V(!s.has("count") || !s.has("indices") || !s.has("values"), ERR_PARSE_ERROR);
+ d["sparse"] = s;
+ }
- // sv["bufferView"] = accessor->sparse_values_buffer_view;
- // if (sv.has("byteOffset")) {
- // sv["byteOffset"] = accessor->sparse_values_byte_offset;
- // }
- // ERR_FAIL_COND_V(!sv.has("bufferView"), ERR_PARSE_ERROR);
- // s["values"] = sv;
- // ERR_FAIL_COND_V(!s.has("values"), ERR_PARSE_ERROR);
- // d["sparse"] = s;
accessors.push_back(d);
}
@@ -1026,8 +1024,6 @@ Error GLTFDocument::_parse_accessors(Ref<GLTFState> p_state) {
}
if (d.has("sparse")) {
- //eeh..
-
const Dictionary &s = d["sparse"];
ERR_FAIL_COND_V(!s.has("count"), ERR_PARSE_ERROR);
@@ -1143,7 +1139,7 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
const uint32_t offset = bv->byte_offset = p_byte_offset;
Vector<uint8_t> &gltf_buffer = p_state->buffers.write[0];
- int stride = _get_component_type_size(p_component_type);
+ int stride = component_count * component_size;
if (p_for_vertex && stride % 4) {
stride += 4 - (stride % 4); //according to spec must be multiple of 4
}
@@ -1152,13 +1148,14 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
print_verbose("glTF: encoding accessor offset " + itos(p_byte_offset) + " view offset: " + itos(bv->byte_offset) + " total buffer len: " + itos(gltf_buffer.size()) + " view len " + itos(bv->byte_length));
- const int buffer_end = (stride * (p_count - 1)) + _get_component_type_size(p_component_type);
+ const int buffer_end = (stride * (p_count - 1)) + component_size;
// TODO define bv->byte_stride
bv->byte_offset = gltf_buffer.size();
if (p_for_vertex_indices) {
bv->indices = true;
} else if (p_for_vertex) {
bv->vertex_attributes = true;
+ bv->byte_stride = stride;
}
switch (p_component_type) {
@@ -1300,6 +1297,11 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
ERR_FAIL_COND_V(buffer_end > bv->byte_length, ERR_INVALID_DATA);
ERR_FAIL_COND_V((int)(offset + buffer_end) > gltf_buffer.size(), ERR_INVALID_DATA);
+ int pad_bytes = (4 - gltf_buffer.size()) & 3;
+ for (int i = 0; i < pad_bytes; i++) {
+ gltf_buffer.push_back(0);
+ }
+
r_accessor = bv->buffer = p_state->buffer_views.size();
p_state->buffer_views.push_back(bv);
return OK;
@@ -1519,8 +1521,12 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(Ref<GLTFState> p_state,
type_max.resize(element_count);
Vector<double> type_min;
type_min.resize(element_count);
+ int max_index = 0;
for (int i = 0; i < p_attribs.size(); i++) {
attribs.write[i] = p_attribs[i];
+ if (p_attribs[i] > max_index) {
+ max_index = p_attribs[i];
+ }
if (i == 0) {
for (int32_t type_i = 0; type_i < element_count; type_i++) {
type_max.write[type_i] = attribs[(i * element_count) + type_i];
@@ -1539,7 +1545,12 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(Ref<GLTFState> p_state,
GLTFBufferIndex buffer_view_i;
int64_t size = p_state->buffers[0].size();
const GLTFType type = GLTFType::TYPE_SCALAR;
- const int component_type = GLTFDocument::COMPONENT_TYPE_INT;
+ int component_type;
+ if (max_index > 65535 || p_for_vertex) {
+ component_type = GLTFDocument::COMPONENT_TYPE_INT;
+ } else {
+ component_type = GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT;
+ }
accessor->max = type_max;
accessor->min = type_min;
@@ -1557,7 +1568,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(Ref<GLTFState> p_state,
return p_state->accessors.size() - 1;
}
-Vector<int> GLTFDocument::_decode_accessor_as_ints(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
+Vector<int> GLTFDocument::_decode_accessor_as_ints(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex, const Vector<int> &p_packed_vertex_ids) {
const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex);
Vector<int> ret;
@@ -1566,17 +1577,23 @@ Vector<int> GLTFDocument::_decode_accessor_as_ints(Ref<GLTFState> p_state, const
}
const double *attribs_ptr = attribs.ptr();
- const int ret_size = attribs.size();
+ int ret_size = attribs.size();
+ if (!p_packed_vertex_ids.is_empty()) {
+ ERR_FAIL_COND_V(p_packed_vertex_ids[p_packed_vertex_ids.size() - 1] >= ret_size, ret);
+ ret_size = p_packed_vertex_ids.size();
+ }
ret.resize(ret_size);
- {
- for (int i = 0; i < ret_size; i++) {
- ret.write[i] = int(attribs_ptr[i]);
+ for (int i = 0; i < ret_size; i++) {
+ int src_i = i;
+ if (!p_packed_vertex_ids.is_empty()) {
+ src_i = p_packed_vertex_ids[i];
}
+ ret.write[i] = int(attribs_ptr[src_i]);
}
return ret;
}
-Vector<float> GLTFDocument::_decode_accessor_as_floats(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
+Vector<float> GLTFDocument::_decode_accessor_as_floats(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex, const Vector<int> &p_packed_vertex_ids) {
const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex);
Vector<float> ret;
@@ -1585,12 +1602,18 @@ Vector<float> GLTFDocument::_decode_accessor_as_floats(Ref<GLTFState> p_state, c
}
const double *attribs_ptr = attribs.ptr();
- const int ret_size = attribs.size();
+ int ret_size = attribs.size();
+ if (!p_packed_vertex_ids.is_empty()) {
+ ERR_FAIL_COND_V(p_packed_vertex_ids[p_packed_vertex_ids.size() - 1] >= ret_size, ret);
+ ret_size = p_packed_vertex_ids.size();
+ }
ret.resize(ret_size);
- {
- for (int i = 0; i < ret_size; i++) {
- ret.write[i] = float(attribs_ptr[i]);
+ for (int i = 0; i < ret_size; i++) {
+ int src_i = i;
+ if (!p_packed_vertex_ids.is_empty()) {
+ src_i = p_packed_vertex_ids[i];
}
+ ret.write[i] = float(attribs_ptr[src_i]);
}
return ret;
}
@@ -1863,7 +1886,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_quaternions(Ref<GLTFState> p
return p_state->accessors.size() - 1;
}
-Vector<Vector2> GLTFDocument::_decode_accessor_as_vec2(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
+Vector<Vector2> GLTFDocument::_decode_accessor_as_vec2(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex, const Vector<int> &p_packed_vertex_ids) {
const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex);
Vector<Vector2> ret;
@@ -1873,12 +1896,18 @@ Vector<Vector2> GLTFDocument::_decode_accessor_as_vec2(Ref<GLTFState> p_state, c
ERR_FAIL_COND_V(attribs.size() % 2 != 0, ret);
const double *attribs_ptr = attribs.ptr();
- const int ret_size = attribs.size() / 2;
+ int ret_size = attribs.size() / 2;
+ if (!p_packed_vertex_ids.is_empty()) {
+ ERR_FAIL_COND_V(p_packed_vertex_ids[p_packed_vertex_ids.size() - 1] >= ret_size, ret);
+ ret_size = p_packed_vertex_ids.size();
+ }
ret.resize(ret_size);
- {
- for (int i = 0; i < ret_size; i++) {
- ret.write[i] = Vector2(attribs_ptr[i * 2 + 0], attribs_ptr[i * 2 + 1]);
+ for (int i = 0; i < ret_size; i++) {
+ int src_i = i;
+ if (!p_packed_vertex_ids.is_empty()) {
+ src_i = p_packed_vertex_ids[i];
}
+ ret.write[i] = Vector2(attribs_ptr[src_i * 2 + 0], attribs_ptr[src_i * 2 + 1]);
}
return ret;
}
@@ -1976,6 +2005,112 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec3(Ref<GLTFState> p_state,
return p_state->accessors.size() - 1;
}
+GLTFAccessorIndex GLTFDocument::_encode_sparse_accessor_as_vec3(Ref<GLTFState> p_state, const Vector<Vector3> p_attribs, const Vector<Vector3> p_reference_attribs, const float p_reference_multiplier, const bool p_for_vertex, const GLTFAccessorIndex p_reference_accessor) {
+ if (p_attribs.size() == 0) {
+ return -1;
+ }
+
+ const int element_count = 3;
+ Vector<double> attribs;
+ Vector<double> type_max;
+ Vector<double> type_min;
+ attribs.resize(p_attribs.size() * element_count);
+ type_max.resize(element_count);
+ type_min.resize(element_count);
+
+ Vector<double> changed_indices;
+ Vector<double> changed_values;
+ int max_changed_index = 0;
+
+ for (int i = 0; i < p_attribs.size(); i++) {
+ Vector3 attrib = p_attribs[i];
+ bool is_different = false;
+ if (i < p_reference_attribs.size()) {
+ is_different = !(attrib * p_reference_multiplier).is_equal_approx(p_reference_attribs[i]);
+ if (!is_different) {
+ attrib = p_reference_attribs[i];
+ }
+ } else {
+ is_different = !(attrib * p_reference_multiplier).is_zero_approx();
+ if (!is_different) {
+ attrib = Vector3();
+ }
+ }
+ attribs.write[(i * element_count) + 0] = _filter_number(attrib.x);
+ attribs.write[(i * element_count) + 1] = _filter_number(attrib.y);
+ attribs.write[(i * element_count) + 2] = _filter_number(attrib.z);
+ if (is_different) {
+ changed_indices.push_back(i);
+ if (i > max_changed_index) {
+ max_changed_index = i;
+ }
+ changed_values.push_back(_filter_number(attrib.x));
+ changed_values.push_back(_filter_number(attrib.y));
+ changed_values.push_back(_filter_number(attrib.z));
+ }
+ _calc_accessor_min_max(i, element_count, type_max, attribs, type_min);
+ }
+ _round_min_max_components(type_min, type_max);
+
+ if (attribs.size() % element_count != 0) {
+ return -1;
+ }
+
+ Ref<GLTFAccessor> sparse_accessor;
+ sparse_accessor.instantiate();
+ int64_t size = p_state->buffers[0].size();
+ const GLTFType type = GLTFType::TYPE_VEC3;
+ const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
+
+ sparse_accessor->normalized = false;
+ sparse_accessor->count = p_attribs.size();
+ sparse_accessor->type = type;
+ sparse_accessor->component_type = component_type;
+ if (p_reference_accessor < p_state->accessors.size() && p_reference_accessor >= 0 && p_state->accessors[p_reference_accessor].is_valid()) {
+ sparse_accessor->byte_offset = p_state->accessors[p_reference_accessor]->byte_offset;
+ sparse_accessor->buffer_view = p_state->accessors[p_reference_accessor]->buffer_view;
+ }
+ sparse_accessor->max = type_max;
+ sparse_accessor->min = type_min;
+ int sparse_accessor_index_stride = max_changed_index > 65535 ? 4 : 2;
+
+ int sparse_accessor_storage_size = changed_indices.size() * (sparse_accessor_index_stride + element_count * sizeof(float));
+ int conventional_storage_size = p_attribs.size() * element_count * sizeof(float);
+
+ if (changed_indices.size() > 0 && sparse_accessor_storage_size < conventional_storage_size) {
+ // It must be worthwhile to use a sparse accessor.
+
+ 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;
+ } else {
+ sparse_accessor->sparse_indices_component_type = GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT;
+ }
+ if (_encode_buffer_view(p_state, changed_indices.ptr(), changed_indices.size(), GLTFType::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;
+ }
+ // We use changed_indices.size() here, because we must pass the number of vec3 values rather than the number of components.
+ if (_encode_buffer_view(p_state, changed_values.ptr(), changed_indices.size(), sparse_accessor->type, sparse_accessor->component_type, sparse_accessor->normalized, sparse_accessor->sparse_values_byte_offset, false, buffer_view_i_values) != OK) {
+ return -1;
+ }
+ sparse_accessor->sparse_indices_buffer_view = buffer_view_i_indices;
+ sparse_accessor->sparse_values_buffer_view = buffer_view_i_values;
+ sparse_accessor->sparse_count = changed_indices.size();
+ } else if (changed_indices.size() > 0) {
+ GLTFBufferIndex buffer_view_i;
+ sparse_accessor->byte_offset = 0;
+ Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), type, component_type, sparse_accessor->normalized, size, p_for_vertex, buffer_view_i);
+ if (err != OK) {
+ return -1;
+ }
+ sparse_accessor->buffer_view = buffer_view_i;
+ }
+ p_state->accessors.push_back(sparse_accessor);
+
+ return p_state->accessors.size() - 1;
+}
+
GLTFAccessorIndex GLTFDocument::_encode_accessor_as_xform(Ref<GLTFState> p_state, const Vector<Transform3D> p_attribs, const bool p_for_vertex) {
if (p_attribs.size() == 0) {
return -1;
@@ -2045,7 +2180,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_xform(Ref<GLTFState> p_state
return p_state->accessors.size() - 1;
}
-Vector<Vector3> GLTFDocument::_decode_accessor_as_vec3(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
+Vector<Vector3> GLTFDocument::_decode_accessor_as_vec3(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex, const Vector<int> &p_packed_vertex_ids) {
const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex);
Vector<Vector3> ret;
@@ -2055,17 +2190,23 @@ Vector<Vector3> GLTFDocument::_decode_accessor_as_vec3(Ref<GLTFState> p_state, c
ERR_FAIL_COND_V(attribs.size() % 3 != 0, ret);
const double *attribs_ptr = attribs.ptr();
- const int ret_size = attribs.size() / 3;
+ int ret_size = attribs.size() / 3;
+ if (!p_packed_vertex_ids.is_empty()) {
+ ERR_FAIL_COND_V(p_packed_vertex_ids[p_packed_vertex_ids.size() - 1] >= ret_size, ret);
+ ret_size = p_packed_vertex_ids.size();
+ }
ret.resize(ret_size);
- {
- for (int i = 0; i < ret_size; i++) {
- ret.write[i] = Vector3(attribs_ptr[i * 3 + 0], attribs_ptr[i * 3 + 1], attribs_ptr[i * 3 + 2]);
+ for (int i = 0; i < ret_size; i++) {
+ int src_i = i;
+ if (!p_packed_vertex_ids.is_empty()) {
+ src_i = p_packed_vertex_ids[i];
}
+ ret.write[i] = Vector3(attribs_ptr[src_i * 3 + 0], attribs_ptr[src_i * 3 + 1], attribs_ptr[src_i * 3 + 2]);
}
return ret;
}
-Vector<Color> GLTFDocument::_decode_accessor_as_color(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
+Vector<Color> GLTFDocument::_decode_accessor_as_color(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex, const Vector<int> &p_packed_vertex_ids) {
const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex);
Vector<Color> ret;
@@ -2082,12 +2223,18 @@ Vector<Color> GLTFDocument::_decode_accessor_as_color(Ref<GLTFState> p_state, co
ERR_FAIL_COND_V(attribs.size() % vec_len != 0, ret);
const double *attribs_ptr = attribs.ptr();
- const int ret_size = attribs.size() / vec_len;
+ int ret_size = attribs.size() / vec_len;
+ if (!p_packed_vertex_ids.is_empty()) {
+ ERR_FAIL_COND_V(p_packed_vertex_ids[p_packed_vertex_ids.size() - 1] >= ret_size, ret);
+ ret_size = p_packed_vertex_ids.size();
+ }
ret.resize(ret_size);
- {
- for (int i = 0; i < ret_size; i++) {
- ret.write[i] = Color(attribs_ptr[i * vec_len + 0], attribs_ptr[i * vec_len + 1], attribs_ptr[i * vec_len + 2], vec_len == 4 ? attribs_ptr[i * 4 + 3] : 1.0);
+ for (int i = 0; i < ret_size; i++) {
+ int src_i = i;
+ if (!p_packed_vertex_ids.is_empty()) {
+ src_i = p_packed_vertex_ids[i];
}
+ ret.write[i] = Color(attribs_ptr[src_i * vec_len + 0], attribs_ptr[src_i * vec_len + 1], attribs_ptr[src_i * vec_len + 2], vec_len == 4 ? attribs_ptr[src_i * 4 + 3] : 1.0);
}
return ret;
}
@@ -2430,16 +2577,16 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) {
Vector<int32_t> mesh_indices = array[Mesh::ARRAY_INDEX];
if (mesh_indices.size()) {
if (primitive_type == Mesh::PRIMITIVE_TRIANGLES) {
- //swap around indices, convert ccw to cw for front face
+ // Swap around indices, convert ccw to cw for front face.
const int is = mesh_indices.size();
for (int k = 0; k < is; k += 3) {
SWAP(mesh_indices.write[k + 0], mesh_indices.write[k + 2]);
}
}
- primitive["indices"] = _encode_accessor_as_ints(p_state, mesh_indices, true, true);
+ primitive["indices"] = _encode_accessor_as_ints(p_state, mesh_indices, false, true);
} else {
if (primitive_type == Mesh::PRIMITIVE_TRIANGLES) {
- //generate indices because they need to be swapped for CW/CCW
+ // Generate indices because they need to be swapped for CW/CCW.
const Vector<Vector3> &vertices = array[Mesh::ARRAY_VERTEX];
Ref<SurfaceTool> st;
st.instantiate();
@@ -2455,50 +2602,77 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) {
generated_indices.write[k + 2] = k + 1;
}
}
- primitive["indices"] = _encode_accessor_as_ints(p_state, generated_indices, true, true);
+ primitive["indices"] = _encode_accessor_as_ints(p_state, generated_indices, false, true);
}
}
}
primitive["attributes"] = attributes;
- //blend shapes
+ // Blend shapes
print_verbose("glTF: Mesh has targets");
if (import_mesh->get_blend_shape_count()) {
ArrayMesh::BlendShapeMode shape_mode = import_mesh->get_blend_shape_mode();
+ const float normal_tangent_sparse_rounding = 0.001;
for (int morph_i = 0; morph_i < import_mesh->get_blend_shape_count(); morph_i++) {
Array array_morph = import_mesh->get_surface_blend_shape_arrays(surface_i, morph_i);
Dictionary t;
Vector<Vector3> varr = array_morph[Mesh::ARRAY_VERTEX];
+ Vector<Vector3> src_varr = array[Mesh::ARRAY_VERTEX];
Array mesh_arrays = import_mesh->get_surface_arrays(surface_i);
- if (varr.size()) {
- Vector<Vector3> src_varr = array[Mesh::ARRAY_VERTEX];
+ if (varr.size() && varr.size() == src_varr.size()) {
if (shape_mode == ArrayMesh::BlendShapeMode::BLEND_SHAPE_MODE_NORMALIZED) {
const int max_idx = src_varr.size();
for (int blend_i = 0; blend_i < max_idx; blend_i++) {
- varr.write[blend_i] = Vector3(varr[blend_i]) - src_varr[blend_i];
+ varr.write[blend_i] = varr[blend_i] - src_varr[blend_i];
+ }
+ }
+ GLTFAccessorIndex position_accessor = attributes["POSITION"];
+ if (position_accessor != -1) {
+ int new_accessor = _encode_sparse_accessor_as_vec3(p_state, varr, Vector<Vector3>(), 1.0, true, -1);
+ if (new_accessor != -1) {
+ t["POSITION"] = new_accessor;
}
}
-
- t["POSITION"] = _encode_accessor_as_vec3(p_state, varr, true);
}
Vector<Vector3> narr = array_morph[Mesh::ARRAY_NORMAL];
- if (narr.size()) {
- t["NORMAL"] = _encode_accessor_as_vec3(p_state, narr, true);
+ Vector<Vector3> src_narr = array[Mesh::ARRAY_NORMAL];
+ if (narr.size() && narr.size() == src_narr.size()) {
+ if (shape_mode == ArrayMesh::BlendShapeMode::BLEND_SHAPE_MODE_NORMALIZED) {
+ const int max_idx = src_narr.size();
+ for (int blend_i = 0; blend_i < max_idx; blend_i++) {
+ narr.write[blend_i] = narr[blend_i] - src_narr[blend_i];
+ }
+ }
+ GLTFAccessorIndex normal_accessor = attributes["NORMAL"];
+ if (normal_accessor != -1) {
+ int new_accessor = _encode_sparse_accessor_as_vec3(p_state, narr, Vector<Vector3>(), normal_tangent_sparse_rounding, true, -1);
+ if (new_accessor != -1) {
+ t["NORMAL"] = new_accessor;
+ }
+ }
}
Vector<real_t> tarr = array_morph[Mesh::ARRAY_TANGENT];
- if (tarr.size()) {
+ Vector<real_t> src_tarr = array[Mesh::ARRAY_TANGENT];
+ if (tarr.size() && tarr.size() == src_tarr.size()) {
const int ret_size = tarr.size() / 4;
Vector<Vector3> attribs;
attribs.resize(ret_size);
for (int i = 0; i < ret_size; i++) {
Vector3 vec3;
- vec3.x = tarr[(i * 4) + 0];
- vec3.y = tarr[(i * 4) + 1];
- vec3.z = tarr[(i * 4) + 2];
+ vec3.x = tarr[(i * 4) + 0] - src_tarr[(i * 4) + 0];
+ vec3.y = tarr[(i * 4) + 1] - src_tarr[(i * 4) + 1];
+ vec3.z = tarr[(i * 4) + 2] - src_tarr[(i * 4) + 2];
+ attribs.write[i] = vec3;
+ }
+ GLTFAccessorIndex tangent_accessor = attributes["TANGENT"];
+ if (tangent_accessor != -1) {
+ int new_accessor = _encode_sparse_accessor_as_vec3(p_state, attribs, Vector<Vector3>(), normal_tangent_sparse_rounding, true, -1);
+ if (new_accessor != -1) {
+ t["TANGENT"] = new_accessor;
+ }
}
- t["TANGENT"] = _encode_accessor_as_vec3(p_state, attribs, true);
}
targets.push_back(t);
}
@@ -2623,24 +2797,72 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
primitive = primitives2[mode];
}
+ int32_t orig_vertex_num = 0;
ERR_FAIL_COND_V(!a.has("POSITION"), ERR_PARSE_ERROR);
- int32_t vertex_num = 0;
if (a.has("POSITION")) {
PackedVector3Array vertices = _decode_accessor_as_vec3(p_state, a["POSITION"], true);
array[Mesh::ARRAY_VERTEX] = vertices;
- vertex_num = vertices.size();
+ orig_vertex_num = vertices.size();
+ }
+ int32_t vertex_num = orig_vertex_num;
+
+ Vector<int> indices;
+ 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);
+ const int is = indices.size();
+
+ if (primitive == Mesh::PRIMITIVE_TRIANGLES) {
+ // Swap around indices, convert ccw to cw for front face.
+
+ int *w = indices.ptrw();
+ for (int k = 0; k < is; k += 3) {
+ SWAP(w[k + 1], w[k + 2]);
+ }
+ }
+
+ const int *indices_w = indices.ptrw();
+ Vector<bool> used_indices;
+ used_indices.resize_zeroed(orig_vertex_num);
+ bool *used_w = used_indices.ptrw();
+ for (int idx_i = 0; idx_i < is; idx_i++) {
+ ERR_FAIL_INDEX_V(indices_w[idx_i], orig_vertex_num, ERR_INVALID_DATA);
+ used_w[indices_w[idx_i]] = true;
+ }
+ indices_rev_mapping.resize_zeroed(orig_vertex_num);
+ int *rev_w = indices_rev_mapping.ptrw();
+ vertex_num = 0;
+ for (int vert_i = 0; vert_i < orig_vertex_num; vert_i++) {
+ if (used_w[vert_i]) {
+ rev_w[vert_i] = indices_mapping.size();
+ indices_mapping.push_back(vert_i);
+ indices_vec4_mapping.push_back(vert_i * 4 + 0);
+ indices_vec4_mapping.push_back(vert_i * 4 + 1);
+ indices_vec4_mapping.push_back(vert_i * 4 + 2);
+ indices_vec4_mapping.push_back(vert_i * 4 + 3);
+ vertex_num++;
+ }
+ }
+ }
+ ERR_FAIL_COND_V(vertex_num <= 0, ERR_INVALID_DECLARATION);
+
+ if (a.has("POSITION")) {
+ PackedVector3Array vertices = _decode_accessor_as_vec3(p_state, a["POSITION"], true, indices_mapping);
+ array[Mesh::ARRAY_VERTEX] = vertices;
}
if (a.has("NORMAL")) {
- array[Mesh::ARRAY_NORMAL] = _decode_accessor_as_vec3(p_state, a["NORMAL"], true);
+ array[Mesh::ARRAY_NORMAL] = _decode_accessor_as_vec3(p_state, a["NORMAL"], true, indices_mapping);
}
if (a.has("TANGENT")) {
- array[Mesh::ARRAY_TANGENT] = _decode_accessor_as_floats(p_state, a["TANGENT"], true);
+ array[Mesh::ARRAY_TANGENT] = _decode_accessor_as_floats(p_state, a["TANGENT"], true, indices_vec4_mapping);
}
if (a.has("TEXCOORD_0")) {
- array[Mesh::ARRAY_TEX_UV] = _decode_accessor_as_vec2(p_state, a["TEXCOORD_0"], true);
+ array[Mesh::ARRAY_TEX_UV] = _decode_accessor_as_vec2(p_state, a["TEXCOORD_0"], true, indices_mapping);
}
if (a.has("TEXCOORD_1")) {
- array[Mesh::ARRAY_TEX_UV2] = _decode_accessor_as_vec2(p_state, a["TEXCOORD_1"], true);
+ array[Mesh::ARRAY_TEX_UV2] = _decode_accessor_as_vec2(p_state, a["TEXCOORD_1"], true, indices_mapping);
}
for (int custom_i = 0; custom_i < 3; custom_i++) {
Vector<float> cur_custom;
@@ -2651,12 +2873,12 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
String gltf_texcoord_key = vformat("TEXCOORD_%d", texcoord_i);
int num_channels = 0;
if (a.has(gltf_texcoord_key)) {
- texcoord_first = _decode_accessor_as_vec2(p_state, a[gltf_texcoord_key], true);
+ texcoord_first = _decode_accessor_as_vec2(p_state, a[gltf_texcoord_key], true, indices_mapping);
num_channels = 2;
}
gltf_texcoord_key = vformat("TEXCOORD_%d", texcoord_i + 1);
if (a.has(gltf_texcoord_key)) {
- texcoord_second = _decode_accessor_as_vec2(p_state, a[gltf_texcoord_key], true);
+ texcoord_second = _decode_accessor_as_vec2(p_state, a[gltf_texcoord_key], true, indices_mapping);
num_channels = 4;
}
if (!num_channels) {
@@ -2697,15 +2919,18 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
}
}
if (a.has("COLOR_0")) {
- array[Mesh::ARRAY_COLOR] = _decode_accessor_as_color(p_state, a["COLOR_0"], true);
+ array[Mesh::ARRAY_COLOR] = _decode_accessor_as_color(p_state, a["COLOR_0"], true, indices_mapping);
has_vertex_color = true;
}
if (a.has("JOINTS_0") && !a.has("JOINTS_1")) {
- array[Mesh::ARRAY_BONES] = _decode_accessor_as_ints(p_state, a["JOINTS_0"], true);
+ PackedInt32Array joints_0 = _decode_accessor_as_ints(p_state, a["JOINTS_0"], true, indices_vec4_mapping);
+ ERR_FAIL_COND_V(joints_0.size() != 4 * vertex_num, ERR_INVALID_DATA);
+ array[Mesh::ARRAY_BONES] = joints_0;
} else if (a.has("JOINTS_0") && a.has("JOINTS_1")) {
- PackedInt32Array joints_0 = _decode_accessor_as_ints(p_state, a["JOINTS_0"], true);
- PackedInt32Array joints_1 = _decode_accessor_as_ints(p_state, a["JOINTS_1"], true);
+ PackedInt32Array joints_0 = _decode_accessor_as_ints(p_state, a["JOINTS_0"], true, indices_vec4_mapping);
+ PackedInt32Array joints_1 = _decode_accessor_as_ints(p_state, a["JOINTS_1"], true, indices_vec4_mapping);
ERR_FAIL_COND_V(joints_0.size() != joints_1.size(), ERR_INVALID_DATA);
+ ERR_FAIL_COND_V(joints_0.size() != 4 * vertex_num, ERR_INVALID_DATA);
int32_t weight_8_count = JOINT_GROUP_SIZE * 2;
Vector<int> joints;
joints.resize(vertex_num * weight_8_count);
@@ -2722,8 +2947,9 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
array[Mesh::ARRAY_BONES] = joints;
}
if (a.has("WEIGHTS_0") && !a.has("WEIGHTS_1")) {
- Vector<float> weights = _decode_accessor_as_floats(p_state, a["WEIGHTS_0"], true);
- { //gltf does not seem to normalize the weights for some reason..
+ Vector<float> weights = _decode_accessor_as_floats(p_state, a["WEIGHTS_0"], true, indices_vec4_mapping);
+ ERR_FAIL_COND_V(weights.size() != 4 * vertex_num, ERR_INVALID_DATA);
+ { // glTF does not seem to normalize the weights for some reason.
int wc = weights.size();
float *w = weights.ptrw();
@@ -2743,10 +2969,11 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
}
array[Mesh::ARRAY_WEIGHTS] = weights;
} else if (a.has("WEIGHTS_0") && a.has("WEIGHTS_1")) {
- Vector<float> weights_0 = _decode_accessor_as_floats(p_state, a["WEIGHTS_0"], true);
- Vector<float> weights_1 = _decode_accessor_as_floats(p_state, a["WEIGHTS_1"], true);
+ Vector<float> weights_0 = _decode_accessor_as_floats(p_state, a["WEIGHTS_0"], true, indices_vec4_mapping);
+ Vector<float> weights_1 = _decode_accessor_as_floats(p_state, a["WEIGHTS_1"], true, indices_vec4_mapping);
Vector<float> weights;
ERR_FAIL_COND_V(weights_0.size() != weights_1.size(), ERR_INVALID_DATA);
+ ERR_FAIL_COND_V(weights_0.size() != 4 * vertex_num, ERR_INVALID_DATA);
int32_t weight_8_count = JOINT_GROUP_SIZE * 2;
weights.resize(vertex_num * weight_8_count);
for (int32_t vertex_i = 0; vertex_i < vertex_num; vertex_i++) {
@@ -2759,7 +2986,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
weights.write[vertex_i * weight_8_count + 6] = weights_1[vertex_i * JOINT_GROUP_SIZE + 2];
weights.write[vertex_i * weight_8_count + 7] = weights_1[vertex_i * JOINT_GROUP_SIZE + 3];
}
- { //gltf does not seem to normalize the weights for some reason..
+ { // glTF does not seem to normalize the weights for some reason.
int wc = weights.size();
float *w = weights.ptrw();
@@ -2788,25 +3015,18 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
array[Mesh::ARRAY_WEIGHTS] = weights;
}
- if (p.has("indices")) {
- Vector<int> indices = _decode_accessor_as_ints(p_state, p["indices"], false);
-
- if (primitive == Mesh::PRIMITIVE_TRIANGLES) {
- //swap around indices, convert ccw to cw for front face
-
- const int is = indices.size();
- int *w = indices.ptrw();
- for (int k = 0; k < is; k += 3) {
- SWAP(w[k + 1], w[k + 2]);
- }
+ if (!indices.is_empty()) {
+ int *w = indices.ptrw();
+ const int is = indices.size();
+ for (int ind_i = 0; ind_i < is; ind_i++) {
+ w[ind_i] = indices_rev_mapping[indices[ind_i]];
}
array[Mesh::ARRAY_INDEX] = indices;
} else if (primitive == Mesh::PRIMITIVE_TRIANGLES) {
- //generate indices because they need to be swapped for CW/CCW
+ // Generate indices because they need to be swapped for CW/CCW.
const Vector<Vector3> &vertices = array[Mesh::ARRAY_VERTEX];
ERR_FAIL_COND_V(vertices.is_empty(), ERR_PARSE_ERROR);
- Vector<int> indices;
const int vs = vertices.size();
indices.resize(vs);
{
@@ -2870,13 +3090,11 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
}
Array morphs;
- //blend shapes
+ // Blend shapes
if (p.has("targets")) {
print_verbose("glTF: Mesh has targets");
const Array &targets = p["targets"];
- //ideally BLEND_SHAPE_MODE_RELATIVE since gltf2 stores in displacement
- //but it could require a larger refactor?
import_mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED);
if (j == 0) {
@@ -2903,7 +3121,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
}
if (t.has("POSITION")) {
- Vector<Vector3> varr = _decode_accessor_as_vec3(p_state, t["POSITION"], true);
+ Vector<Vector3> varr = _decode_accessor_as_vec3(p_state, t["POSITION"], true, indices_mapping);
const Vector<Vector3> src_varr = array[Mesh::ARRAY_VERTEX];
const int size = src_varr.size();
ERR_FAIL_COND_V(size == 0, ERR_PARSE_ERROR);
@@ -2925,7 +3143,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
array_copy[Mesh::ARRAY_VERTEX] = varr;
}
if (t.has("NORMAL")) {
- Vector<Vector3> narr = _decode_accessor_as_vec3(p_state, t["NORMAL"], true);
+ Vector<Vector3> narr = _decode_accessor_as_vec3(p_state, t["NORMAL"], true, indices_mapping);
const Vector<Vector3> src_narr = array[Mesh::ARRAY_NORMAL];
int size = src_narr.size();
ERR_FAIL_COND_V(size == 0, ERR_PARSE_ERROR);
@@ -2947,7 +3165,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
array_copy[Mesh::ARRAY_NORMAL] = narr;
}
if (t.has("TANGENT")) {
- const Vector<Vector3> tangents_v3 = _decode_accessor_as_vec3(p_state, t["TANGENT"], true);
+ const Vector<Vector3> tangents_v3 = _decode_accessor_as_vec3(p_state, t["TANGENT"], true, indices_mapping);
const Vector<float> src_tangents = array[Mesh::ARRAY_TANGENT];
ERR_FAIL_COND_V(src_tangents.is_empty(), ERR_PARSE_ERROR);
@@ -5517,7 +5735,7 @@ void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> p_state, const GL
}
}
-template <class T>
+template <typename T>
struct SceneFormatImporterGLTFInterpolate {
T lerp(const T &a, const T &b, float c) const {
return a + (b - a) * c;
@@ -5567,7 +5785,7 @@ struct SceneFormatImporterGLTFInterpolate<Quaternion> {
}
};
-template <class T>
+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) {
ERR_FAIL_COND_V(p_values.is_empty(), T());
if (p_times.size() != (p_values.size() / (p_interp == GLTFAnimation::INTERP_CUBIC_SPLINE ? 3 : 1))) {
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index 1682e9eeb7..1001b482cd 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -57,13 +57,6 @@ public:
ARRAY_BUFFER = 34962,
ELEMENT_ARRAY_BUFFER = 34963,
- TYPE_BYTE = 5120,
- TYPE_UNSIGNED_BYTE = 5121,
- TYPE_SHORT = 5122,
- TYPE_UNSIGNED_SHORT = 5123,
- TYPE_UNSIGNED_INT = 5125,
- TYPE_FLOAT = 5126,
-
COMPONENT_TYPE_BYTE = 5120,
COMPONENT_TYPE_UNSIGNED_BYTE = 5121,
COMPONENT_TYPE_SHORT = 5122,
@@ -155,19 +148,24 @@ private:
const bool p_for_vertex);
Vector<float> _decode_accessor_as_floats(Ref<GLTFState> p_state,
const GLTFAccessorIndex p_accessor,
- const bool p_for_vertex);
+ const bool p_for_vertex,
+ const Vector<int> &p_packed_vertex_ids = Vector<int>());
Vector<int> _decode_accessor_as_ints(Ref<GLTFState> p_state,
const GLTFAccessorIndex p_accessor,
- const bool p_for_vertex);
+ const bool p_for_vertex,
+ const Vector<int> &p_packed_vertex_ids = Vector<int>());
Vector<Vector2> _decode_accessor_as_vec2(Ref<GLTFState> p_state,
const GLTFAccessorIndex p_accessor,
- const bool p_for_vertex);
+ const bool p_for_vertex,
+ const Vector<int> &p_packed_vertex_ids = Vector<int>());
Vector<Vector3> _decode_accessor_as_vec3(Ref<GLTFState> p_state,
const GLTFAccessorIndex p_accessor,
- const bool p_for_vertex);
+ const bool p_for_vertex,
+ const Vector<int> &p_packed_vertex_ids = Vector<int>());
Vector<Color> _decode_accessor_as_color(Ref<GLTFState> p_state,
const GLTFAccessorIndex p_accessor,
- const bool p_for_vertex);
+ const bool p_for_vertex,
+ const Vector<int> &p_packed_vertex_ids = Vector<int>());
Vector<Quaternion> _decode_accessor_as_quaternion(Ref<GLTFState> p_state,
const GLTFAccessorIndex p_accessor,
const bool p_for_vertex);
@@ -217,7 +215,7 @@ private:
Light3D *_generate_light(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index);
Node3D *_generate_spatial(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index);
void _assign_node_names(Ref<GLTFState> p_state);
- template <class T>
+ template <typename T>
T _interpolate_track(const Vector<real_t> &p_times, const Vector<T> &p_values,
const float p_time,
const GLTFAnimation::Interpolation p_interp);
@@ -255,6 +253,7 @@ private:
GLTFAccessorIndex _encode_accessor_as_vec3(Ref<GLTFState> p_state,
const Vector<Vector3> p_attribs,
const bool p_for_vertex);
+ GLTFAccessorIndex _encode_sparse_accessor_as_vec3(Ref<GLTFState> p_state, const Vector<Vector3> p_attribs, const Vector<Vector3> p_reference_attribs, const float p_reference_multiplier, const bool p_for_vertex, const GLTFAccessorIndex p_reference_accessor);
GLTFAccessorIndex _encode_accessor_as_color(Ref<GLTFState> p_state,
const Vector<Color> p_attribs,
const bool p_for_vertex);
@@ -273,6 +272,7 @@ private:
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_indices = false);
+
Error _encode_accessors(Ref<GLTFState> p_state);
Error _encode_buffer_views(Ref<GLTFState> p_state);
Error _serialize_materials(Ref<GLTFState> p_state);
diff --git a/modules/gltf/gltf_template_convert.h b/modules/gltf/gltf_template_convert.h
index 2743cd8a9b..46f185867a 100644
--- a/modules/gltf/gltf_template_convert.h
+++ b/modules/gltf/gltf_template_convert.h
@@ -37,7 +37,7 @@
#include "core/variant/typed_array.h"
namespace GLTFTemplateConvert {
-template <class T>
+template <typename T>
static Array to_array(const Vector<T> &p_inp) {
Array ret;
for (int i = 0; i < p_inp.size(); i++) {
@@ -46,7 +46,7 @@ static Array to_array(const Vector<T> &p_inp) {
return ret;
}
-template <class T>
+template <typename T>
static TypedArray<T> to_array(const HashSet<T> &p_inp) {
TypedArray<T> ret;
typename HashSet<T>::Iterator elem = p_inp.begin();
@@ -57,7 +57,7 @@ static TypedArray<T> to_array(const HashSet<T> &p_inp) {
return ret;
}
-template <class T>
+template <typename T>
static void set_from_array(Vector<T> &r_out, const Array &p_inp) {
r_out.clear();
for (int i = 0; i < p_inp.size(); i++) {
@@ -65,7 +65,7 @@ static void set_from_array(Vector<T> &r_out, const Array &p_inp) {
}
}
-template <class T>
+template <typename T>
static void set_from_array(HashSet<T> &r_out, const TypedArray<T> &p_inp) {
r_out.clear();
for (int i = 0; i < p_inp.size(); i++) {
@@ -73,7 +73,7 @@ static void set_from_array(HashSet<T> &r_out, const TypedArray<T> &p_inp) {
}
}
-template <class K, class V>
+template <typename K, typename V>
static Dictionary to_dictionary(const HashMap<K, V> &p_inp) {
Dictionary ret;
for (const KeyValue<K, V> &E : p_inp) {
@@ -82,7 +82,7 @@ static Dictionary to_dictionary(const HashMap<K, V> &p_inp) {
return ret;
}
-template <class K, class V>
+template <typename K, typename V>
static void set_from_dictionary(HashMap<K, V> &r_out, const Dictionary &p_inp) {
r_out.clear();
Array keys = p_inp.keys();
diff --git a/modules/gltf/structures/gltf_animation.h b/modules/gltf/structures/gltf_animation.h
index 7f769752c2..afc9784895 100644
--- a/modules/gltf/structures/gltf_animation.h
+++ b/modules/gltf/structures/gltf_animation.h
@@ -47,7 +47,7 @@ public:
INTERP_CUBIC_SPLINE,
};
- template <class T>
+ template <typename T>
struct Channel {
Interpolation interpolation = INTERP_LINEAR;
Vector<real_t> times;
diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp
index 5d22cb1301..b6f5d6ce57 100644
--- a/modules/lightmapper_rd/lightmapper_rd.cpp
+++ b/modules/lightmapper_rd/lightmapper_rd.cpp
@@ -233,8 +233,7 @@ Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_
MeshInstance &mi = mesh_instances.write[m_i];
Size2i s = Size2i(mi.data.albedo_on_uv2->get_width(), mi.data.albedo_on_uv2->get_height());
sizes.push_back(s);
- atlas_size.width = MAX(atlas_size.width, s.width + 2);
- atlas_size.height = MAX(atlas_size.height, s.height + 2);
+ atlas_size = atlas_size.max(s + Size2i(2, 2));
}
int max = nearest_power_of_2_templated(atlas_size.width);
diff --git a/modules/multiplayer/scene_replication_interface.h b/modules/multiplayer/scene_replication_interface.h
index 31211bb108..cb582a2caf 100644
--- a/modules/multiplayer/scene_replication_interface.h
+++ b/modules/multiplayer/scene_replication_interface.h
@@ -112,7 +112,7 @@ private:
Error _update_spawn_visibility(int p_peer, const ObjectID &p_oid);
void _free_remotes(const PeerInfo &p_info);
- template <class T>
+ template <typename T>
static T *get_id_as(const ObjectID &p_id) {
return p_id.is_valid() ? Object::cast_to<T>(ObjectDB::get_instance(p_id)) : nullptr;
}
diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp
index 3dbf9f8735..069cc0f4a8 100644
--- a/modules/navigation/2d/nav_mesh_generator_2d.cpp
+++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp
@@ -35,6 +35,7 @@
#include "core/config/project_settings.h"
#include "scene/2d/mesh_instance_2d.h"
#include "scene/2d/multimesh_instance_2d.h"
+#include "scene/2d/navigation_obstacle_2d.h"
#include "scene/2d/physics/static_body_2d.h"
#include "scene/2d/polygon_2d.h"
#include "scene/2d/tile_map.h"
@@ -233,6 +234,7 @@ void NavMeshGenerator2D::generator_parse_geometry_node(Ref<NavigationPolygon> p_
generator_parse_polygon2d_node(p_navigation_mesh, p_source_geometry_data, p_node);
generator_parse_staticbody2d_node(p_navigation_mesh, p_source_geometry_data, p_node);
generator_parse_tilemap_node(p_navigation_mesh, p_source_geometry_data, p_node);
+ generator_parse_navigationobstacle_node(p_navigation_mesh, p_source_geometry_data, p_node);
if (p_recurse_children) {
for (int i = 0; i < p_node->get_child_count(); i++) {
@@ -660,6 +662,58 @@ void NavMeshGenerator2D::generator_parse_tilemap_node(const Ref<NavigationPolygo
}
}
+void NavMeshGenerator2D::generator_parse_navigationobstacle_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) {
+ NavigationObstacle2D *obstacle = Object::cast_to<NavigationObstacle2D>(p_node);
+ if (obstacle == nullptr) {
+ return;
+ }
+
+ if (!obstacle->get_affect_navigation_mesh()) {
+ return;
+ }
+
+ const Transform2D node_xform = p_source_geometry_data->root_node_transform * Transform2D(0.0, obstacle->get_global_position());
+
+ const float obstacle_radius = obstacle->get_radius();
+
+ if (obstacle_radius > 0.0) {
+ Vector<Vector2> obstruction_circle_vertices;
+
+ // The point of this is that the moving obstacle can make a simple hole in the navigation mesh and affect the pathfinding.
+ // Without, navigation paths can go directly through the middle of the obstacle and conflict with the avoidance to get agents stuck.
+ // No place for excessive "round" detail here. Every additional edge adds a high cost for something that needs to be quick, not pretty.
+ static const int circle_points = 12;
+
+ obstruction_circle_vertices.resize(circle_points);
+ Vector2 *circle_vertices_ptrw = obstruction_circle_vertices.ptrw();
+ const real_t circle_point_step = Math_TAU / circle_points;
+
+ for (int i = 0; i < circle_points; i++) {
+ const float angle = i * circle_point_step;
+ circle_vertices_ptrw[i] = node_xform.xform(Vector2(Math::cos(angle) * obstacle_radius, Math::sin(angle) * obstacle_radius));
+ }
+
+ p_source_geometry_data->add_projected_obstruction(obstruction_circle_vertices, obstacle->get_carve_navigation_mesh());
+ }
+
+ const Vector<Vector2> &obstacle_vertices = obstacle->get_vertices();
+
+ if (obstacle_vertices.is_empty()) {
+ return;
+ }
+
+ Vector<Vector2> obstruction_shape_vertices;
+ obstruction_shape_vertices.resize(obstacle_vertices.size());
+
+ const Vector2 *obstacle_vertices_ptr = obstacle_vertices.ptr();
+ Vector2 *obstruction_shape_vertices_ptrw = obstruction_shape_vertices.ptrw();
+
+ for (int i = 0; i < obstacle_vertices.size(); i++) {
+ obstruction_shape_vertices_ptrw[i] = node_xform.xform(obstacle_vertices_ptr[i]);
+ }
+ p_source_geometry_data->add_projected_obstruction(obstruction_shape_vertices, obstacle->get_carve_navigation_mesh());
+}
+
void NavMeshGenerator2D::generator_parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node) {
List<Node *> parse_nodes;
@@ -779,6 +833,30 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
obstruction_polygon_paths.push_back(clip_path);
}
+ const Vector<NavigationMeshSourceGeometryData2D::ProjectedObstruction> &projected_obstructions = p_source_geometry_data->_get_projected_obstructions();
+
+ if (!projected_obstructions.is_empty()) {
+ for (const NavigationMeshSourceGeometryData2D::ProjectedObstruction &projected_obstruction : projected_obstructions) {
+ if (projected_obstruction.carve) {
+ continue;
+ }
+ if (projected_obstruction.vertices.is_empty() || projected_obstruction.vertices.size() % 2 != 0) {
+ continue;
+ }
+
+ Path64 clip_path;
+ clip_path.reserve(projected_obstruction.vertices.size() / 2);
+ for (int i = 0; i < projected_obstruction.vertices.size() / 2; i++) {
+ const Point64 &point = Point64(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]);
+ clip_path.push_back(point);
+ }
+ if (!IsPositive(clip_path)) {
+ std::reverse(clip_path.begin(), clip_path.end());
+ }
+ obstruction_polygon_paths.push_back(clip_path);
+ }
+ }
+
Rect2 baking_rect = p_navigation_mesh->get_baking_rect();
if (baking_rect.has_area()) {
Vector2 baking_rect_offset = p_navigation_mesh->get_baking_rect_offset();
@@ -809,6 +887,33 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
if (agent_radius_offset > 0.0) {
path_solution = InflatePaths(path_solution, -agent_radius_offset, JoinType::Miter, EndType::Polygon);
}
+
+ if (!projected_obstructions.is_empty()) {
+ obstruction_polygon_paths.resize(0);
+ for (const NavigationMeshSourceGeometryData2D::ProjectedObstruction &projected_obstruction : projected_obstructions) {
+ if (!projected_obstruction.carve) {
+ continue;
+ }
+ if (projected_obstruction.vertices.is_empty() || projected_obstruction.vertices.size() % 2 != 0) {
+ continue;
+ }
+
+ Path64 clip_path;
+ clip_path.reserve(projected_obstruction.vertices.size() / 2);
+ for (int i = 0; i < projected_obstruction.vertices.size() / 2; i++) {
+ const Point64 &point = Point64(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]);
+ clip_path.push_back(point);
+ }
+ if (!IsPositive(clip_path)) {
+ std::reverse(clip_path.begin(), clip_path.end());
+ }
+ obstruction_polygon_paths.push_back(clip_path);
+ }
+ if (obstruction_polygon_paths.size() > 0) {
+ path_solution = Difference(path_solution, obstruction_polygon_paths, FillRule::NonZero);
+ }
+ }
+
//path_solution = RamerDouglasPeucker(path_solution, 0.025); //
real_t border_size = p_navigation_mesh->get_border_size();
diff --git a/modules/navigation/2d/nav_mesh_generator_2d.h b/modules/navigation/2d/nav_mesh_generator_2d.h
index b606f3f6fc..2567a170ef 100644
--- a/modules/navigation/2d/nav_mesh_generator_2d.h
+++ b/modules/navigation/2d/nav_mesh_generator_2d.h
@@ -81,6 +81,7 @@ class NavMeshGenerator2D : public Object {
static void generator_parse_polygon2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
static void generator_parse_staticbody2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
static void generator_parse_tilemap_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
+ static void generator_parse_navigationobstacle_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
static bool generator_emit_callback(const Callable &p_callback);
diff --git a/modules/navigation/3d/nav_mesh_generator_3d.cpp b/modules/navigation/3d/nav_mesh_generator_3d.cpp
index e1ed9c51aa..b3391398a6 100644
--- a/modules/navigation/3d/nav_mesh_generator_3d.cpp
+++ b/modules/navigation/3d/nav_mesh_generator_3d.cpp
@@ -37,6 +37,7 @@
#include "core/os/thread.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/multimesh_instance_3d.h"
+#include "scene/3d/navigation_obstacle_3d.h"
#include "scene/3d/physics/static_body_3d.h"
#include "scene/resources/3d/box_shape_3d.h"
#include "scene/resources/3d/capsule_shape_3d.h"
@@ -251,6 +252,7 @@ void NavMeshGenerator3D::generator_parse_geometry_node(const Ref<NavigationMesh>
#ifdef MODULE_GRIDMAP_ENABLED
generator_parse_gridmap_node(p_navigation_mesh, p_source_geometry_data, p_node);
#endif
+ generator_parse_navigationobstacle_node(p_navigation_mesh, p_source_geometry_data, p_node);
if (p_recurse_children) {
for (int i = 0; i < p_node->get_child_count(); i++) {
@@ -569,6 +571,59 @@ void NavMeshGenerator3D::generator_parse_gridmap_node(const Ref<NavigationMesh>
}
#endif // MODULE_GRIDMAP_ENABLED
+void NavMeshGenerator3D::generator_parse_navigationobstacle_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node) {
+ NavigationObstacle3D *obstacle = Object::cast_to<NavigationObstacle3D>(p_node);
+ if (obstacle == nullptr) {
+ return;
+ }
+
+ if (!obstacle->get_affect_navigation_mesh()) {
+ return;
+ }
+
+ const Transform3D node_xform = p_source_geometry_data->root_node_transform * Transform3D(Basis(), obstacle->get_global_position());
+
+ const float obstacle_radius = obstacle->get_radius();
+
+ if (obstacle_radius > 0.0) {
+ Vector<Vector3> obstruction_circle_vertices;
+
+ // The point of this is that the moving obstacle can make a simple hole in the navigation mesh and affect the pathfinding.
+ // Without, navigation paths can go directly through the middle of the obstacle and conflict with the avoidance to get agents stuck.
+ // No place for excessive "round" detail here. Every additional edge adds a high cost for something that needs to be quick, not pretty.
+ static const int circle_points = 12;
+
+ obstruction_circle_vertices.resize(circle_points);
+ Vector3 *circle_vertices_ptrw = obstruction_circle_vertices.ptrw();
+ const real_t circle_point_step = Math_TAU / circle_points;
+
+ for (int i = 0; i < circle_points; i++) {
+ const float angle = i * circle_point_step;
+ circle_vertices_ptrw[i] = node_xform.xform(Vector3(Math::cos(angle) * obstacle_radius, 0.0, Math::sin(angle) * obstacle_radius));
+ }
+
+ p_source_geometry_data->add_projected_obstruction(obstruction_circle_vertices, obstacle->get_global_position().y + p_source_geometry_data->root_node_transform.origin.y - obstacle_radius, obstacle_radius, obstacle->get_carve_navigation_mesh());
+ }
+
+ const Vector<Vector3> &obstacle_vertices = obstacle->get_vertices();
+
+ if (obstacle_vertices.is_empty()) {
+ return;
+ }
+
+ Vector<Vector3> obstruction_shape_vertices;
+ obstruction_shape_vertices.resize(obstacle_vertices.size());
+
+ const Vector3 *obstacle_vertices_ptr = obstacle_vertices.ptr();
+ Vector3 *obstruction_shape_vertices_ptrw = obstruction_shape_vertices.ptrw();
+
+ for (int i = 0; i < obstacle_vertices.size(); i++) {
+ obstruction_shape_vertices_ptrw[i] = node_xform.xform(obstacle_vertices_ptr[i]);
+ obstruction_shape_vertices_ptrw[i].y = 0.0;
+ }
+ p_source_geometry_data->add_projected_obstruction(obstruction_shape_vertices, obstacle->get_global_position().y + p_source_geometry_data->root_node_transform.origin.y, obstacle->get_height(), obstacle->get_carve_navigation_mesh());
+}
+
void NavMeshGenerator3D::generator_parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node) {
List<Node *> parse_nodes;
@@ -741,10 +796,46 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation
rcFreeHeightField(hf);
hf = nullptr;
+ const Vector<NavigationMeshSourceGeometryData3D::ProjectedObstruction> &projected_obstructions = p_source_geometry_data->_get_projected_obstructions();
+
+ // Add obstacles to the source geometry. Those will be affected by e.g. agent_radius.
+ if (!projected_obstructions.is_empty()) {
+ for (const NavigationMeshSourceGeometryData3D::ProjectedObstruction &projected_obstruction : projected_obstructions) {
+ if (projected_obstruction.carve) {
+ continue;
+ }
+ if (projected_obstruction.vertices.is_empty() || projected_obstruction.vertices.size() % 3 != 0) {
+ continue;
+ }
+
+ const float *projected_obstruction_verts = projected_obstruction.vertices.ptr();
+ const int projected_obstruction_nverts = projected_obstruction.vertices.size() / 3;
+
+ rcMarkConvexPolyArea(&ctx, projected_obstruction_verts, projected_obstruction_nverts, projected_obstruction.elevation, projected_obstruction.elevation + projected_obstruction.height, RC_NULL_AREA, *chf);
+ }
+ }
+
bake_state = "Eroding walkable area..."; // step #6
ERR_FAIL_COND(!rcErodeWalkableArea(&ctx, cfg.walkableRadius, *chf));
+ // Carve obstacles to the eroded geometry. Those will NOT be affected by e.g. agent_radius because that step is already done.
+ if (!projected_obstructions.is_empty()) {
+ for (const NavigationMeshSourceGeometryData3D::ProjectedObstruction &projected_obstruction : projected_obstructions) {
+ if (!projected_obstruction.carve) {
+ continue;
+ }
+ if (projected_obstruction.vertices.is_empty() || projected_obstruction.vertices.size() % 3 != 0) {
+ continue;
+ }
+
+ const float *projected_obstruction_verts = projected_obstruction.vertices.ptr();
+ const int projected_obstruction_nverts = projected_obstruction.vertices.size() / 3;
+
+ rcMarkConvexPolyArea(&ctx, projected_obstruction_verts, projected_obstruction_nverts, projected_obstruction.elevation, projected_obstruction.elevation + projected_obstruction.height, RC_NULL_AREA, *chf);
+ }
+ }
+
bake_state = "Partitioning..."; // step #7
if (p_navigation_mesh->get_sample_partition_type() == NavigationMesh::SAMPLE_PARTITION_WATERSHED) {
diff --git a/modules/navigation/3d/nav_mesh_generator_3d.h b/modules/navigation/3d/nav_mesh_generator_3d.h
index 0251b02235..9c9b3bdefe 100644
--- a/modules/navigation/3d/nav_mesh_generator_3d.h
+++ b/modules/navigation/3d/nav_mesh_generator_3d.h
@@ -86,6 +86,7 @@ class NavMeshGenerator3D : public Object {
#ifdef MODULE_GRIDMAP_ENABLED
static void generator_parse_gridmap_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node);
#endif // MODULE_GRIDMAP_ENABLED
+ static void generator_parse_navigationobstacle_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node);
static bool generator_emit_callback(const Callable &p_callback);
diff --git a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml
index 20ce1c42bf..9d6b197ee1 100644
--- a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml
+++ b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml
@@ -11,8 +11,25 @@
<methods>
<method name="_get_composition_layer" qualifiers="virtual">
<return type="int" />
+ <param index="0" name="index" type="int" />
<description>
- Returns a pointer to an [code]XrCompositionLayerBaseHeader[/code] struct to provide a composition layer. This will only be called if the extension previously registered itself with [method OpenXRAPIExtension.register_composition_layer_provider].
+ Returns a pointer to an [code]XrCompositionLayerBaseHeader[/code] struct to provide the given composition layer.
+ This will only be called if the extension previously registered itself with [method OpenXRAPIExtension.register_composition_layer_provider].
+ </description>
+ </method>
+ <method name="_get_composition_layer_count" qualifiers="virtual">
+ <return type="int" />
+ <description>
+ Returns the number of composition layers this extension wrapper provides via [method _get_composition_layer].
+ This will only be called if the extension previously registered itself with [method OpenXRAPIExtension.register_composition_layer_provider].
+ </description>
+ </method>
+ <method name="_get_composition_layer_order" qualifiers="virtual">
+ <return type="int" />
+ <param index="0" name="index" type="int" />
+ <description>
+ Returns an integer that will be used to sort the given composition layer provided via [method _get_composition_layer]. Lower numbers will move the layer to the front of the list, and higher numbers to the end. The default projection layer has an order of [code]0[/code], so layers provided by this method should probably be above or below (but not exactly) [code]0[/code].
+ This will only be called if the extension previously registered itself with [method OpenXRAPIExtension.register_composition_layer_provider].
</description>
</method>
<method name="_get_requested_extensions" qualifiers="virtual">
diff --git a/modules/openxr/extensions/openxr_composition_layer_depth_extension.cpp b/modules/openxr/extensions/openxr_composition_layer_depth_extension.cpp
index 7a16da144e..b3c94d44f0 100644
--- a/modules/openxr/extensions/openxr_composition_layer_depth_extension.cpp
+++ b/modules/openxr/extensions/openxr_composition_layer_depth_extension.cpp
@@ -56,8 +56,15 @@ bool OpenXRCompositionLayerDepthExtension::is_available() {
return available;
}
-XrCompositionLayerBaseHeader *OpenXRCompositionLayerDepthExtension::get_composition_layer() {
- // Seems this is all done in our base layer... Just in case this changes...
+int OpenXRCompositionLayerDepthExtension::get_composition_layer_count() {
+ return 0;
+}
+XrCompositionLayerBaseHeader *OpenXRCompositionLayerDepthExtension::get_composition_layer(int p_index) {
+ // Seems this is all done in our base layer... Just in case this changes...
return nullptr;
}
+
+int OpenXRCompositionLayerDepthExtension::get_composition_layer_order(int p_index) {
+ return 0;
+}
diff --git a/modules/openxr/extensions/openxr_composition_layer_depth_extension.h b/modules/openxr/extensions/openxr_composition_layer_depth_extension.h
index 50bbef4db4..1fda8844af 100644
--- a/modules/openxr/extensions/openxr_composition_layer_depth_extension.h
+++ b/modules/openxr/extensions/openxr_composition_layer_depth_extension.h
@@ -43,7 +43,9 @@ public:
virtual HashMap<String, bool *> get_requested_extensions() override;
bool is_available();
- virtual XrCompositionLayerBaseHeader *get_composition_layer() override;
+ virtual int get_composition_layer_count() override;
+ virtual XrCompositionLayerBaseHeader *get_composition_layer(int p_index) override;
+ virtual int get_composition_layer_order(int p_index) override;
private:
static OpenXRCompositionLayerDepthExtension *singleton;
diff --git a/modules/openxr/extensions/openxr_composition_layer_provider.h b/modules/openxr/extensions/openxr_composition_layer_provider.h
index d77ae06174..44f90a0e0e 100644
--- a/modules/openxr/extensions/openxr_composition_layer_provider.h
+++ b/modules/openxr/extensions/openxr_composition_layer_provider.h
@@ -38,7 +38,9 @@
// Interface for OpenXR extensions that provide a composition layer.
class OpenXRCompositionLayerProvider {
public:
- virtual XrCompositionLayerBaseHeader *get_composition_layer() = 0;
+ virtual int get_composition_layer_count() = 0;
+ virtual XrCompositionLayerBaseHeader *get_composition_layer(int p_index) = 0;
+ virtual int get_composition_layer_order(int p_index) = 0;
virtual ~OpenXRCompositionLayerProvider() {}
};
diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
index a9b62819b7..60a934e3a8 100644
--- a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
+++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
@@ -39,7 +39,9 @@ void OpenXRExtensionWrapperExtension::_bind_methods() {
GDVIRTUAL_BIND(_set_session_create_and_get_next_pointer, "next_pointer");
GDVIRTUAL_BIND(_set_swapchain_create_info_and_get_next_pointer, "next_pointer");
GDVIRTUAL_BIND(_set_hand_joint_locations_and_get_next_pointer, "hand_index", "next_pointer");
- GDVIRTUAL_BIND(_get_composition_layer);
+ GDVIRTUAL_BIND(_get_composition_layer_count);
+ GDVIRTUAL_BIND(_get_composition_layer, "index");
+ GDVIRTUAL_BIND(_get_composition_layer_order, "index");
GDVIRTUAL_BIND(_get_suggested_tracker_names);
GDVIRTUAL_BIND(_on_register_metadata);
GDVIRTUAL_BIND(_on_before_instance_created);
@@ -140,16 +142,28 @@ PackedStringArray OpenXRExtensionWrapperExtension::get_suggested_tracker_names()
return PackedStringArray();
}
-XrCompositionLayerBaseHeader *OpenXRExtensionWrapperExtension::get_composition_layer() {
+int OpenXRExtensionWrapperExtension::get_composition_layer_count() {
+ int count = 0;
+ GDVIRTUAL_CALL(_get_composition_layer_count, count);
+ return count;
+}
+
+XrCompositionLayerBaseHeader *OpenXRExtensionWrapperExtension::get_composition_layer(int p_index) {
uint64_t pointer;
- if (GDVIRTUAL_CALL(_get_composition_layer, pointer)) {
+ if (GDVIRTUAL_CALL(_get_composition_layer, p_index, pointer)) {
return reinterpret_cast<XrCompositionLayerBaseHeader *>(pointer);
}
return nullptr;
}
+int OpenXRExtensionWrapperExtension::get_composition_layer_order(int p_index) {
+ int order = 0;
+ GDVIRTUAL_CALL(_get_composition_layer_order, p_index, order);
+ return order;
+}
+
void OpenXRExtensionWrapperExtension::on_register_metadata() {
GDVIRTUAL_CALL(_on_register_metadata);
}
diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.h b/modules/openxr/extensions/openxr_extension_wrapper_extension.h
index edcbc0139b..d3b78bf617 100644
--- a/modules/openxr/extensions/openxr_extension_wrapper_extension.h
+++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.h
@@ -59,7 +59,9 @@ public:
virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) override;
virtual void *set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) override;
virtual void *set_hand_joint_locations_and_get_next_pointer(int p_hand_index, void *p_next_pointer) override;
- virtual XrCompositionLayerBaseHeader *get_composition_layer() override;
+ virtual int get_composition_layer_count() override;
+ virtual XrCompositionLayerBaseHeader *get_composition_layer(int p_index) override;
+ virtual int get_composition_layer_order(int p_index) override;
//TODO workaround as GDExtensionPtr<void> return type results in build error in godot-cpp
GDVIRTUAL1R(uint64_t, _set_system_properties_and_get_next_pointer, GDExtensionPtr<void>);
@@ -67,7 +69,9 @@ public:
GDVIRTUAL1R(uint64_t, _set_session_create_and_get_next_pointer, GDExtensionPtr<void>);
GDVIRTUAL1R(uint64_t, _set_swapchain_create_info_and_get_next_pointer, GDExtensionPtr<void>);
GDVIRTUAL2R(uint64_t, _set_hand_joint_locations_and_get_next_pointer, int, GDExtensionPtr<void>);
- GDVIRTUAL0R(uint64_t, _get_composition_layer);
+ GDVIRTUAL0R(int, _get_composition_layer_count);
+ GDVIRTUAL1R(uint64_t, _get_composition_layer, int);
+ GDVIRTUAL1R(int, _get_composition_layer_order, int);
virtual PackedStringArray get_suggested_tracker_names() override;
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index e978c012b5..8dd017c213 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -2080,18 +2080,29 @@ void OpenXRAPI::end_frame() {
projection_views[eye].pose = views[eye].pose;
}
- Vector<const XrCompositionLayerBaseHeader *> layers_list;
+ Vector<OrderedCompositionLayer> ordered_layers_list;
+ bool projection_layer_is_first = true;
// Add composition layers from providers
for (OpenXRCompositionLayerProvider *provider : composition_layer_providers) {
- XrCompositionLayerBaseHeader *layer = provider->get_composition_layer();
- if (layer) {
- layers_list.push_back(layer);
+ for (int i = 0; i < provider->get_composition_layer_count(); i++) {
+ OrderedCompositionLayer layer = {
+ provider->get_composition_layer(i),
+ provider->get_composition_layer_order(i),
+ };
+ if (layer.composition_layer) {
+ ordered_layers_list.push_back(layer);
+ if (layer.sort_order == 0) {
+ WARN_PRINT_ONCE_ED("Composition layer returned sort order 0, it may be overwritten by projection layer.");
+ } else if (layer.sort_order < 0) {
+ projection_layer_is_first = false;
+ }
+ }
}
}
XrCompositionLayerFlags layer_flags = XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT;
- if (layers_list.size() > 0 || environment_blend_mode != XR_ENVIRONMENT_BLEND_MODE_OPAQUE) {
+ if (!projection_layer_is_first || environment_blend_mode != XR_ENVIRONMENT_BLEND_MODE_OPAQUE) {
layer_flags |= XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
}
@@ -2103,7 +2114,16 @@ void OpenXRAPI::end_frame() {
view_count, // viewCount
projection_views, // views
};
- layers_list.push_back((const XrCompositionLayerBaseHeader *)&projection_layer);
+ ordered_layers_list.push_back({ (const XrCompositionLayerBaseHeader *)&projection_layer, 0 });
+
+ // Sort our layers.
+ ordered_layers_list.sort_custom<OrderedCompositionLayer>();
+
+ // Now make a list we can pass on to OpenXR.
+ Vector<const XrCompositionLayerBaseHeader *> layers_list;
+ for (OrderedCompositionLayer &ordered_layer : ordered_layers_list) {
+ layers_list.push_back(ordered_layer.composition_layer);
+ }
XrFrameEndInfo frame_end_info = {
XR_TYPE_FRAME_END_INFO, // type
diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h
index e1a04a0796..7ec622364b 100644
--- a/modules/openxr/openxr_api.h
+++ b/modules/openxr/openxr_api.h
@@ -287,6 +287,15 @@ private:
RID get_interaction_profile_rid(XrPath p_path);
XrPath get_interaction_profile_path(RID p_interaction_profile);
+ struct OrderedCompositionLayer {
+ const XrCompositionLayerBaseHeader *composition_layer;
+ int sort_order;
+
+ _FORCE_INLINE_ bool operator()(const OrderedCompositionLayer &a, const OrderedCompositionLayer &b) const {
+ return a.sort_order < b.sort_order || (a.sort_order == b.sort_order && uint64_t(a.composition_layer) < uint64_t(b.composition_layer));
+ }
+ };
+
// state changes
bool poll_events();
bool on_state_idle();
diff --git a/modules/regex/SCsub b/modules/regex/SCsub
index 3e31e3c2d9..f5e2dd5dfc 100644
--- a/modules/regex/SCsub
+++ b/modules/regex/SCsub
@@ -29,7 +29,7 @@ if env["builtin_pcre2"]:
"pcre2_extuni.c",
"pcre2_find_bracket.c",
"pcre2_jit_compile.c",
- # "pcre2_jit_match.c", "pcre2_jit_misc.c", # these files are included in pcre2_jit_compile.c.
+ # "pcre2_jit_match.c", "pcre2_jit_misc.c", # Included in `pcre2_jit_compile.c`.
"pcre2_maketables.c",
"pcre2_match.c",
"pcre2_match_data.c",
@@ -44,6 +44,7 @@ if env["builtin_pcre2"]:
"pcre2_substring.c",
"pcre2_tables.c",
"pcre2_ucd.c",
+ # "pcre2_ucptables.c", # Included in `pcre2_tables.c`.
"pcre2_valid_utf.c",
"pcre2_xclass.c",
]
diff --git a/modules/websocket/packet_buffer.h b/modules/websocket/packet_buffer.h
index 25e1b1f15a..f98ee12ef9 100644
--- a/modules/websocket/packet_buffer.h
+++ b/modules/websocket/packet_buffer.h
@@ -33,7 +33,7 @@
#include "core/templates/ring_buffer.h"
-template <class T>
+template <typename T>
class PacketBuffer {
private:
typedef struct {
diff --git a/platform/ios/detect.py b/platform/ios/detect.py
index 4d6e3ae9ba..cd303295ad 100644
--- a/platform/ios/detect.py
+++ b/platform/ios/detect.py
@@ -52,6 +52,7 @@ def get_flags():
("target", "template_debug"),
("use_volk", False),
("supported", ["mono"]),
+ ("builtin_pcre2_with_jit", False),
]
diff --git a/platform/ios/godot_ios.mm b/platform/ios/godot_ios.mm
index 5e66c8b47b..9d35d43344 100644
--- a/platform/ios/godot_ios.mm
+++ b/platform/ios/godot_ios.mm
@@ -102,15 +102,16 @@ int ios_main(int argc, char **argv) {
Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);
- if (err == ERR_HELP) { // Returned by --help and --version, so success.
- return 0;
- } else if (err != OK) {
- return 255;
+ if (err != OK) {
+ if (err == ERR_HELP) { // Returned by --help and --version, so success.
+ return EXIT_SUCCESS;
+ }
+ return EXIT_FAILURE;
}
os->initialize_modules();
- return 0;
+ return os->get_exit_code();
}
void ios_finish() {
diff --git a/platform/linuxbsd/godot_linuxbsd.cpp b/platform/linuxbsd/godot_linuxbsd.cpp
index a2b6fbeb25..b0880c86b8 100644
--- a/platform/linuxbsd/godot_linuxbsd.cpp
+++ b/platform/linuxbsd/godot_linuxbsd.cpp
@@ -72,18 +72,19 @@ int main(int argc, char *argv[]) {
char *ret = getcwd(cwd, PATH_MAX);
Error err = Main::setup(argv[0], argc - 1, &argv[1]);
+
if (err != OK) {
free(cwd);
-
if (err == ERR_HELP) { // Returned by --help and --version, so success.
- return 0;
+ return EXIT_SUCCESS;
}
- return 255;
+ return EXIT_FAILURE;
}
- if (Main::start()) {
- os.set_exit_code(EXIT_SUCCESS);
- os.run(); // it is actually the OS that decides how to run
+ if (Main::start() == EXIT_SUCCESS) {
+ os.run();
+ } else {
+ os.set_exit_code(EXIT_FAILURE);
}
Main::cleanup();
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index 34aa15abbb..226e123648 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -1995,8 +1995,7 @@ void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window
Size2i wsize = window_get_size(p_window);
wpos += srect.position;
if (srect != Rect2i()) {
- wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - wsize.width / 3);
- wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - wsize.height / 3);
+ wpos = wpos.clamp(srect.position, srect.position + srect.size - wsize / 3);
}
window_set_position(wpos, p_window);
}
@@ -2224,8 +2223,7 @@ void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) {
ERR_FAIL_COND(!windows.has(p_window));
Size2i size = p_size;
- size.x = MAX(1, size.x);
- size.y = MAX(1, size.y);
+ size = size.max(Size2i(1, 1));
WindowData &wd = windows[p_window];
@@ -5451,8 +5449,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
} else {
Rect2i srect = screen_get_usable_rect(rq_screen);
Point2i wpos = p_rect.position;
- wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - p_rect.size.width / 3);
- wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - p_rect.size.height / 3);
+ wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3);
win_rect.position = wpos;
}
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index c52ed00b3d..99e9d20f2b 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -83,8 +83,7 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod
Rect2i srect = screen_get_usable_rect(rq_screen);
Point2i wpos = p_rect.position;
if (srect != Rect2i()) {
- wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - p_rect.size.width / 3);
- wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - p_rect.size.height / 3);
+ wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3);
}
// macOS native y-coordinate relative to _get_screens_origin() is negative,
// Godot passes a positive value.
@@ -1877,8 +1876,7 @@ void DisplayServerMacOS::window_set_current_screen(int p_screen, WindowID p_wind
Size2i wsize = window_get_size(p_window);
wpos += srect.position;
- wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - wsize.width / 3);
- wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - wsize.height / 3);
+ wpos = wpos.clamp(srect.position, srect.position + srect.size - wsize / 3);
window_set_position(wpos, p_window);
if (was_fullscreen) {
@@ -2309,8 +2307,7 @@ void DisplayServerMacOS::window_set_window_buttons_offset(const Vector2i &p_offs
WindowData &wd = windows[p_window];
float scale = screen_get_max_scale();
wd.wb_offset = p_offset / scale;
- wd.wb_offset.x = MAX(wd.wb_offset.x, 12);
- wd.wb_offset.y = MAX(wd.wb_offset.y, 12);
+ wd.wb_offset = wd.wb_offset.max(Vector2i(12, 12));
if (wd.window_button_view) {
[wd.window_button_view setOffset:NSMakePoint(wd.wb_offset.x, wd.wb_offset.y)];
}
diff --git a/platform/macos/godot_main_macos.mm b/platform/macos/godot_main_macos.mm
index 3959fb686c..279d805462 100644
--- a/platform/macos/godot_main_macos.mm
+++ b/platform/macos/godot_main_macos.mm
@@ -69,18 +69,21 @@ int main(int argc, char **argv) {
err = Main::setup(argv[0], argc - first_arg, &argv[first_arg]);
}
- if (err == ERR_HELP) { // Returned by --help and --version, so success.
- return 0;
- } else if (err != OK) {
- return 255;
+ if (err != OK) {
+ if (err == ERR_HELP) { // Returned by --help and --version, so success.
+ return EXIT_SUCCESS;
+ }
+ return EXIT_FAILURE;
}
- bool ok;
+ int ret;
@autoreleasepool {
- ok = Main::start();
+ ret = Main::start();
}
- if (ok) {
- os.run(); // It is actually the OS that decides how to run.
+ if (ret) {
+ os.run();
+ } else {
+ os.set_exit_code(EXIT_FAILURE);
}
@autoreleasepool {
diff --git a/platform/macos/native_menu_macos.h b/platform/macos/native_menu_macos.h
index e0e15df832..c00a510fd5 100644
--- a/platform/macos/native_menu_macos.h
+++ b/platform/macos/native_menu_macos.h
@@ -88,6 +88,7 @@ public:
virtual Size2 get_size(const RID &p_rid) const override;
virtual void popup(const RID &p_rid, const Vector2i &p_position) override;
+ virtual void set_interface_direction(const RID &p_rid, bool p_is_rtl) override;
virtual void set_popup_open_callback(const RID &p_rid, const Callable &p_callback) override;
virtual Callable get_popup_open_callback(const RID &p_rid) const override;
virtual void set_popup_close_callback(const RID &p_rid, const Callable &p_callback) override;
diff --git a/platform/macos/native_menu_macos.mm b/platform/macos/native_menu_macos.mm
index cb88f94a28..250b64dc04 100644
--- a/platform/macos/native_menu_macos.mm
+++ b/platform/macos/native_menu_macos.mm
@@ -181,6 +181,9 @@ bool NativeMenuMacOS::has_feature(Feature p_feature) const {
switch (p_feature) {
case FEATURE_GLOBAL_MENU:
case FEATURE_POPUP_MENU:
+ case FEATURE_OPEN_CLOSE_CALLBACK:
+ case FEATURE_HOVER_CALLBACK:
+ case FEATURE_KEY_CALLBACK:
return true;
default:
return false;
@@ -264,6 +267,13 @@ void NativeMenuMacOS::popup(const RID &p_rid, const Vector2i &p_position) {
}
}
+void NativeMenuMacOS::set_interface_direction(const RID &p_rid, bool p_is_rtl) {
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+
+ md->menu.userInterfaceLayoutDirection = p_is_rtl ? NSUserInterfaceLayoutDirectionRightToLeft : NSUserInterfaceLayoutDirectionLeftToRight;
+}
+
void NativeMenuMacOS::set_popup_open_callback(const RID &p_rid, const Callable &p_callback) {
MenuData *md = menus.get_or_null(p_rid);
ERR_FAIL_NULL(md);
diff --git a/platform/web/web_main.cpp b/platform/web/web_main.cpp
index ad2a801881..04513f6d57 100644
--- a/platform/web/web_main.cpp
+++ b/platform/web/web_main.cpp
@@ -109,24 +109,22 @@ extern EMSCRIPTEN_KEEPALIVE int godot_web_main(int argc, char *argv[]) {
// Proper shutdown in case of setup failure.
if (err != OK) {
- int exit_code = (int)err;
- if (err == ERR_HELP) {
- exit_code = 0; // Called with --help.
- }
- os->set_exit_code(exit_code);
// Will only exit after sync.
emscripten_set_main_loop(exit_callback, -1, false);
godot_js_os_finish_async(cleanup_after_sync);
- return exit_code;
+ if (err == ERR_HELP) { // Returned by --help and --version, so success.
+ return EXIT_SUCCESS;
+ }
+ return EXIT_FAILURE;
}
- os->set_exit_code(0);
main_started = true;
// Ease up compatibility.
ResourceLoader::set_abort_on_missing_resources(false);
- Main::start();
+ int ret = Main::start();
+ os->set_exit_code(ret);
os->get_main_loop()->initialize();
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_project_manager_hint() && FileAccess::exists("/tmp/preload.zip")) {
@@ -140,5 +138,5 @@ extern EMSCRIPTEN_KEEPALIVE int godot_web_main(int argc, char *argv[]) {
// We are inside an animation frame, we want to immediately draw on the newly setup canvas.
main_loop_callback();
- return 0;
+ return os->get_exit_code();
}
diff --git a/platform/windows/SCsub b/platform/windows/SCsub
index 18c5878a1a..cf6416b8da 100644
--- a/platform/windows/SCsub
+++ b/platform/windows/SCsub
@@ -17,6 +17,7 @@ common_win = [
"joypad_windows.cpp",
"tts_windows.cpp",
"windows_terminal_logger.cpp",
+ "native_menu_windows.cpp",
"gl_manager_windows_native.cpp",
"gl_manager_windows_angle.cpp",
"wgl_detect_version.cpp",
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 14718d8838..79078456a5 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -903,8 +903,7 @@ static BOOL CALLBACK _MonitorEnumProcPos(HMONITOR hMonitor, HDC hdcMonitor, LPRE
static BOOL CALLBACK _MonitorEnumProcOrigin(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
EnumPosData *data = (EnumPosData *)dwData;
- data->pos.x = MIN(data->pos.x, lprcMonitor->left);
- data->pos.y = MIN(data->pos.y, lprcMonitor->top);
+ data->pos = data->pos.min(Point2(lprcMonitor->left, lprcMonitor->top));
return TRUE;
}
@@ -1637,8 +1636,7 @@ void DisplayServerWindows::window_set_current_screen(int p_screen, WindowID p_wi
Size2i wsize = window_get_size(p_window);
wpos += srect.position;
- wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - wsize.width / 3);
- wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - wsize.height / 3);
+ wpos = wpos.clamp(srect.position, srect.position + srect.size - wsize / 3);
window_set_position(wpos, p_window);
}
}
@@ -3664,6 +3662,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
// Process window messages.
switch (uMsg) {
+ case WM_MENUCOMMAND: {
+ native_menu->_menu_activate(HMENU(lParam), (int)wParam);
+ } break;
case WM_CREATE: {
if (is_dark_mode_supported() && dark_title_available) {
BOOL value = is_dark_mode();
@@ -5073,8 +5074,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
Rect2i srect = screen_get_usable_rect(rq_screen);
Point2i wpos = p_rect.position;
if (srect != Rect2i()) {
- wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - p_rect.size.width / 3);
- wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - p_rect.size.height / 3);
+ wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3);
}
WindowRect.left = wpos.x;
@@ -5468,7 +5468,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
if (tts_enabled) {
tts = memnew(TTS_Windows);
}
- native_menu = memnew(NativeMenu);
+ native_menu = memnew(NativeMenuWindows);
// Enforce default keep screen on value.
screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on"));
@@ -5494,6 +5494,32 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
GetImmersiveColorFromColorSetEx = (GetImmersiveColorFromColorSetExPtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(95));
GetImmersiveColorTypeFromName = (GetImmersiveColorTypeFromNamePtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(96));
GetImmersiveUserColorSetPreference = (GetImmersiveUserColorSetPreferencePtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(98));
+ if (os_ver.dwBuildNumber >= 17763) {
+ AllowDarkModeForAppPtr AllowDarkModeForApp = nullptr;
+ SetPreferredAppModePtr SetPreferredAppMode = nullptr;
+ FlushMenuThemesPtr FlushMenuThemes = nullptr;
+ if (os_ver.dwBuildNumber < 18362) {
+ AllowDarkModeForApp = (AllowDarkModeForAppPtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(135));
+ } else {
+ SetPreferredAppMode = (SetPreferredAppModePtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(135));
+ FlushMenuThemes = (FlushMenuThemesPtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(136));
+ }
+ RefreshImmersiveColorPolicyStatePtr RefreshImmersiveColorPolicyState = (RefreshImmersiveColorPolicyStatePtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(104));
+ if (ShouldAppsUseDarkMode) {
+ bool dark_mode = ShouldAppsUseDarkMode();
+ if (SetPreferredAppMode) {
+ SetPreferredAppMode(dark_mode ? APPMODE_ALLOWDARK : APPMODE_DEFAULT);
+ } else if (AllowDarkModeForApp) {
+ AllowDarkModeForApp(dark_mode);
+ }
+ if (RefreshImmersiveColorPolicyState) {
+ RefreshImmersiveColorPolicyState();
+ }
+ if (FlushMenuThemes) {
+ FlushMenuThemes();
+ }
+ }
+ }
ux_theme_available = ShouldAppsUseDarkMode && GetImmersiveColorFromColorSetEx && GetImmersiveColorTypeFromName && GetImmersiveUserColorSetPreference;
if (os_ver.dwBuildNumber >= 18363) {
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index cc5099cbb4..1191f22968 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -61,6 +61,8 @@
#include "gl_manager_windows_native.h"
#endif // GLES3_ENABLED
+#include "native_menu_windows.h"
+
#include <io.h>
#include <stdio.h>
@@ -152,11 +154,23 @@ typedef UINT(WINAPI *WTInfoPtr)(UINT p_category, UINT p_index, LPVOID p_output);
typedef BOOL(WINAPI *WTPacketPtr)(HANDLE p_ctx, UINT p_param, LPVOID p_packets);
typedef BOOL(WINAPI *WTEnablePtr)(HANDLE p_ctx, BOOL p_enable);
+enum PreferredAppMode {
+ APPMODE_DEFAULT = 0,
+ APPMODE_ALLOWDARK = 1,
+ APPMODE_FORCEDARK = 2,
+ APPMODE_FORCELIGHT = 3,
+ APPMODE_MAX = 4
+};
+
typedef bool(WINAPI *ShouldAppsUseDarkModePtr)();
typedef DWORD(WINAPI *GetImmersiveColorFromColorSetExPtr)(UINT dwImmersiveColorSet, UINT dwImmersiveColorType, bool bIgnoreHighContrast, UINT dwHighContrastCacheMode);
typedef int(WINAPI *GetImmersiveColorTypeFromNamePtr)(const WCHAR *name);
typedef int(WINAPI *GetImmersiveUserColorSetPreferencePtr)(bool bForceCheckRegistry, bool bSkipCheckOnFail);
typedef HRESULT(WINAPI *RtlGetVersionPtr)(OSVERSIONINFOW *lpVersionInformation);
+typedef bool(WINAPI *AllowDarkModeForAppPtr)(bool darkMode);
+typedef PreferredAppMode(WINAPI *SetPreferredAppModePtr)(PreferredAppMode appMode);
+typedef void(WINAPI *RefreshImmersiveColorPolicyStatePtr)();
+typedef void(WINAPI *FlushMenuThemesPtr)();
// Windows Ink API
#ifndef POINTER_STRUCTURES
@@ -358,7 +372,7 @@ class DisplayServerWindows : public DisplayServer {
HANDLE power_request;
TTS_Windows *tts = nullptr;
- NativeMenu *native_menu = nullptr;
+ NativeMenuWindows *native_menu = nullptr;
struct WindowData {
HWND hWnd;
diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp
index 0c6721e118..fe2a930cc8 100644
--- a/platform/windows/export/export_plugin.cpp
+++ b/platform/windows/export/export_plugin.cpp
@@ -285,15 +285,15 @@ Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset>
if (p_preset->get("codesign/enable")) {
_code_sign(p_preset, pck_path);
- String wrapper_path = p_path.get_basename() + ".console.exe";
+ String wrapper_path = path.get_basename() + ".console.exe";
if (FileAccess::exists(wrapper_path)) {
_code_sign(p_preset, wrapper_path);
}
}
if (embedded) {
- Ref<DirAccess> tmp_dir = DirAccess::create_for_path(p_path.get_base_dir());
- err = tmp_dir->rename(pck_path, p_path);
+ Ref<DirAccess> tmp_dir = DirAccess::create_for_path(path.get_base_dir());
+ err = tmp_dir->rename(pck_path, path);
if (err != OK) {
add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), vformat(TTR("Failed to rename temporary file \"%s\"."), pck_path));
}
diff --git a/platform/windows/godot_windows.cpp b/platform/windows/godot_windows.cpp
index f86ecd87fb..5f41b4e568 100644
--- a/platform/windows/godot_windows.cpp
+++ b/platform/windows/godot_windows.cpp
@@ -171,13 +171,15 @@ int widechar_main(int argc, wchar_t **argv) {
delete[] argv_utf8;
if (err == ERR_HELP) { // Returned by --help and --version, so success.
- return 0;
+ return EXIT_SUCCESS;
}
- return 255;
+ return EXIT_FAILURE;
}
- if (Main::start()) {
+ if (Main::start() == EXIT_SUCCESS) {
os.run();
+ } else {
+ os.set_exit_code(EXIT_FAILURE);
}
Main::cleanup();
diff --git a/platform/windows/native_menu_windows.cpp b/platform/windows/native_menu_windows.cpp
new file mode 100644
index 0000000000..40a08f87df
--- /dev/null
+++ b/platform/windows/native_menu_windows.cpp
@@ -0,0 +1,1165 @@
+/**************************************************************************/
+/* native_menu_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 "native_menu_windows.h"
+
+#include "display_server_windows.h"
+
+#include "scene/resources/image_texture.h"
+
+HBITMAP NativeMenuWindows::_make_bitmap(const Ref<Image> &p_img) const {
+ Vector2i texture_size = p_img->get_size();
+ UINT image_size = texture_size.width * texture_size.height;
+
+ COLORREF *buffer = nullptr;
+
+ BITMAPV5HEADER bi;
+ ZeroMemory(&bi, sizeof(bi));
+ bi.bV5Size = sizeof(bi);
+ bi.bV5Width = texture_size.width;
+ bi.bV5Height = -texture_size.height;
+ bi.bV5Planes = 1;
+ bi.bV5BitCount = 32;
+ bi.bV5Compression = BI_BITFIELDS;
+ bi.bV5RedMask = 0x00ff0000;
+ bi.bV5GreenMask = 0x0000ff00;
+ bi.bV5BlueMask = 0x000000ff;
+ bi.bV5AlphaMask = 0xff000000;
+
+ HDC dc = GetDC(nullptr);
+ HBITMAP bitmap = CreateDIBSection(dc, reinterpret_cast<BITMAPINFO *>(&bi), DIB_RGB_COLORS, reinterpret_cast<void **>(&buffer), nullptr, 0);
+ for (UINT index = 0; index < image_size; index++) {
+ int row_index = floor(index / texture_size.width);
+ int column_index = (index % int(texture_size.width));
+ const Color &c = p_img->get_pixel(column_index, row_index);
+ *(buffer + index) = c.to_argb32();
+ }
+ ReleaseDC(nullptr, dc);
+
+ return bitmap;
+}
+
+void NativeMenuWindows::_menu_activate(HMENU p_menu, int p_index) const {
+ if (menu_lookup.has(p_menu)) {
+ MenuData *md = menus.get_or_null(menu_lookup[p_menu]);
+ if (md) {
+ int count = GetMenuItemCount(md->menu);
+ if (p_index >= 0 && p_index < count) {
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_STATE | MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_index, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ if (item_data->max_states > 0) {
+ item_data->state++;
+ if (item_data->state >= item_data->max_states) {
+ item_data->state = 0;
+ }
+ }
+
+ if (item_data->checkable_type == CHECKABLE_TYPE_CHECK_BOX) {
+ if ((item.fState & MFS_CHECKED) == MFS_CHECKED) {
+ item.fState &= ~MFS_CHECKED;
+ } else {
+ item.fState |= MFS_CHECKED;
+ }
+ SetMenuItemInfoW(md->menu, p_index, true, &item);
+ }
+
+ if (item_data->callback.is_valid()) {
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[1] = { &item_data->meta };
+
+ item_data->callback.callp(args, 1, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute menu callback: %s.", Variant::get_callable_error_text(item_data->callback, args, 1, ce)));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+bool NativeMenuWindows::has_feature(Feature p_feature) const {
+ switch (p_feature) {
+ // case FEATURE_GLOBAL_MENU:
+ // case FEATURE_OPEN_CLOSE_CALLBACK:
+ // case FEATURE_HOVER_CALLBACK:
+ // case FEATURE_KEY_CALLBACK:
+ case FEATURE_POPUP_MENU:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool NativeMenuWindows::has_system_menu(SystemMenus p_menu_id) const {
+ return false;
+}
+
+RID NativeMenuWindows::get_system_menu(SystemMenus p_menu_id) const {
+ return RID();
+}
+
+RID NativeMenuWindows::create_menu() {
+ MenuData *md = memnew(MenuData);
+ md->menu = CreatePopupMenu();
+
+ MENUINFO menu_info;
+ ZeroMemory(&menu_info, sizeof(menu_info));
+ menu_info.cbSize = sizeof(menu_info);
+ menu_info.fMask = MIM_STYLE;
+ menu_info.dwStyle = MNS_NOTIFYBYPOS;
+ SetMenuInfo(md->menu, &menu_info);
+
+ RID rid = menus.make_rid(md);
+ menu_lookup[md->menu] = rid;
+ return rid;
+}
+
+bool NativeMenuWindows::has_menu(const RID &p_rid) const {
+ return menus.owns(p_rid);
+}
+
+void NativeMenuWindows::free_menu(const RID &p_rid) {
+ MenuData *md = menus.get_or_null(p_rid);
+ if (md) {
+ clear(p_rid);
+ DestroyMenu(md->menu);
+ menus.free(p_rid);
+ menu_lookup.erase(md->menu);
+ memdelete(md);
+ }
+}
+
+Size2 NativeMenuWindows::get_size(const RID &p_rid) const {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, Size2());
+
+ Size2 size;
+ int count = GetMenuItemCount(md->menu);
+ for (int i = 0; i < count; i++) {
+ RECT rect;
+ if (GetMenuItemRect(NULL, md->menu, i, &rect)) {
+ size.x = MAX(size.x, rect.right - rect.left);
+ size.y += rect.bottom - rect.top;
+ }
+ }
+ return size;
+}
+
+void NativeMenuWindows::popup(const RID &p_rid, const Vector2i &p_position) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+
+ HWND hwnd = (HWND)DisplayServer::get_singleton()->window_get_native_handle(DisplayServer::WINDOW_HANDLE, DisplayServer::MAIN_WINDOW_ID);
+ UINT flags = TPM_HORIZONTAL | TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON | TPM_VERPOSANIMATION;
+ if (md->is_rtl) {
+ flags |= TPM_LAYOUTRTL;
+ }
+ SetForegroundWindow(hwnd);
+ TrackPopupMenuEx(md->menu, flags, p_position.x, p_position.y, hwnd, nullptr);
+ PostMessage(hwnd, WM_NULL, 0, 0);
+}
+
+void NativeMenuWindows::set_interface_direction(const RID &p_rid, bool p_is_rtl) {
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+
+ if (md->is_rtl == p_is_rtl) {
+ return;
+ }
+ md->is_rtl = p_is_rtl;
+}
+
+void NativeMenuWindows::set_popup_open_callback(const RID &p_rid, const Callable &p_callback) {
+ // Not supported.
+}
+
+Callable NativeMenuWindows::get_popup_open_callback(const RID &p_rid) const {
+ // Not supported.
+ return Callable();
+}
+
+void NativeMenuWindows::set_popup_close_callback(const RID &p_rid, const Callable &p_callback) {
+ // Not supported.
+}
+
+Callable NativeMenuWindows::get_popup_close_callback(const RID &p_rid) const {
+ // Not supported.
+ return Callable();
+}
+
+void NativeMenuWindows::set_minimum_width(const RID &p_rid, float p_width) {
+ // Not supported.
+}
+
+float NativeMenuWindows::get_minimum_width(const RID &p_rid) const {
+ // Not supported.
+ return 0.f;
+}
+
+int NativeMenuWindows::add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag, int p_index) {
+ MenuData *md = menus.get_or_null(p_rid);
+ MenuData *md_sub = menus.get_or_null(p_submenu_rid);
+ ERR_FAIL_NULL_V(md, -1);
+ ERR_FAIL_NULL_V(md_sub, -1);
+ ERR_FAIL_COND_V_MSG(md->menu == md_sub->menu, -1, "Can't set submenu to self!");
+
+ if (p_index == -1) {
+ p_index = GetMenuItemCount(md->menu);
+ } else {
+ p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu));
+ }
+
+ MenuItemData *item_data = memnew(MenuItemData);
+ item_data->meta = p_tag;
+ item_data->checkable_type = CHECKABLE_TYPE_NONE;
+ item_data->max_states = 0;
+ item_data->state = 0;
+
+ Char16String label = p_label.utf16();
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA | MIIM_SUBMENU;
+ item.fType = MFT_STRING;
+ item.hSubMenu = md_sub->menu;
+ item.dwItemData = (ULONG_PTR)item_data;
+ item.dwTypeData = (LPWSTR)label.get_data();
+
+ if (!InsertMenuItemW(md->menu, p_index, true, &item)) {
+ memdelete(item_data);
+ return -1;
+ }
+ return p_index;
+}
+
+int NativeMenuWindows::add_item(const RID &p_rid, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ if (p_index == -1) {
+ p_index = GetMenuItemCount(md->menu);
+ } else {
+ p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu));
+ }
+
+ MenuItemData *item_data = memnew(MenuItemData);
+ item_data->callback = p_callback;
+ item_data->meta = p_tag;
+ item_data->checkable_type = CHECKABLE_TYPE_NONE;
+ item_data->max_states = 0;
+ item_data->state = 0;
+
+ Char16String label = p_label.utf16();
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA;
+ item.fType = MFT_STRING;
+ item.dwItemData = (ULONG_PTR)item_data;
+ item.dwTypeData = (LPWSTR)label.get_data();
+
+ if (!InsertMenuItemW(md->menu, p_index, true, &item)) {
+ memdelete(item_data);
+ return -1;
+ }
+ return p_index;
+}
+
+int NativeMenuWindows::add_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ if (p_index == -1) {
+ p_index = GetMenuItemCount(md->menu);
+ } else {
+ p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu));
+ }
+
+ MenuItemData *item_data = memnew(MenuItemData);
+ item_data->callback = p_callback;
+ item_data->meta = p_tag;
+ item_data->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
+ item_data->max_states = 0;
+ item_data->state = 0;
+
+ Char16String label = p_label.utf16();
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA;
+ item.fType = MFT_STRING;
+ item.dwItemData = (ULONG_PTR)item_data;
+ item.dwTypeData = (LPWSTR)label.get_data();
+
+ if (!InsertMenuItemW(md->menu, p_index, true, &item)) {
+ memdelete(item_data);
+ return -1;
+ }
+ return p_index;
+}
+
+int NativeMenuWindows::add_icon_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ if (p_index == -1) {
+ p_index = GetMenuItemCount(md->menu);
+ } else {
+ p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu));
+ }
+
+ MenuItemData *item_data = memnew(MenuItemData);
+ item_data->callback = p_callback;
+ item_data->meta = p_tag;
+ item_data->checkable_type = CHECKABLE_TYPE_NONE;
+ item_data->max_states = 0;
+ item_data->state = 0;
+ item_data->img = p_icon->get_image();
+ item_data->img = item_data->img->duplicate();
+ if (item_data->img->is_compressed()) {
+ item_data->img->decompress();
+ }
+ item_data->bmp = _make_bitmap(item_data->img);
+
+ Char16String label = p_label.utf16();
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA | MIIM_BITMAP;
+ item.fType = MFT_STRING;
+ item.dwItemData = (ULONG_PTR)item_data;
+ item.dwTypeData = (LPWSTR)label.get_data();
+ item.hbmpItem = item_data->bmp;
+
+ if (!InsertMenuItemW(md->menu, p_index, true, &item)) {
+ memdelete(item_data);
+ return -1;
+ }
+ return p_index;
+}
+
+int NativeMenuWindows::add_icon_check_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ if (p_index == -1) {
+ p_index = GetMenuItemCount(md->menu);
+ } else {
+ p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu));
+ }
+
+ MenuItemData *item_data = memnew(MenuItemData);
+ item_data->callback = p_callback;
+ item_data->meta = p_tag;
+ item_data->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
+ item_data->max_states = 0;
+ item_data->state = 0;
+ item_data->img = p_icon->get_image();
+ item_data->img = item_data->img->duplicate();
+ if (item_data->img->is_compressed()) {
+ item_data->img->decompress();
+ }
+ item_data->bmp = _make_bitmap(item_data->img);
+
+ Char16String label = p_label.utf16();
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA | MIIM_BITMAP;
+ item.fType = MFT_STRING;
+ item.dwItemData = (ULONG_PTR)item_data;
+ item.dwTypeData = (LPWSTR)label.get_data();
+ item.hbmpItem = item_data->bmp;
+
+ if (!InsertMenuItemW(md->menu, p_index, true, &item)) {
+ memdelete(item_data);
+ return -1;
+ }
+ return p_index;
+}
+
+int NativeMenuWindows::add_radio_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ if (p_index == -1) {
+ p_index = GetMenuItemCount(md->menu);
+ } else {
+ p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu));
+ }
+
+ MenuItemData *item_data = memnew(MenuItemData);
+ item_data->callback = p_callback;
+ item_data->meta = p_tag;
+ item_data->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
+ item_data->max_states = 0;
+ item_data->state = 0;
+
+ Char16String label = p_label.utf16();
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA;
+ item.fType = MFT_STRING | MFT_RADIOCHECK;
+ item.dwItemData = (ULONG_PTR)item_data;
+ item.dwTypeData = (LPWSTR)label.get_data();
+
+ if (!InsertMenuItemW(md->menu, p_index, true, &item)) {
+ memdelete(item_data);
+ return -1;
+ }
+ return p_index;
+}
+
+int NativeMenuWindows::add_icon_radio_check_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ if (p_index == -1) {
+ p_index = GetMenuItemCount(md->menu);
+ } else {
+ p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu));
+ }
+
+ MenuItemData *item_data = memnew(MenuItemData);
+ item_data->callback = p_callback;
+ item_data->meta = p_tag;
+ item_data->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
+ item_data->max_states = 0;
+ item_data->state = 0;
+ item_data->img = p_icon->get_image();
+ item_data->img = item_data->img->duplicate();
+ if (item_data->img->is_compressed()) {
+ item_data->img->decompress();
+ }
+ item_data->bmp = _make_bitmap(item_data->img);
+
+ Char16String label = p_label.utf16();
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA | MIIM_BITMAP;
+ item.fType = MFT_STRING | MFT_RADIOCHECK;
+ item.dwItemData = (ULONG_PTR)item_data;
+ item.dwTypeData = (LPWSTR)label.get_data();
+ item.hbmpItem = item_data->bmp;
+
+ if (!InsertMenuItemW(md->menu, p_index, true, &item)) {
+ memdelete(item_data);
+ return -1;
+ }
+ return p_index;
+}
+
+int NativeMenuWindows::add_multistate_item(const RID &p_rid, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ if (p_index == -1) {
+ p_index = GetMenuItemCount(md->menu);
+ } else {
+ p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu));
+ }
+
+ MenuItemData *item_data = memnew(MenuItemData);
+ item_data->callback = p_callback;
+ item_data->meta = p_tag;
+ item_data->checkable_type = CHECKABLE_TYPE_NONE;
+ item_data->max_states = p_max_states;
+ item_data->state = p_default_state;
+
+ Char16String label = p_label.utf16();
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA;
+ item.fType = MFT_STRING;
+ item.dwItemData = (ULONG_PTR)item_data;
+ item.dwTypeData = (LPWSTR)label.get_data();
+
+ if (!InsertMenuItemW(md->menu, p_index, true, &item)) {
+ memdelete(item_data);
+ return -1;
+ }
+ return p_index;
+}
+
+int NativeMenuWindows::add_separator(const RID &p_rid, int p_index) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ if (p_index == -1) {
+ p_index = GetMenuItemCount(md->menu);
+ } else {
+ p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu));
+ }
+
+ MenuItemData *item_data = memnew(MenuItemData);
+ item_data->checkable_type = CHECKABLE_TYPE_NONE;
+ item_data->max_states = 0;
+ item_data->state = 0;
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_DATA;
+ item.fType = MFT_SEPARATOR;
+ item.dwItemData = (ULONG_PTR)item_data;
+
+ if (!InsertMenuItemW(md->menu, p_index, true, &item)) {
+ memdelete(item_data);
+ return -1;
+ }
+ return p_index;
+}
+
+int NativeMenuWindows::find_item_index_with_text(const RID &p_rid, const String &p_text) const {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ MENUITEMINFOW item;
+ int count = GetMenuItemCount(md->menu);
+ for (int i = 0; i < count; i++) {
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_STRING;
+ item.dwTypeData = nullptr;
+ if (GetMenuItemInfoW(md->menu, i, true, &item)) {
+ item.cch++;
+ Char16String str;
+ str.resize(item.cch);
+ item.dwTypeData = (LPWSTR)str.ptrw();
+ if (GetMenuItemInfoW(md->menu, i, true, &item)) {
+ if (String::utf16((const char16_t *)str.get_data()) == p_text) {
+ return i;
+ }
+ }
+ }
+ }
+ return -1;
+}
+
+int NativeMenuWindows::find_item_index_with_tag(const RID &p_rid, const Variant &p_tag) const {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ MENUITEMINFOW item;
+ int count = GetMenuItemCount(md->menu);
+ for (int i = 0; i < count; i++) {
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, i, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ if (item_data->meta == p_tag) {
+ return i;
+ }
+ }
+ }
+ }
+ return -1;
+}
+
+bool NativeMenuWindows::is_item_checked(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, false);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, false);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_STATE;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ return (item.fState & MFS_CHECKED) == MFS_CHECKED;
+ }
+ return false;
+}
+
+bool NativeMenuWindows::is_item_checkable(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, false);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, false);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ return item_data->checkable_type == CHECKABLE_TYPE_CHECK_BOX;
+ }
+ }
+ return false;
+}
+
+bool NativeMenuWindows::is_item_radio_checkable(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, false);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, false);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ return item_data->checkable_type == CHECKABLE_TYPE_RADIO_BUTTON;
+ }
+ }
+ return false;
+}
+
+Callable NativeMenuWindows::get_item_callback(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, Callable());
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, Callable());
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, Callable());
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ return item_data->callback;
+ }
+ }
+ return Callable();
+}
+
+Callable NativeMenuWindows::get_item_key_callback(const RID &p_rid, int p_idx) const {
+ // Not supported.
+ return Callable();
+}
+
+Variant NativeMenuWindows::get_item_tag(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, Variant());
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, Variant());
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, Variant());
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ return item_data->meta;
+ }
+ }
+ return Variant();
+}
+
+String NativeMenuWindows::get_item_text(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, String());
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, String());
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, String());
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_STRING;
+ item.dwTypeData = nullptr;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ item.cch++;
+ Char16String str;
+ str.resize(item.cch);
+ item.dwTypeData = (LPWSTR)str.ptrw();
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ return String::utf16((const char16_t *)str.get_data());
+ }
+ }
+ return String();
+}
+
+RID NativeMenuWindows::get_item_submenu(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, RID());
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, RID());
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, RID());
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_SUBMENU;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ if (menu_lookup.has(item.hSubMenu)) {
+ return menu_lookup[item.hSubMenu];
+ }
+ }
+ return RID();
+}
+
+Key NativeMenuWindows::get_item_accelerator(const RID &p_rid, int p_idx) const {
+ // Not supported.
+ return Key::NONE;
+}
+
+bool NativeMenuWindows::is_item_disabled(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, false);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, false);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_STATE;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ return (item.fState & MFS_DISABLED) == MFS_DISABLED;
+ }
+ return false;
+}
+
+bool NativeMenuWindows::is_item_hidden(const RID &p_rid, int p_idx) const {
+ // Not supported.
+ return false;
+}
+
+String NativeMenuWindows::get_item_tooltip(const RID &p_rid, int p_idx) const {
+ // Not supported.
+ return String();
+}
+
+int NativeMenuWindows::get_item_state(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, -1);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, -1);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ return item_data->state;
+ }
+ }
+ return -1;
+}
+
+int NativeMenuWindows::get_item_max_states(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, -1);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, -1);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ return item_data->max_states;
+ }
+ }
+ return -1;
+}
+
+Ref<Texture2D> NativeMenuWindows::get_item_icon(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, Ref<Texture2D>());
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, Ref<Texture2D>());
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, Ref<Texture2D>());
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ return ImageTexture::create_from_image(item_data->img);
+ }
+ }
+ return Ref<Texture2D>();
+}
+
+int NativeMenuWindows::get_item_indentation_level(const RID &p_rid, int p_idx) const {
+ // Not supported.
+ return 0;
+}
+
+void NativeMenuWindows::set_item_checked(const RID &p_rid, int p_idx, bool p_checked) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_STATE;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ if (p_checked) {
+ item.fState |= MFS_CHECKED;
+ } else {
+ item.fState &= ~MFS_CHECKED;
+ }
+ SetMenuItemInfoW(md->menu, p_idx, true, &item);
+ }
+}
+
+void NativeMenuWindows::set_item_checkable(const RID &p_rid, int p_idx, bool p_checkable) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ item.fType &= ~MFT_RADIOCHECK;
+ item_data->checkable_type = (p_checkable) ? CHECKABLE_TYPE_CHECK_BOX : CHECKABLE_TYPE_NONE;
+ SetMenuItemInfoW(md->menu, p_idx, true, &item);
+ }
+ }
+}
+
+void NativeMenuWindows::set_item_radio_checkable(const RID &p_rid, int p_idx, bool p_checkable) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ if (p_checkable) {
+ item.fType |= MFT_RADIOCHECK;
+ item_data->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
+ } else {
+ item.fType &= ~MFT_RADIOCHECK;
+ item_data->checkable_type = CHECKABLE_TYPE_NONE;
+ }
+ SetMenuItemInfoW(md->menu, p_idx, true, &item);
+ }
+ }
+}
+
+void NativeMenuWindows::set_item_callback(const RID &p_rid, int p_idx, const Callable &p_callback) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ item_data->callback = p_callback;
+ }
+ }
+}
+
+void NativeMenuWindows::set_item_key_callback(const RID &p_rid, int p_idx, const Callable &p_key_callback) {
+ // Not supported.
+}
+
+void NativeMenuWindows::set_item_hover_callbacks(const RID &p_rid, int p_idx, const Callable &p_callback) {
+ // Not supported.
+}
+
+void NativeMenuWindows::set_item_tag(const RID &p_rid, int p_idx, const Variant &p_tag) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ item_data->meta = p_tag;
+ }
+ }
+}
+
+void NativeMenuWindows::set_item_text(const RID &p_rid, int p_idx, const String &p_text) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ Char16String label = p_text.utf16();
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ item.dwTypeData = (LPWSTR)label.get_data();
+ SetMenuItemInfoW(md->menu, p_idx, true, &item);
+ }
+}
+
+void NativeMenuWindows::set_item_submenu(const RID &p_rid, int p_idx, const RID &p_submenu_rid) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MenuData *md_sub = menus.get_or_null(p_submenu_rid);
+ ERR_FAIL_COND_MSG(md->menu == md_sub->menu, "Can't set submenu to self!");
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_SUBMENU;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ if (p_submenu_rid.is_valid()) {
+ item.hSubMenu = md_sub->menu;
+ } else {
+ item.hSubMenu = 0;
+ }
+ SetMenuItemInfoW(md->menu, p_idx, true, &item);
+ }
+}
+
+void NativeMenuWindows::set_item_accelerator(const RID &p_rid, int p_idx, Key p_keycode) {
+ // Not supported.
+}
+
+void NativeMenuWindows::set_item_disabled(const RID &p_rid, int p_idx, bool p_disabled) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_STATE;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ if (p_disabled) {
+ item.fState |= MFS_DISABLED;
+ } else {
+ item.fState &= ~MFS_DISABLED;
+ }
+ SetMenuItemInfoW(md->menu, p_idx, true, &item);
+ }
+}
+
+void NativeMenuWindows::set_item_hidden(const RID &p_rid, int p_idx, bool p_hidden) {
+ // Not supported.
+}
+
+void NativeMenuWindows::set_item_tooltip(const RID &p_rid, int p_idx, const String &p_tooltip) {
+ // Not supported.
+}
+
+void NativeMenuWindows::set_item_state(const RID &p_rid, int p_idx, int p_state) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ item_data->state = p_state;
+ }
+ }
+}
+
+void NativeMenuWindows::set_item_max_states(const RID &p_rid, int p_idx, int p_max_states) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ item_data->max_states = p_max_states;
+ }
+ }
+}
+
+void NativeMenuWindows::set_item_icon(const RID &p_rid, int p_idx, const Ref<Texture2D> &p_icon) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA | MIIM_BITMAP;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ if (item_data->bmp) {
+ DeleteObject(item_data->bmp);
+ }
+ if (p_icon.is_valid()) {
+ item_data->img = p_icon->get_image();
+ item_data->img = item_data->img->duplicate();
+ if (item_data->img->is_compressed()) {
+ item_data->img->decompress();
+ }
+ item_data->bmp = _make_bitmap(item_data->img);
+ } else {
+ item_data->img = Ref<Image>();
+ item_data->bmp = 0;
+ }
+ item.hbmpItem = item_data->bmp;
+ SetMenuItemInfoW(md->menu, p_idx, true, &item);
+ }
+ }
+}
+
+void NativeMenuWindows::set_item_indentation_level(const RID &p_rid, int p_idx, int p_level) {
+ // Not supported.
+}
+
+int NativeMenuWindows::get_item_count(const RID &p_rid) const {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, 0);
+
+ return GetMenuItemCount(md->menu);
+}
+
+bool NativeMenuWindows::is_system_menu(const RID &p_rid) const {
+ return false;
+}
+
+void NativeMenuWindows::remove_item(const RID &p_rid, int p_idx) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ if (item_data->bmp) {
+ DeleteObject(item_data->bmp);
+ }
+ memdelete(item_data);
+ }
+ }
+ RemoveMenu(md->menu, p_idx, MF_BYPOSITION);
+}
+
+void NativeMenuWindows::clear(const RID &p_rid) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+
+ MENUITEMINFOW item;
+ int count = GetMenuItemCount(md->menu);
+ for (int i = 0; i < count; i++) {
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, 0, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ if (item_data->bmp) {
+ DeleteObject(item_data->bmp);
+ }
+ memdelete(item_data);
+ }
+ }
+ RemoveMenu(md->menu, 0, MF_BYPOSITION);
+ }
+}
+
+NativeMenuWindows::NativeMenuWindows() {}
+
+NativeMenuWindows::~NativeMenuWindows() {}
diff --git a/platform/windows/native_menu_windows.h b/platform/windows/native_menu_windows.h
new file mode 100644
index 0000000000..6eab56d8b6
--- /dev/null
+++ b/platform/windows/native_menu_windows.h
@@ -0,0 +1,151 @@
+/**************************************************************************/
+/* native_menu_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 NATIVE_MENU_WINDOWS_H
+#define NATIVE_MENU_WINDOWS_H
+
+#include "core/templates/hash_map.h"
+#include "core/templates/rid_owner.h"
+#include "servers/native_menu.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+class NativeMenuWindows : public NativeMenu {
+ GDCLASS(NativeMenuWindows, NativeMenu)
+
+ enum GlobalMenuCheckType {
+ CHECKABLE_TYPE_NONE,
+ CHECKABLE_TYPE_CHECK_BOX,
+ CHECKABLE_TYPE_RADIO_BUTTON,
+ };
+
+ struct MenuItemData {
+ Callable callback;
+ Variant meta;
+ GlobalMenuCheckType checkable_type;
+ int max_states = 0;
+ int state = 0;
+ Ref<Image> img;
+ HBITMAP bmp = 0;
+ };
+
+ struct MenuData {
+ HMENU menu = 0;
+ bool is_rtl = false;
+ };
+
+ mutable RID_PtrOwner<MenuData> menus;
+ HashMap<HMENU, RID> menu_lookup;
+
+ HBITMAP _make_bitmap(const Ref<Image> &p_img) const;
+
+public:
+ void _menu_activate(HMENU p_menu, int p_index) const;
+
+ virtual bool has_feature(Feature p_feature) const override;
+
+ virtual bool has_system_menu(SystemMenus p_menu_id) const override;
+ virtual RID get_system_menu(SystemMenus p_menu_id) const override;
+
+ virtual RID create_menu() override;
+ virtual bool has_menu(const RID &p_rid) const override;
+ virtual void free_menu(const RID &p_rid) override;
+
+ virtual Size2 get_size(const RID &p_rid) const override;
+ virtual void popup(const RID &p_rid, const Vector2i &p_position) override;
+
+ virtual void set_interface_direction(const RID &p_rid, bool p_is_rtl) override;
+ virtual void set_popup_open_callback(const RID &p_rid, const Callable &p_callback) override;
+ virtual Callable get_popup_open_callback(const RID &p_rid) const override;
+ virtual void set_popup_close_callback(const RID &p_rid, const Callable &p_callback) override;
+ virtual Callable get_popup_close_callback(const RID &p_rid) const override;
+ virtual void set_minimum_width(const RID &p_rid, float p_width) override;
+ virtual float get_minimum_width(const RID &p_rid) const override;
+
+ virtual int add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag = Variant(), int p_index = -1) override;
+ virtual int add_item(const RID &p_rid, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_icon_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_icon_check_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_radio_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_icon_radio_check_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_multistate_item(const RID &p_rid, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_separator(const RID &p_rid, int p_index = -1) override;
+
+ virtual int find_item_index_with_text(const RID &p_rid, const String &p_text) const override;
+ virtual int find_item_index_with_tag(const RID &p_rid, const Variant &p_tag) const override;
+
+ virtual bool is_item_checked(const RID &p_rid, int p_idx) const override;
+ virtual bool is_item_checkable(const RID &p_rid, int p_idx) const override;
+ virtual bool is_item_radio_checkable(const RID &p_rid, int p_idx) const override;
+ virtual Callable get_item_callback(const RID &p_rid, int p_idx) const override;
+ virtual Callable get_item_key_callback(const RID &p_rid, int p_idx) const override;
+ virtual Variant get_item_tag(const RID &p_rid, int p_idx) const override;
+ virtual String get_item_text(const RID &p_rid, int p_idx) const override;
+ virtual RID get_item_submenu(const RID &p_rid, int p_idx) const override;
+ virtual Key get_item_accelerator(const RID &p_rid, int p_idx) const override;
+ virtual bool is_item_disabled(const RID &p_rid, int p_idx) const override;
+ virtual bool is_item_hidden(const RID &p_rid, int p_idx) const override;
+ virtual String get_item_tooltip(const RID &p_rid, int p_idx) const override;
+ virtual int get_item_state(const RID &p_rid, int p_idx) const override;
+ virtual int get_item_max_states(const RID &p_rid, int p_idx) const override;
+ virtual Ref<Texture2D> get_item_icon(const RID &p_rid, int p_idx) const override;
+ virtual int get_item_indentation_level(const RID &p_rid, int p_idx) const override;
+
+ virtual void set_item_checked(const RID &p_rid, int p_idx, bool p_checked) override;
+ virtual void set_item_checkable(const RID &p_rid, int p_idx, bool p_checkable) override;
+ virtual void set_item_radio_checkable(const RID &p_rid, int p_idx, bool p_checkable) override;
+ virtual void set_item_callback(const RID &p_rid, int p_idx, const Callable &p_callback) override;
+ virtual void set_item_key_callback(const RID &p_rid, int p_idx, const Callable &p_key_callback) override;
+ virtual void set_item_hover_callbacks(const RID &p_rid, int p_idx, const Callable &p_callback) override;
+ virtual void set_item_tag(const RID &p_rid, int p_idx, const Variant &p_tag) override;
+ virtual void set_item_text(const RID &p_rid, int p_idx, const String &p_text) override;
+ virtual void set_item_submenu(const RID &p_rid, int p_idx, const RID &p_submenu_rid) override;
+ virtual void set_item_accelerator(const RID &p_rid, int p_idx, Key p_keycode) override;
+ virtual void set_item_disabled(const RID &p_rid, int p_idx, bool p_disabled) override;
+ virtual void set_item_hidden(const RID &p_rid, int p_idx, bool p_hidden) override;
+ virtual void set_item_tooltip(const RID &p_rid, int p_idx, const String &p_tooltip) override;
+ virtual void set_item_state(const RID &p_rid, int p_idx, int p_state) override;
+ virtual void set_item_max_states(const RID &p_rid, int p_idx, int p_max_states) override;
+ virtual void set_item_icon(const RID &p_rid, int p_idx, const Ref<Texture2D> &p_icon) override;
+ virtual void set_item_indentation_level(const RID &p_rid, int p_idx, int p_level) override;
+
+ virtual int get_item_count(const RID &p_rid) const override;
+ virtual bool is_system_menu(const RID &p_rid) const override;
+
+ virtual void remove_item(const RID &p_rid, int p_idx) override;
+ virtual void clear(const RID &p_rid) override;
+
+ NativeMenuWindows();
+ ~NativeMenuWindows();
+};
+
+#endif // NATIVE_MENU_WINDOWS_H
diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h
index 7e7d23a2a8..351cb32c9b 100644
--- a/platform/windows/os_windows.h
+++ b/platform/windows/os_windows.h
@@ -69,7 +69,7 @@
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x4
#endif
-template <class T>
+template <typename T>
class ComAutoreleaseRef {
public:
T *reference = nullptr;
diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp
index 722858b674..e9bab274c6 100644
--- a/scene/2d/camera_2d.cpp
+++ b/scene/2d/camera_2d.cpp
@@ -54,7 +54,12 @@ void Camera2D::_update_scroll() {
if (is_current()) {
ERR_FAIL_COND(custom_viewport && !ObjectDB::get_instance(custom_viewport_id));
- Transform2D xform = get_camera_transform();
+ Transform2D xform;
+ if (is_physics_interpolated_and_enabled()) {
+ xform = _interpolation_data.xform_prev.interpolate_with(_interpolation_data.xform_curr, Engine::get_singleton()->get_physics_interpolation_fraction());
+ } else {
+ xform = get_camera_transform();
+ }
viewport->set_canvas_transform(xform);
@@ -68,15 +73,26 @@ void Camera2D::_update_scroll() {
}
void Camera2D::_update_process_callback() {
- if (_is_editing_in_editor()) {
+ if (is_physics_interpolated_and_enabled()) {
+ set_process_internal(is_current());
+ set_physics_process_internal(is_current());
+
+#ifdef TOOLS_ENABLED
+ if (process_callback == CAMERA2D_PROCESS_IDLE) {
+ WARN_PRINT_ONCE("Camera2D overridden to physics process mode due to use of physics interpolation.");
+ }
+#endif
+ } else if (_is_editing_in_editor()) {
set_process_internal(false);
set_physics_process_internal(false);
- } else if (process_callback == CAMERA2D_PROCESS_IDLE) {
- set_process_internal(true);
- set_physics_process_internal(false);
} else {
- set_process_internal(false);
- set_physics_process_internal(true);
+ if (process_callback == CAMERA2D_PROCESS_IDLE) {
+ set_process_internal(true);
+ set_physics_process_internal(false);
+ } else {
+ set_process_internal(false);
+ set_physics_process_internal(true);
+ }
}
}
@@ -161,8 +177,15 @@ Transform2D Camera2D::get_camera_transform() {
}
}
+ // FIXME: There is a bug here, introduced before physics interpolation.
+ // Smoothing occurs rather confusingly during the call to get_camera_transform().
+ // It may be called MULTIPLE TIMES on certain frames,
+ // therefore smoothing is not currently applied only once per frame / tick,
+ // which will result in some haphazard results.
if (position_smoothing_enabled && !_is_editing_in_editor()) {
- real_t c = position_smoothing_speed * (process_callback == CAMERA2D_PROCESS_PHYSICS ? get_physics_process_delta_time() : get_process_delta_time());
+ bool physics_process = (process_callback == CAMERA2D_PROCESS_PHYSICS) || is_physics_interpolated_and_enabled();
+ real_t delta = physics_process ? get_physics_process_delta_time() : get_process_delta_time();
+ real_t c = position_smoothing_speed * delta;
smoothed_camera_pos = ((camera_pos - smoothed_camera_pos) * c) + smoothed_camera_pos;
ret_camera_pos = smoothed_camera_pos;
//camera_pos=camera_pos*(1.0-position_smoothing_speed)+new_camera_pos*position_smoothing_speed;
@@ -223,17 +246,52 @@ Transform2D Camera2D::get_camera_transform() {
return xform.affine_inverse();
}
+void Camera2D::_ensure_update_interpolation_data() {
+ // The "curr -> previous" update can either occur
+ // on NOTIFICATION_INTERNAL_PHYSICS_PROCESS, OR
+ // on NOTIFICATION_TRANSFORM_CHANGED,
+ // if NOTIFICATION_TRANSFORM_CHANGED takes place earlier than
+ // NOTIFICATION_INTERNAL_PHYSICS_PROCESS on a tick.
+ // This is to ensure that the data keeps flowing, but the new data
+ // doesn't overwrite before prev has been set.
+
+ // Keep the data flowing.
+ uint64_t tick = Engine::get_singleton()->get_physics_frames();
+ if (_interpolation_data.last_update_physics_tick != tick) {
+ _interpolation_data.xform_prev = _interpolation_data.xform_curr;
+ _interpolation_data.last_update_physics_tick = tick;
+ }
+}
+
void Camera2D::_notification(int p_what) {
switch (p_what) {
- case NOTIFICATION_INTERNAL_PROCESS:
- case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
+ case NOTIFICATION_INTERNAL_PROCESS: {
_update_scroll();
} break;
+ case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
+ if (is_physics_interpolated_and_enabled()) {
+ _ensure_update_interpolation_data();
+ _interpolation_data.xform_curr = get_camera_transform();
+ } else {
+ _update_scroll();
+ }
+ } break;
+
+ case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+ // Force the limits etc. to update.
+ _interpolation_data.xform_curr = get_camera_transform();
+ _interpolation_data.xform_prev = _interpolation_data.xform_curr;
+ } break;
+
case NOTIFICATION_TRANSFORM_CHANGED: {
- if (!position_smoothing_enabled || _is_editing_in_editor()) {
+ if ((!position_smoothing_enabled && !is_physics_interpolated_and_enabled()) || _is_editing_in_editor()) {
_update_scroll();
}
+ if (is_physics_interpolated_and_enabled()) {
+ _ensure_update_interpolation_data();
+ _interpolation_data.xform_curr = get_camera_transform();
+ }
} break;
case NOTIFICATION_ENTER_TREE: {
@@ -260,6 +318,15 @@ void Camera2D::_notification(int p_what) {
_update_process_callback();
first = true;
_update_scroll();
+
+ // Note that NOTIFICATION_RESET_PHYSICS_INTERPOLATION
+ // is automatically called before this because Camera2D is inherited
+ // from CanvasItem. However, the camera transform is not up to date
+ // until this point, so we do an extra manual reset.
+ if (is_physics_interpolated_and_enabled()) {
+ _interpolation_data.xform_curr = get_camera_transform();
+ _interpolation_data.xform_prev = _interpolation_data.xform_curr;
+ }
} break;
case NOTIFICATION_EXIT_TREE: {
@@ -431,12 +498,17 @@ void Camera2D::_make_current(Object *p_which) {
queue_redraw();
- if (p_which == this) {
+ bool was_current = viewport->get_camera_2d() == this;
+ bool is_current = p_which == this;
+
+ if (is_current) {
viewport->_camera_2d_set(this);
- } else {
- if (viewport->get_camera_2d() == this) {
- viewport->_camera_2d_set(nullptr);
- }
+ } else if (was_current) {
+ viewport->_camera_2d_set(nullptr);
+ }
+
+ if (is_current != was_current) {
+ _update_process_callback();
}
}
@@ -456,6 +528,7 @@ void Camera2D::make_current() {
_make_current(this);
}
_update_scroll();
+ _update_process_callback();
}
void Camera2D::clear_current() {
@@ -468,6 +541,8 @@ void Camera2D::clear_current() {
if (!custom_viewport || ObjectDB::get_instance(custom_viewport_id)) {
viewport->assign_next_enabled_camera_2d(group_name);
}
+
+ _update_process_callback();
}
bool Camera2D::is_current() const {
diff --git a/scene/2d/camera_2d.h b/scene/2d/camera_2d.h
index 5693d05ee5..bf25267aa8 100644
--- a/scene/2d/camera_2d.h
+++ b/scene/2d/camera_2d.h
@@ -102,6 +102,14 @@ protected:
Camera2DProcessCallback process_callback = CAMERA2D_PROCESS_IDLE;
+ struct InterpolationData {
+ Transform2D xform_curr;
+ Transform2D xform_prev;
+ uint32_t last_update_physics_tick = 0;
+ } _interpolation_data;
+
+ void _ensure_update_interpolation_data();
+
Size2 _get_camera_screen_size() const;
protected:
diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp
index c03786caef..79732d7861 100644
--- a/scene/2d/light_2d.cpp
+++ b/scene/2d/light_2d.cpp
@@ -197,6 +197,10 @@ Light2D::BlendMode Light2D::get_blend_mode() const {
return blend_mode;
}
+void Light2D::_physics_interpolated_changed() {
+ RenderingServer::get_singleton()->canvas_light_set_interpolated(canvas_light, is_physics_interpolated());
+}
+
void Light2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
@@ -212,6 +216,17 @@ void Light2D::_notification(int p_what) {
_update_light_visibility();
} break;
+ case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+ if (is_visible_in_tree() && is_physics_interpolated()) {
+ // Explicitly make sure the transform is up to date in RenderingServer before
+ // resetting. This is necessary because NOTIFICATION_TRANSFORM_CHANGED
+ // is normally deferred, and a client change to transform will not always be sent
+ // before the reset, so we need to guarantee this.
+ RS::get_singleton()->canvas_light_set_transform(canvas_light, get_global_transform());
+ RS::get_singleton()->canvas_light_reset_physics_interpolation(canvas_light);
+ }
+ } break;
+
case NOTIFICATION_EXIT_TREE: {
RS::get_singleton()->canvas_light_attach_to_canvas(canvas_light, RID());
_update_light_visibility();
diff --git a/scene/2d/light_2d.h b/scene/2d/light_2d.h
index 3c1171deae..8a0c2a2a92 100644
--- a/scene/2d/light_2d.h
+++ b/scene/2d/light_2d.h
@@ -74,6 +74,7 @@ private:
void _update_light_visibility();
virtual void owner_changed_notify() override;
+ virtual void _physics_interpolated_changed() override;
protected:
_FORCE_INLINE_ RID _get_light() const { return canvas_light; }
diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp
index 61f5d5cd46..092c987ac0 100644
--- a/scene/2d/light_occluder_2d.cpp
+++ b/scene/2d/light_occluder_2d.cpp
@@ -158,6 +158,10 @@ void LightOccluder2D::_poly_changed() {
#endif
}
+void LightOccluder2D::_physics_interpolated_changed() {
+ RenderingServer::get_singleton()->canvas_light_occluder_set_interpolated(occluder, is_physics_interpolated());
+}
+
void LightOccluder2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_CANVAS: {
@@ -199,6 +203,17 @@ void LightOccluder2D::_notification(int p_what) {
case NOTIFICATION_EXIT_CANVAS: {
RS::get_singleton()->canvas_light_occluder_attach_to_canvas(occluder, RID());
} break;
+
+ case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+ if (is_visible_in_tree() && is_physics_interpolated()) {
+ // Explicitly make sure the transform is up to date in RenderingServer before
+ // resetting. This is necessary because NOTIFICATION_TRANSFORM_CHANGED
+ // is normally deferred, and a client change to transform will not always be sent
+ // before the reset, so we need to guarantee this.
+ RS::get_singleton()->canvas_light_occluder_set_transform(occluder, get_global_transform());
+ RS::get_singleton()->canvas_light_occluder_reset_physics_interpolation(occluder);
+ }
+ } break;
}
}
diff --git a/scene/2d/light_occluder_2d.h b/scene/2d/light_occluder_2d.h
index dd3130394e..4c499d0465 100644
--- a/scene/2d/light_occluder_2d.h
+++ b/scene/2d/light_occluder_2d.h
@@ -86,6 +86,8 @@ class LightOccluder2D : public Node2D {
bool sdf_collision = false;
void _poly_changed();
+ virtual void _physics_interpolated_changed() override;
+
protected:
void _notification(int p_what);
static void _bind_methods();
diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp
index 07a3910720..3bf90249f8 100644
--- a/scene/2d/navigation_obstacle_2d.cpp
+++ b/scene/2d/navigation_obstacle_2d.cpp
@@ -55,14 +55,24 @@ void NavigationObstacle2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_avoidance_layers", "layers"), &NavigationObstacle2D::set_avoidance_layers);
ClassDB::bind_method(D_METHOD("get_avoidance_layers"), &NavigationObstacle2D::get_avoidance_layers);
+
ClassDB::bind_method(D_METHOD("set_avoidance_layer_value", "layer_number", "value"), &NavigationObstacle2D::set_avoidance_layer_value);
ClassDB::bind_method(D_METHOD("get_avoidance_layer_value", "layer_number"), &NavigationObstacle2D::get_avoidance_layer_value);
+ ClassDB::bind_method(D_METHOD("set_affect_navigation_mesh", "enabled"), &NavigationObstacle2D::set_affect_navigation_mesh);
+ ClassDB::bind_method(D_METHOD("get_affect_navigation_mesh"), &NavigationObstacle2D::get_affect_navigation_mesh);
+
+ ClassDB::bind_method(D_METHOD("set_carve_navigation_mesh", "enabled"), &NavigationObstacle2D::set_carve_navigation_mesh);
+ ClassDB::bind_method(D_METHOD("get_carve_navigation_mesh"), &NavigationObstacle2D::get_carve_navigation_mesh);
+
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.0,500,0.01,suffix:px"), "set_radius", "get_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "vertices"), "set_vertices", "get_vertices");
+ ADD_GROUP("NavigationMesh", "");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "affect_navigation_mesh"), "set_affect_navigation_mesh", "get_affect_navigation_mesh");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "carve_navigation_mesh"), "set_carve_navigation_mesh", "get_carve_navigation_mesh");
ADD_GROUP("Avoidance", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.0,500,0.01,suffix:px"), "set_radius", "get_radius");
- ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "vertices"), "set_vertices", "get_vertices");
ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers");
}
@@ -277,6 +287,22 @@ void NavigationObstacle2D::set_velocity(const Vector2 p_velocity) {
velocity_submitted = true;
}
+void NavigationObstacle2D::set_affect_navigation_mesh(bool p_enabled) {
+ affect_navigation_mesh = p_enabled;
+}
+
+bool NavigationObstacle2D::get_affect_navigation_mesh() const {
+ return affect_navigation_mesh;
+}
+
+void NavigationObstacle2D::set_carve_navigation_mesh(bool p_enabled) {
+ carve_navigation_mesh = p_enabled;
+}
+
+bool NavigationObstacle2D::get_carve_navigation_mesh() const {
+ return carve_navigation_mesh;
+}
+
void NavigationObstacle2D::_update_map(RID p_map) {
map_current = p_map;
NavigationServer2D::get_singleton()->obstacle_set_map(obstacle, p_map);
diff --git a/scene/2d/navigation_obstacle_2d.h b/scene/2d/navigation_obstacle_2d.h
index f9d0e27714..30328f7086 100644
--- a/scene/2d/navigation_obstacle_2d.h
+++ b/scene/2d/navigation_obstacle_2d.h
@@ -54,6 +54,9 @@ class NavigationObstacle2D : public Node2D {
Vector2 previous_velocity;
bool velocity_submitted = false;
+ bool affect_navigation_mesh = false;
+ bool carve_navigation_mesh = false;
+
#ifdef DEBUG_ENABLED
private:
RID debug_canvas_item;
@@ -97,6 +100,12 @@ public:
void _avoidance_done(Vector3 p_new_velocity); // Dummy
+ void set_affect_navigation_mesh(bool p_enabled);
+ bool get_affect_navigation_mesh() const;
+
+ void set_carve_navigation_mesh(bool p_enabled);
+ bool get_carve_navigation_mesh() const;
+
private:
void _update_map(RID p_map);
void _update_position(const Vector2 p_position);
diff --git a/scene/2d/parallax_2d.cpp b/scene/2d/parallax_2d.cpp
index f516fd41ac..7e47e0f061 100644
--- a/scene/2d/parallax_2d.cpp
+++ b/scene/2d/parallax_2d.cpp
@@ -129,6 +129,7 @@ void Parallax2D::_update_repeat() {
Point2 repeat_scale = repeat_size * get_scale();
RenderingServer::get_singleton()->canvas_set_item_repeat(get_canvas_item(), repeat_scale, repeat_times);
+ RenderingServer::get_singleton()->canvas_item_set_interpolated(get_canvas_item(), false);
}
void Parallax2D::set_scroll_scale(const Size2 &p_scale) {
@@ -287,4 +288,6 @@ void Parallax2D::_bind_methods() {
}
Parallax2D::Parallax2D() {
+ // Parallax2D is always updated every frame so there is no need to interpolate.
+ set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF);
}
diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp
index 3dd0d7b61c..e2a7e9e154 100644
--- a/scene/2d/parallax_layer.cpp
+++ b/scene/2d/parallax_layer.cpp
@@ -73,6 +73,7 @@ void ParallaxLayer::_update_mirroring() {
RID ci = get_canvas_item();
Point2 mirrorScale = mirroring * get_scale();
RenderingServer::get_singleton()->canvas_set_item_mirroring(c, ci, mirrorScale);
+ RenderingServer::get_singleton()->canvas_item_set_interpolated(ci, false);
}
}
@@ -162,4 +163,6 @@ void ParallaxLayer::_bind_methods() {
}
ParallaxLayer::ParallaxLayer() {
+ // ParallaxLayer is always updated every frame so there is no need to interpolate.
+ set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF);
}
diff --git a/scene/2d/physics/rigid_body_2d.cpp b/scene/2d/physics/rigid_body_2d.cpp
index 12112510a8..5e05c563a4 100644
--- a/scene/2d/physics/rigid_body_2d.cpp
+++ b/scene/2d/physics/rigid_body_2d.cpp
@@ -356,6 +356,8 @@ void RigidBody2D::set_center_of_mass_mode(CenterOfMassMode p_mode) {
PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_CENTER_OF_MASS, center_of_mass);
} break;
}
+
+ notify_property_list_changed();
}
RigidBody2D::CenterOfMassMode RigidBody2D::get_center_of_mass_mode() const {
@@ -614,6 +616,8 @@ void RigidBody2D::set_contact_monitor(bool p_enabled) {
contact_monitor = memnew(ContactMonitor);
contact_monitor->locked = false;
}
+
+ notify_property_list_changed();
}
bool RigidBody2D::is_contact_monitor_enabled() const {
@@ -736,13 +740,12 @@ void RigidBody2D::_bind_methods() {
GDVIRTUAL_BIND(_integrate_forces, "state");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,1000,0.01,or_greater,exp,suffix:kg"), "set_mass", "get_mass");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.001,1000,0.001,or_greater,exp,suffix:kg"), "set_mass", "get_mass");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_scale", PROPERTY_HINT_RANGE, "-8,8,0.001,or_less,or_greater"), "set_gravity_scale", "get_gravity_scale");
ADD_GROUP("Mass Distribution", "");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "center_of_mass_mode", PROPERTY_HINT_ENUM, "Auto,Custom", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_center_of_mass_mode", "get_center_of_mass_mode");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "center_of_mass", PROPERTY_HINT_RANGE, "-10,10,0.01,or_less,or_greater,suffix:px"), "set_center_of_mass", "get_center_of_mass");
- ADD_LINKED_PROPERTY("center_of_mass_mode", "center_of_mass");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "center_of_mass_mode", PROPERTY_HINT_ENUM, "Auto,Custom"), "set_center_of_mass_mode", "get_center_of_mass_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "center_of_mass", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_less,or_greater,suffix:px"), "set_center_of_mass", "get_center_of_mass");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inertia", PROPERTY_HINT_RANGE, U"0,1000,0.01,or_greater,exp,suffix:kg\u22C5px\u00B2"), "set_inertia", "get_inertia");
ADD_GROUP("Deactivation", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sleeping"), "set_sleeping", "is_sleeping");
@@ -753,8 +756,8 @@ void RigidBody2D::_bind_methods() {
ADD_GROUP("Solver", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "custom_integrator"), "set_use_custom_integrator", "is_using_custom_integrator");
ADD_PROPERTY(PropertyInfo(Variant::INT, "continuous_cd", PROPERTY_HINT_ENUM, "Disabled,Cast Ray,Cast Shape"), "set_continuous_collision_detection_mode", "get_continuous_collision_detection_mode");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "max_contacts_reported", PROPERTY_HINT_RANGE, "0,64,1,or_greater"), "set_max_contacts_reported", "get_max_contacts_reported");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "contact_monitor"), "set_contact_monitor", "is_contact_monitor_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "max_contacts_reported", PROPERTY_HINT_RANGE, "0,64,1,or_greater"), "set_max_contacts_reported", "get_max_contacts_reported");
ADD_GROUP("Linear", "linear_");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "linear_velocity", PROPERTY_HINT_NONE, "suffix:px/s"), "set_linear_velocity", "get_linear_velocity");
ADD_PROPERTY(PropertyInfo(Variant::INT, "linear_damp_mode", PROPERTY_HINT_ENUM, "Combine,Replace"), "set_linear_damp_mode", "get_linear_damp_mode");
@@ -788,10 +791,12 @@ void RigidBody2D::_bind_methods() {
}
void RigidBody2D::_validate_property(PropertyInfo &p_property) const {
- if (center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM) {
- if (p_property.name == "center_of_mass") {
- p_property.usage = PROPERTY_USAGE_NO_EDITOR;
- }
+ if (center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM && p_property.name == "center_of_mass") {
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+ }
+
+ if (!contact_monitor && p_property.name == "max_contacts_reported") {
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
diff --git a/scene/2d/sprite_2d.cpp b/scene/2d/sprite_2d.cpp
index 3c19dd0020..5745a59297 100644
--- a/scene/2d/sprite_2d.cpp
+++ b/scene/2d/sprite_2d.cpp
@@ -374,8 +374,7 @@ bool Sprite2D::is_pixel_opaque(const Point2 &p_point) const {
q.y = texture->get_size().height - q.y - 1;
}
} else {
- q.x = MIN(q.x, texture->get_size().width - 1);
- q.y = MIN(q.y, texture->get_size().height - 1);
+ q = q.min(texture->get_size() - Vector2(1, 1));
}
return texture->is_pixel_opaque((int)q.x, (int)q.y);
diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp
index c7aa4c2be4..84c4c43a5a 100644
--- a/scene/2d/tile_map_layer.cpp
+++ b/scene/2d/tile_map_layer.cpp
@@ -356,6 +356,14 @@ void TileMapLayer::_rendering_update() {
// Drawing the tile in the canvas item.
TileMap::draw_tile(ci, local_tile_pos - rendering_quadrant->canvas_items_position, tile_set, cell_data.cell.source_id, cell_data.cell.get_atlas_coords(), cell_data.cell.alternative_tile, -1, get_self_modulate(), tile_data, random_animation_offset);
}
+
+ // Reset physics interpolation for any recreated canvas items.
+ if (is_physics_interpolated_and_enabled() && is_visible_in_tree()) {
+ for (const RID &ci : rendering_quadrant->canvas_items) {
+ rs->canvas_item_reset_physics_interpolation(ci);
+ }
+ }
+
} else {
// Free the quadrant.
for (int i = 0; i < rendering_quadrant->canvas_items.size(); i++) {
@@ -453,6 +461,15 @@ void TileMapLayer::_rendering_notification(int p_what) {
}
}
}
+ } else if (p_what == NOTIFICATION_RESET_PHYSICS_INTERPOLATION) {
+ for (const KeyValue<Vector2i, Ref<RenderingQuadrant>> &kv : rendering_quadrant_map) {
+ for (const RID &ci : kv.value->canvas_items) {
+ if (ci.is_null()) {
+ continue;
+ }
+ rs->canvas_item_reset_physics_interpolation(ci);
+ }
+ }
}
}
diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp
index 6878df21d8..caabb4225f 100644
--- a/scene/3d/decal.cpp
+++ b/scene/3d/decal.cpp
@@ -31,7 +31,7 @@
#include "decal.h"
void Decal::set_size(const Vector3 &p_size) {
- size = Vector3(MAX(0.001, p_size.x), MAX(0.001, p_size.y), MAX(0.001, p_size.z));
+ size = p_size.max(Vector3(0.001, 0.001, 0.001));
RS::get_singleton()->decal_set_size(decal, size);
update_gizmos();
}
diff --git a/scene/3d/fog_volume.cpp b/scene/3d/fog_volume.cpp
index 12ca1888c4..8af386f282 100644
--- a/scene/3d/fog_volume.cpp
+++ b/scene/3d/fog_volume.cpp
@@ -73,9 +73,7 @@ bool FogVolume::_get(const StringName &p_name, Variant &r_property) const {
void FogVolume::set_size(const Vector3 &p_size) {
size = p_size;
- size.x = MAX(0.0, size.x);
- size.y = MAX(0.0, size.y);
- size.z = MAX(0.0, size.z);
+ size = size.max(Vector3());
RS::get_singleton()->fog_volume_set_size(_get_volume(), size);
update_gizmos();
}
diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp
index cbc75801b0..8fd5f25749 100644
--- a/scene/3d/gpu_particles_collision_3d.cpp
+++ b/scene/3d/gpu_particles_collision_3d.cpp
@@ -330,7 +330,7 @@ void GPUParticlesCollisionSDF3D::_find_closest_distance(const Vector3 &p_pos, co
Vector3 center = p_bvh[p_bvh_cell].bounds.position + he;
Vector3 rel = (p_pos - center).abs();
- Vector3 closest(MIN(rel.x, he.x), MIN(rel.y, he.y), MIN(rel.z, he.z));
+ Vector3 closest = rel.min(he);
float d = rel.distance_to(closest);
if (d >= r_closest_distance) {
@@ -382,9 +382,7 @@ Vector3i GPUParticlesCollisionSDF3D::get_estimated_cell_size() const {
float cell_size = aabb.get_longest_axis_size() / float(subdiv);
Vector3i sdf_size = Vector3i(aabb.size / cell_size);
- sdf_size.x = MAX(1, sdf_size.x);
- sdf_size.y = MAX(1, sdf_size.y);
- sdf_size.z = MAX(1, sdf_size.z);
+ sdf_size = sdf_size.max(Vector3i(1, 1, 1));
return sdf_size;
}
@@ -397,9 +395,7 @@ Ref<Image> GPUParticlesCollisionSDF3D::bake() {
float cell_size = aabb.get_longest_axis_size() / float(subdiv);
Vector3i sdf_size = Vector3i(aabb.size / cell_size);
- sdf_size.x = MAX(1, sdf_size.x);
- sdf_size.y = MAX(1, sdf_size.y);
- sdf_size.z = MAX(1, sdf_size.z);
+ sdf_size = sdf_size.max(Vector3i(1, 1, 1));
if (bake_begin_function) {
bake_begin_function(100);
diff --git a/scene/3d/navigation_obstacle_3d.cpp b/scene/3d/navigation_obstacle_3d.cpp
index 96f7fb137c..f2ac8f789c 100644
--- a/scene/3d/navigation_obstacle_3d.cpp
+++ b/scene/3d/navigation_obstacle_3d.cpp
@@ -63,12 +63,21 @@ void NavigationObstacle3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_use_3d_avoidance", "enabled"), &NavigationObstacle3D::set_use_3d_avoidance);
ClassDB::bind_method(D_METHOD("get_use_3d_avoidance"), &NavigationObstacle3D::get_use_3d_avoidance);
- ADD_GROUP("Avoidance", "");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity");
+ ClassDB::bind_method(D_METHOD("set_affect_navigation_mesh", "enabled"), &NavigationObstacle3D::set_affect_navigation_mesh);
+ ClassDB::bind_method(D_METHOD("get_affect_navigation_mesh"), &NavigationObstacle3D::get_affect_navigation_mesh);
+
+ ClassDB::bind_method(D_METHOD("set_carve_navigation_mesh", "enabled"), &NavigationObstacle3D::set_carve_navigation_mesh);
+ ClassDB::bind_method(D_METHOD("get_carve_navigation_mesh"), &NavigationObstacle3D::get_carve_navigation_mesh);
+
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.0,100,0.01,suffix:m"), "set_radius", "get_radius");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.0,100,0.01,suffix:m"), "set_height", "get_height");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices"), "set_vertices", "get_vertices");
+ ADD_GROUP("NavigationMesh", "");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "affect_navigation_mesh"), "set_affect_navigation_mesh", "get_affect_navigation_mesh");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "carve_navigation_mesh"), "set_carve_navigation_mesh", "get_carve_navigation_mesh");
+ ADD_GROUP("Avoidance", "");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity");
ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_3d_avoidance"), "set_use_3d_avoidance", "get_use_3d_avoidance");
}
@@ -321,6 +330,22 @@ void NavigationObstacle3D::set_velocity(const Vector3 p_velocity) {
velocity_submitted = true;
}
+void NavigationObstacle3D::set_affect_navigation_mesh(bool p_enabled) {
+ affect_navigation_mesh = p_enabled;
+}
+
+bool NavigationObstacle3D::get_affect_navigation_mesh() const {
+ return affect_navigation_mesh;
+}
+
+void NavigationObstacle3D::set_carve_navigation_mesh(bool p_enabled) {
+ carve_navigation_mesh = p_enabled;
+}
+
+bool NavigationObstacle3D::get_carve_navigation_mesh() const {
+ return carve_navigation_mesh;
+}
+
void NavigationObstacle3D::_update_map(RID p_map) {
NavigationServer3D::get_singleton()->obstacle_set_map(obstacle, p_map);
map_current = p_map;
diff --git a/scene/3d/navigation_obstacle_3d.h b/scene/3d/navigation_obstacle_3d.h
index 51a84c9a54..e9a4669fa2 100644
--- a/scene/3d/navigation_obstacle_3d.h
+++ b/scene/3d/navigation_obstacle_3d.h
@@ -57,6 +57,9 @@ class NavigationObstacle3D : public Node3D {
Vector3 previous_velocity;
bool velocity_submitted = false;
+ bool affect_navigation_mesh = false;
+ bool carve_navigation_mesh = false;
+
#ifdef DEBUG_ENABLED
RID fake_agent_radius_debug_instance;
Ref<ArrayMesh> fake_agent_radius_debug_mesh;
@@ -108,6 +111,12 @@ public:
void _avoidance_done(Vector3 p_new_velocity); // Dummy
+ void set_affect_navigation_mesh(bool p_enabled);
+ bool get_affect_navigation_mesh() const;
+
+ void set_carve_navigation_mesh(bool p_enabled);
+ bool get_carve_navigation_mesh() const;
+
private:
void _update_map(RID p_map);
void _update_position(const Vector3 p_position);
diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp
index 8d995a8785..2f77185d0d 100644
--- a/scene/3d/occluder_instance_3d.cpp
+++ b/scene/3d/occluder_instance_3d.cpp
@@ -236,7 +236,7 @@ void BoxOccluder3D::set_size(const Vector3 &p_size) {
return;
}
- size = Vector3(MAX(p_size.x, 0.0f), MAX(p_size.y, 0.0f), MAX(p_size.z, 0.0f));
+ size = p_size.max(Vector3());
_update();
}
diff --git a/scene/3d/physics/rigid_body_3d.cpp b/scene/3d/physics/rigid_body_3d.cpp
index 54bd1c0d25..6cd621c1c7 100644
--- a/scene/3d/physics/rigid_body_3d.cpp
+++ b/scene/3d/physics/rigid_body_3d.cpp
@@ -375,6 +375,8 @@ void RigidBody3D::set_center_of_mass_mode(CenterOfMassMode p_mode) {
PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_CENTER_OF_MASS, center_of_mass);
} break;
}
+
+ notify_property_list_changed();
}
RigidBody3D::CenterOfMassMode RigidBody3D::get_center_of_mass_mode() const {
@@ -622,6 +624,8 @@ void RigidBody3D::set_contact_monitor(bool p_enabled) {
contact_monitor = memnew(ContactMonitor);
contact_monitor->locked = false;
}
+
+ notify_property_list_changed();
}
bool RigidBody3D::is_contact_monitor_enabled() const {
@@ -758,13 +762,12 @@ void RigidBody3D::_bind_methods() {
GDVIRTUAL_BIND(_integrate_forces, "state");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,1000,0.01,or_greater,exp,suffix:kg"), "set_mass", "get_mass");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.001,1000,0.001,or_greater,exp,suffix:kg"), "set_mass", "get_mass");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_scale", PROPERTY_HINT_RANGE, "-8,8,0.001,or_less,or_greater"), "set_gravity_scale", "get_gravity_scale");
ADD_GROUP("Mass Distribution", "");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "center_of_mass_mode", PROPERTY_HINT_ENUM, "Auto,Custom", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_center_of_mass_mode", "get_center_of_mass_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "center_of_mass_mode", PROPERTY_HINT_ENUM, "Auto,Custom"), "set_center_of_mass_mode", "get_center_of_mass_mode");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_of_mass", PROPERTY_HINT_RANGE, "-10,10,0.01,or_less,or_greater,suffix:m"), "set_center_of_mass", "get_center_of_mass");
- ADD_LINKED_PROPERTY("center_of_mass_mode", "center_of_mass");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "inertia", PROPERTY_HINT_RANGE, U"0,1000,0.01,or_greater,exp,suffix:kg\u22C5m\u00B2"), "set_inertia", "get_inertia");
ADD_GROUP("Deactivation", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sleeping"), "set_sleeping", "is_sleeping");
@@ -775,8 +778,8 @@ void RigidBody3D::_bind_methods() {
ADD_GROUP("Solver", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "custom_integrator"), "set_use_custom_integrator", "is_using_custom_integrator");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "continuous_cd"), "set_use_continuous_collision_detection", "is_using_continuous_collision_detection");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "max_contacts_reported", PROPERTY_HINT_RANGE, "0,64,1,or_greater"), "set_max_contacts_reported", "get_max_contacts_reported");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "contact_monitor"), "set_contact_monitor", "is_contact_monitor_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "max_contacts_reported", PROPERTY_HINT_RANGE, "0,64,1,or_greater"), "set_max_contacts_reported", "get_max_contacts_reported");
ADD_GROUP("Linear", "linear_");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity", PROPERTY_HINT_NONE, "suffix:m/s"), "set_linear_velocity", "get_linear_velocity");
ADD_PROPERTY(PropertyInfo(Variant::INT, "linear_damp_mode", PROPERTY_HINT_ENUM, "Combine,Replace"), "set_linear_damp_mode", "get_linear_damp_mode");
@@ -806,10 +809,12 @@ void RigidBody3D::_bind_methods() {
}
void RigidBody3D::_validate_property(PropertyInfo &p_property) const {
- if (center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM) {
- if (p_property.name == "center_of_mass") {
- p_property.usage = PROPERTY_USAGE_NO_EDITOR;
- }
+ if (center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM && p_property.name == "center_of_mass") {
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+ }
+
+ if (!contact_monitor && p_property.name == "max_contacts_reported") {
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
diff --git a/scene/3d/voxel_gi.cpp b/scene/3d/voxel_gi.cpp
index eb8569fa30..938d6e5699 100644
--- a/scene/3d/voxel_gi.cpp
+++ b/scene/3d/voxel_gi.cpp
@@ -294,7 +294,7 @@ VoxelGI::Subdiv VoxelGI::get_subdiv() const {
void VoxelGI::set_size(const Vector3 &p_size) {
// Prevent very small size dimensions as these breaks baking if other size dimensions are set very high.
- size = Vector3(MAX(1.0, p_size.x), MAX(1.0, p_size.y), MAX(1.0, p_size.z));
+ size = p_size.max(Vector3(1.0, 1.0, 1.0));
update_gizmos();
}
diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp
index 4cbd9b1d76..36343edd11 100644
--- a/scene/animation/animation_blend_space_1d.cpp
+++ b/scene/animation/animation_blend_space_1d.cpp
@@ -33,12 +33,17 @@
#include "animation_blend_tree.h"
void AnimationNodeBlendSpace1D::get_parameter_list(List<PropertyInfo> *r_list) const {
+ AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, blend_position));
r_list->push_back(PropertyInfo(Variant::INT, closest, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
- r_list->push_back(PropertyInfo(Variant::FLOAT, length_internal, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
}
Variant AnimationNodeBlendSpace1D::get_parameter_default_value(const StringName &p_parameter) const {
+ Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
+ if (ret != Variant()) {
+ return ret;
+ }
+
if (p_parameter == closest) {
return -1;
} else {
@@ -272,9 +277,9 @@ void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Ref<Animatio
}
}
-double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
if (blend_points_used == 0) {
- return 0.0;
+ return NodeTimeInfo();
}
AnimationMixer::PlaybackInfo pi = p_playback_info;
@@ -287,8 +292,7 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_
double blend_pos = get_parameter(blend_position);
int cur_closest = get_parameter(closest);
- double cur_length_internal = get_parameter(length_internal);
- double max_time_remaining = 0.0;
+ NodeTimeInfo mind;
if (blend_mode == BLEND_MODE_INTERPOLATED) {
int point_lower = -1;
@@ -341,12 +345,17 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_
}
// actually blend the animations now
-
+ bool first = true;
+ double max_weight = 0.0;
for (int i = 0; i < blend_points_used; i++) {
if (i == point_lower || i == point_higher) {
pi.weight = weights[i];
- double remaining = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
- max_time_remaining = MAX(max_time_remaining, remaining);
+ NodeTimeInfo t = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
+ if (first || pi.weight > max_weight) {
+ max_weight = pi.weight;
+ mind = t;
+ first = false;
+ }
} else if (sync) {
pi.weight = 0;
blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
@@ -365,7 +374,7 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_
}
if (new_closest != cur_closest && new_closest != -1) {
- double from = 0.0;
+ NodeTimeInfo from;
if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) {
//for ping-pong loop
Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node);
@@ -376,18 +385,17 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_
//see how much animation remains
pi.seeked = false;
pi.weight = 0;
- from = cur_length_internal - blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
+ from = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
}
- pi.time = from;
+ pi.time = from.position;
pi.seeked = true;
pi.weight = 1.0;
- max_time_remaining = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only);
- cur_length_internal = from + max_time_remaining;
+ mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only);
cur_closest = new_closest;
} else {
pi.weight = 1.0;
- max_time_remaining = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
+ mind = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
}
if (sync) {
@@ -402,8 +410,7 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_
}
set_parameter(closest, cur_closest);
- set_parameter(length_internal, cur_length_internal);
- return max_time_remaining;
+ return mind;
}
String AnimationNodeBlendSpace1D::get_caption() const {
diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h
index 40679d55ef..64ae4d0505 100644
--- a/scene/animation/animation_blend_space_1d.h
+++ b/scene/animation/animation_blend_space_1d.h
@@ -68,7 +68,6 @@ protected:
StringName blend_position = "blend_position";
StringName closest = "closest";
- StringName length_internal = "length_internal";
BlendMode blend_mode = BLEND_MODE_INTERPOLATED;
@@ -114,7 +113,7 @@ public:
void set_use_sync(bool p_sync);
bool is_using_sync() const;
- virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
+ virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
String get_caption() const override;
Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override;
diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp
index d5c6253e9d..2634248231 100644
--- a/scene/animation/animation_blend_space_2d.cpp
+++ b/scene/animation/animation_blend_space_2d.cpp
@@ -34,16 +34,19 @@
#include "core/math/geometry_2d.h"
void AnimationNodeBlendSpace2D::get_parameter_list(List<PropertyInfo> *r_list) const {
+ AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::VECTOR2, blend_position));
r_list->push_back(PropertyInfo(Variant::INT, closest, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
- r_list->push_back(PropertyInfo(Variant::FLOAT, length_internal, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
}
Variant AnimationNodeBlendSpace2D::get_parameter_default_value(const StringName &p_parameter) const {
+ Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
+ if (ret != Variant()) {
+ return ret;
+ }
+
if (p_parameter == closest) {
return -1;
- } else if (p_parameter == length_internal) {
- return 0;
} else {
return Vector2();
}
@@ -442,19 +445,18 @@ void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vect
r_weights[2] = w;
}
-double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
_update_triangles();
Vector2 blend_pos = get_parameter(blend_position);
int cur_closest = get_parameter(closest);
- double cur_length_internal = get_parameter(length_internal);
- double mind = 0.0; //time of min distance point
+ NodeTimeInfo mind; //time of min distance point
AnimationMixer::PlaybackInfo pi = p_playback_info;
if (blend_mode == BLEND_MODE_INTERPOLATED) {
if (triangles.size() == 0) {
- return 0;
+ return NodeTimeInfo();
}
Vector2 best_point;
@@ -500,7 +502,7 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_
}
}
- ERR_FAIL_COND_V(blend_triangle == -1, 0); //should never reach here
+ ERR_FAIL_COND_V(blend_triangle == -1, NodeTimeInfo()); //should never reach here
int triangle_points[3];
for (int j = 0; j < 3; j++) {
@@ -509,15 +511,17 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_
first = true;
+ bool found = false;
+ double max_weight = 0.0;
for (int i = 0; i < blend_points_used; i++) {
- bool found = false;
for (int j = 0; j < 3; j++) {
if (i == triangle_points[j]) {
//blend with the given weight
pi.weight = blend_weights[j];
- double t = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
- if (first || t < mind) {
+ NodeTimeInfo t = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
+ if (first || pi.weight > max_weight) {
mind = t;
+ max_weight = pi.weight;
first = false;
}
found = true;
@@ -543,7 +547,7 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_
}
if (new_closest != cur_closest && new_closest != -1) {
- double from = 0.0;
+ NodeTimeInfo from;
if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) {
//for ping-pong loop
Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node);
@@ -554,14 +558,13 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_
//see how much animation remains
pi.seeked = false;
pi.weight = 0;
- from = cur_length_internal - blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
+ from = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
}
- pi.time = from;
+ pi.time = from.position;
pi.seeked = true;
pi.weight = 1.0;
mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only);
- cur_length_internal = from + mind;
cur_closest = new_closest;
} else {
pi.weight = 1.0;
@@ -580,7 +583,6 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_
}
set_parameter(closest, cur_closest);
- set_parameter(length_internal, cur_length_internal);
return mind;
}
diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h
index 33a821d80c..c26ff2bce0 100644
--- a/scene/animation/animation_blend_space_2d.h
+++ b/scene/animation/animation_blend_space_2d.h
@@ -65,7 +65,6 @@ protected:
StringName blend_position = "blend_position";
StringName closest = "closest";
- StringName length_internal = "length_internal";
Vector2 max_space = Vector2(1, 1);
Vector2 min_space = Vector2(-1, -1);
Vector2 snap = Vector2(0.1, 0.1);
@@ -129,7 +128,7 @@ public:
void set_y_label(const String &p_label);
String get_y_label() const;
- virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
+ virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual String get_caption() const override;
Vector2 get_closest_point(const Vector2 &p_point);
diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp
index 4a01b2ad65..71f9c45eea 100644
--- a/scene/animation/animation_blend_tree.cpp
+++ b/scene/animation/animation_blend_tree.cpp
@@ -44,7 +44,26 @@ StringName AnimationNodeAnimation::get_animation() const {
Vector<String> (*AnimationNodeAnimation::get_editable_animation_list)() = nullptr;
void AnimationNodeAnimation::get_parameter_list(List<PropertyInfo> *r_list) const {
- r_list->push_back(PropertyInfo(Variant::FLOAT, time, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
+ AnimationNode::get_parameter_list(r_list);
+}
+
+AnimationNode::NodeTimeInfo AnimationNodeAnimation::get_node_time_info() const {
+ NodeTimeInfo nti;
+ if (!process_state->tree->has_animation(animation)) {
+ return nti;
+ }
+
+ if (use_custom_timeline) {
+ nti.length = timeline_length;
+ nti.loop_mode = loop_mode;
+ } else {
+ Ref<Animation> anim = process_state->tree->get_animation(animation);
+ nti.length = (double)anim->get_length();
+ nti.loop_mode = anim->get_loop_mode();
+ }
+ nti.position = get_parameter(current_position);
+
+ return nti;
}
void AnimationNodeAnimation::_validate_property(PropertyInfo &p_property) const {
@@ -62,11 +81,34 @@ void AnimationNodeAnimation::_validate_property(PropertyInfo &p_property) const
p_property.hint_string = anims;
}
}
+
+ if (!use_custom_timeline) {
+ if (p_property.name == "timeline_length" || p_property.name == "start_offset" || p_property.name == "loop_mode" || p_property.name == "stretch_time_scale") {
+ p_property.usage = PROPERTY_USAGE_NONE;
+ }
+ }
}
-double AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
- double cur_time = get_parameter(time);
+AnimationNode::NodeTimeInfo AnimationNodeAnimation::process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+ process_state->is_testing = p_test_only;
+ AnimationMixer::PlaybackInfo pi = p_playback_info;
+ if (p_playback_info.seeked) {
+ pi.delta = get_node_time_info().position - p_playback_info.time;
+ } else {
+ pi.time = get_node_time_info().position + (backward ? -p_playback_info.delta : p_playback_info.delta);
+ }
+
+ NodeTimeInfo nti = _process(pi, p_test_only);
+
+ if (!p_test_only) {
+ set_node_time_info(nti);
+ }
+
+ return nti;
+}
+
+AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
if (!process_state->tree->has_animation(animation)) {
AnimationNodeBlendTree *tree = Object::cast_to<AnimationNodeBlendTree>(node_state.parent);
if (tree) {
@@ -77,87 +119,129 @@ double AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_pla
make_invalid(vformat(RTR("Animation not found: '%s'"), animation));
}
- return 0;
+ return NodeTimeInfo();
}
Ref<Animation> anim = process_state->tree->get_animation(animation);
double anim_size = (double)anim->get_length();
- double step = 0.0;
- double prev_time = cur_time;
+
+ NodeTimeInfo cur_nti = get_node_time_info();
+ double cur_len = cur_nti.length;
+ double cur_time = p_playback_info.time;
+ double cur_delta = p_playback_info.delta;
+
+ Animation::LoopMode cur_loop_mode = cur_nti.loop_mode;
+ double prev_time = cur_nti.position;
+
Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE;
bool node_backward = play_mode == PLAY_MODE_BACKWARD;
- double p_time = p_playback_info.time;
bool p_seek = p_playback_info.seeked;
bool p_is_external_seeking = p_playback_info.is_external_seeking;
- if (p_playback_info.seeked) {
- step = p_time - cur_time;
- cur_time = p_time;
+ bool is_just_looped = false;
+
+ // 1. Progress for AnimationNode.
+ if (cur_loop_mode != Animation::LOOP_NONE) {
+ if (cur_loop_mode == Animation::LOOP_LINEAR) {
+ if (!Math::is_zero_approx(cur_len)) {
+ if (prev_time <= cur_len && cur_time > cur_len) {
+ is_just_looped = true; // Don't break with negative timescale since remain will not be 0.
+ }
+ cur_time = Math::fposmod(cur_time, cur_len);
+ }
+ backward = false;
+ } else {
+ if (!Math::is_zero_approx(cur_len)) {
+ if (prev_time >= 0 && cur_time < 0) {
+ backward = !backward;
+ } else if (prev_time <= cur_len && cur_time > cur_len) {
+ backward = !backward;
+ is_just_looped = true; // Don't break with negative timescale since remain will not be 0.
+ }
+ cur_time = Math::pingpong(cur_time, cur_len);
+ }
+ }
} else {
- p_time *= backward ? -1.0 : 1.0;
- cur_time = cur_time + p_time;
- step = p_time;
+ if (cur_time < 0) {
+ cur_delta += cur_time;
+ cur_time = 0;
+ } else if (cur_time > cur_len) {
+ cur_delta += cur_time - cur_len;
+ cur_time = cur_len;
+ }
+ backward = false;
+ // If ended, don't progress AnimationNode. So set delta to 0.
+ if (!Math::is_zero_approx(cur_delta)) {
+ if (play_mode == PLAY_MODE_FORWARD) {
+ if (prev_time >= cur_len) {
+ cur_delta = 0;
+ }
+ } else {
+ if (prev_time <= 0) {
+ cur_delta = 0;
+ }
+ }
+ }
}
- bool is_looping = false;
- if (anim->get_loop_mode() == Animation::LOOP_PINGPONG) {
+ // 2. For return, store "AnimationNode" time info here, not "Animation" time info as below.
+ NodeTimeInfo nti;
+ nti.length = cur_len;
+ nti.position = cur_time;
+ nti.delta = cur_delta;
+ nti.loop_mode = cur_loop_mode;
+ nti.is_just_looped = is_just_looped;
+
+ // 3. Progress for Animation.
+ double prev_playback_time = prev_time - start_offset;
+ double cur_playback_time = cur_time - start_offset;
+ if (stretch_time_scale) {
+ double mlt = anim_size / cur_len;
+ cur_playback_time *= mlt;
+ cur_delta *= mlt;
+ }
+ if (cur_loop_mode == Animation::LOOP_LINEAR) {
if (!Math::is_zero_approx(anim_size)) {
- if (prev_time >= 0 && cur_time < 0) {
- backward = !backward;
+ prev_playback_time = Math::fposmod(prev_playback_time, anim_size);
+ cur_playback_time = Math::fposmod(cur_playback_time, anim_size);
+ if (prev_playback_time >= 0 && cur_playback_time < 0) {
looped_flag = node_backward ? Animation::LOOPED_FLAG_END : Animation::LOOPED_FLAG_START;
}
- if (prev_time <= anim_size && cur_time > anim_size) {
- backward = !backward;
+ if (prev_playback_time <= anim_size && cur_playback_time > anim_size) {
looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END;
}
- cur_time = Math::pingpong(cur_time, anim_size);
}
- is_looping = true;
- } else if (anim->get_loop_mode() == Animation::LOOP_LINEAR) {
+ } else if (cur_loop_mode == Animation::LOOP_PINGPONG) {
if (!Math::is_zero_approx(anim_size)) {
- if (prev_time >= 0 && cur_time < 0) {
+ if (Math::fposmod(cur_playback_time, anim_size * 2.0) >= anim_size) {
+ cur_delta = -cur_delta; // Needed for retrieveing discrete keys correctly.
+ }
+ prev_playback_time = Math::pingpong(prev_playback_time, anim_size);
+ cur_playback_time = Math::pingpong(cur_playback_time, anim_size);
+ if (prev_playback_time >= 0 && cur_playback_time < 0) {
looped_flag = node_backward ? Animation::LOOPED_FLAG_END : Animation::LOOPED_FLAG_START;
}
- if (prev_time <= anim_size && cur_time > anim_size) {
+ if (prev_playback_time <= anim_size && cur_playback_time > anim_size) {
looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END;
}
- cur_time = Math::fposmod(cur_time, anim_size);
}
- backward = false;
- is_looping = true;
} else {
- if (cur_time < 0) {
- step += cur_time;
- cur_time = 0;
- } else if (cur_time > anim_size) {
- step += anim_size - cur_time;
- cur_time = anim_size;
- }
- backward = false;
-
- // If ended, don't progress animation. So set delta to 0.
- if (p_time > 0) {
- if (play_mode == PLAY_MODE_FORWARD) {
- if (prev_time >= anim_size) {
- step = 0;
- }
- } else {
- if (prev_time <= 0) {
- step = 0;
- }
- }
+ if (cur_playback_time < 0) {
+ cur_playback_time = 0;
+ } else if (cur_playback_time > anim_size) {
+ cur_playback_time = anim_size;
}
// Emit start & finish signal. Internally, the detections are the same for backward.
// We should use call_deferred since the track keys are still being processed.
if (process_state->tree && !p_test_only) {
// AnimationTree uses seek to 0 "internally" to process the first key of the animation, which is used as the start detection.
- if (p_seek && !p_is_external_seeking && cur_time == 0) {
+ if (p_seek && !p_is_external_seeking && cur_playback_time == 0) {
process_state->tree->call_deferred(SNAME("emit_signal"), "animation_started", animation);
}
// Finished.
- if (prev_time < anim_size && cur_time >= anim_size) {
+ if (prev_time - start_offset < anim_size && cur_playback_time >= anim_size) {
process_state->tree->call_deferred(SNAME("emit_signal"), "animation_finished", animation);
}
}
@@ -166,19 +250,18 @@ double AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_pla
if (!p_test_only) {
AnimationMixer::PlaybackInfo pi = p_playback_info;
if (play_mode == PLAY_MODE_FORWARD) {
- pi.time = cur_time;
- pi.delta = step;
+ pi.time = cur_playback_time;
+ pi.delta = cur_delta;
} else {
- pi.time = anim_size - cur_time;
- pi.delta = -step;
+ pi.time = anim_size - cur_playback_time;
+ pi.delta = -cur_delta;
}
pi.weight = 1.0;
pi.looped_flag = looped_flag;
blend_animation(animation, pi);
}
- set_parameter(time, cur_time);
- return is_looping ? HUGE_LENGTH : anim_size - cur_time;
+ return nti;
}
String AnimationNodeAnimation::get_caption() const {
@@ -201,6 +284,48 @@ bool AnimationNodeAnimation::is_backward() const {
return backward;
}
+void AnimationNodeAnimation::set_use_custom_timeline(bool p_use_custom_timeline) {
+ use_custom_timeline = p_use_custom_timeline;
+ notify_property_list_changed();
+}
+
+bool AnimationNodeAnimation::is_using_custom_timeline() const {
+ return use_custom_timeline;
+}
+
+void AnimationNodeAnimation::set_timeline_length(double p_length) {
+ timeline_length = p_length;
+}
+
+double AnimationNodeAnimation::get_timeline_length() const {
+ return timeline_length;
+}
+
+void AnimationNodeAnimation::set_stretch_time_scale(bool p_strech_time_scale) {
+ stretch_time_scale = p_strech_time_scale;
+ notify_property_list_changed();
+}
+
+bool AnimationNodeAnimation::is_stretching_time_scale() const {
+ return stretch_time_scale;
+}
+
+void AnimationNodeAnimation::set_start_offset(double p_offset) {
+ start_offset = p_offset;
+}
+
+double AnimationNodeAnimation::get_start_offset() const {
+ return start_offset;
+}
+
+void AnimationNodeAnimation::set_loop_mode(Animation::LoopMode p_loop_mode) {
+ loop_mode = p_loop_mode;
+}
+
+Animation::LoopMode AnimationNodeAnimation::get_loop_mode() const {
+ return loop_mode;
+}
+
void AnimationNodeAnimation::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_animation", "name"), &AnimationNodeAnimation::set_animation);
ClassDB::bind_method(D_METHOD("get_animation"), &AnimationNodeAnimation::get_animation);
@@ -208,8 +333,28 @@ void AnimationNodeAnimation::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_play_mode", "mode"), &AnimationNodeAnimation::set_play_mode);
ClassDB::bind_method(D_METHOD("get_play_mode"), &AnimationNodeAnimation::get_play_mode);
+ ClassDB::bind_method(D_METHOD("set_use_custom_timeline", "use_custom_timeline"), &AnimationNodeAnimation::set_use_custom_timeline);
+ ClassDB::bind_method(D_METHOD("is_using_custom_timeline"), &AnimationNodeAnimation::is_using_custom_timeline);
+
+ ClassDB::bind_method(D_METHOD("set_timeline_length", "timeline_length"), &AnimationNodeAnimation::set_timeline_length);
+ ClassDB::bind_method(D_METHOD("get_timeline_length"), &AnimationNodeAnimation::get_timeline_length);
+
+ ClassDB::bind_method(D_METHOD("set_stretch_time_scale", "stretch_time_scale"), &AnimationNodeAnimation::set_stretch_time_scale);
+ ClassDB::bind_method(D_METHOD("is_stretching_time_scale"), &AnimationNodeAnimation::is_stretching_time_scale);
+
+ ClassDB::bind_method(D_METHOD("set_start_offset", "start_offset"), &AnimationNodeAnimation::set_start_offset);
+ ClassDB::bind_method(D_METHOD("get_start_offset"), &AnimationNodeAnimation::get_start_offset);
+
+ ClassDB::bind_method(D_METHOD("set_loop_mode", "loop_mode"), &AnimationNodeAnimation::set_loop_mode);
+ ClassDB::bind_method(D_METHOD("get_loop_mode"), &AnimationNodeAnimation::get_loop_mode);
+
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation"), "set_animation", "get_animation");
ADD_PROPERTY(PropertyInfo(Variant::INT, "play_mode", PROPERTY_HINT_ENUM, "Forward,Backward"), "set_play_mode", "get_play_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_custom_timeline"), "set_use_custom_timeline", "is_using_custom_timeline");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "timeline_length", PROPERTY_HINT_RANGE, "0.001,60,0.001,or_greater,or_less,hide_slider,suffix:s"), "set_timeline_length", "get_timeline_length");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stretch_time_scale"), "set_stretch_time_scale", "is_stretching_time_scale");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "start_offset", PROPERTY_HINT_RANGE, "-60,60,0.001,or_greater,or_less,hide_slider,suffix:s"), "set_start_offset", "get_start_offset");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "None,Linear,Ping-Pong"), "set_loop_mode", "get_loop_mode");
BIND_ENUM_CONSTANT(PLAY_MODE_FORWARD);
BIND_ENUM_CONSTANT(PLAY_MODE_BACKWARD);
@@ -240,16 +385,21 @@ AnimationNodeSync::AnimationNodeSync() {
////////////////////////////////////////////////////////
void AnimationNodeOneShot::get_parameter_list(List<PropertyInfo> *r_list) const {
+ AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::BOOL, active, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY));
r_list->push_back(PropertyInfo(Variant::BOOL, internal_active, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY));
r_list->push_back(PropertyInfo(Variant::INT, request, PROPERTY_HINT_ENUM, ",Fire,Abort,Fade Out"));
- r_list->push_back(PropertyInfo(Variant::FLOAT, time, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
- r_list->push_back(PropertyInfo(Variant::FLOAT, remaining, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
+ r_list->push_back(PropertyInfo(Variant::FLOAT, fade_in_remaining, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
r_list->push_back(PropertyInfo(Variant::FLOAT, fade_out_remaining, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
r_list->push_back(PropertyInfo(Variant::FLOAT, time_to_restart, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
}
Variant AnimationNodeOneShot::get_parameter_default_value(const StringName &p_parameter) const {
+ Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
+ if (ret != Variant()) {
+ return ret;
+ }
+
if (p_parameter == request) {
return ONE_SHOT_REQUEST_NONE;
} else if (p_parameter == active || p_parameter == internal_active) {
@@ -262,6 +412,10 @@ Variant AnimationNodeOneShot::get_parameter_default_value(const StringName &p_pa
}
bool AnimationNodeOneShot::is_parameter_read_only(const StringName &p_parameter) const {
+ if (AnimationNode::is_parameter_read_only(p_parameter)) {
+ return true;
+ }
+
if (p_parameter == active || p_parameter == internal_active) {
return true;
}
@@ -332,6 +486,14 @@ AnimationNodeOneShot::MixMode AnimationNodeOneShot::get_mix_mode() const {
return mix;
}
+void AnimationNodeOneShot::set_break_loop_at_end(bool p_enable) {
+ break_loop_at_end = p_enable;
+}
+
+bool AnimationNodeOneShot::is_loop_broken_at_end() const {
+ return break_loop_at_end;
+}
+
String AnimationNodeOneShot::get_caption() const {
return "OneShot";
}
@@ -340,14 +502,14 @@ bool AnimationNodeOneShot::has_filter() const {
return true;
}
-double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
OneShotRequest cur_request = static_cast<OneShotRequest>((int)get_parameter(request));
bool cur_active = get_parameter(active);
bool cur_internal_active = get_parameter(internal_active);
- double cur_time = get_parameter(time);
- double cur_remaining = get_parameter(remaining);
- double cur_fade_out_remaining = get_parameter(fade_out_remaining);
+ NodeTimeInfo cur_nti = get_node_time_info();
double cur_time_to_restart = get_parameter(time_to_restart);
+ double cur_fade_in_remaining = get_parameter(fade_in_remaining);
+ double cur_fade_out_remaining = get_parameter(fade_out_remaining);
set_parameter(request, ONE_SHOT_REQUEST_NONE);
@@ -356,6 +518,8 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb
bool is_fading_out = cur_active == true && cur_internal_active == false;
double p_time = p_playback_info.time;
+ double p_delta = p_playback_info.delta;
+ double abs_delta = Math::abs(p_delta);
bool p_seek = p_playback_info.seeked;
bool p_is_external_seeking = p_playback_info.is_external_seeking;
@@ -374,6 +538,7 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb
// Request fading.
is_fading_out = true;
cur_fade_out_remaining = fade_out;
+ cur_fade_in_remaining = 0;
} else {
// Shot is ended, do nothing.
is_shooting = false;
@@ -382,7 +547,7 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb
set_parameter(time_to_restart, -1);
} else if (!do_start && !cur_active) {
if (cur_time_to_restart >= 0.0 && !p_seek) {
- cur_time_to_restart -= p_time;
+ cur_time_to_restart -= abs_delta;
if (cur_time_to_restart < 0) {
do_start = true; // Restart.
}
@@ -413,8 +578,11 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb
}
if (do_start) {
- cur_time = 0;
os_seek = true;
+ if (!cur_internal_active) {
+ cur_fade_in_remaining = fade_in; // If already active, don't fade-in again.
+ }
+ cur_internal_active = true;
set_parameter(request, ONE_SHOT_REQUEST_NONE);
set_parameter(internal_active, true);
set_parameter(active, true);
@@ -422,20 +590,17 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb
real_t blend = 1.0;
bool use_blend = sync;
- if (cur_time < fade_in) {
+
+ if (cur_fade_in_remaining > 0) {
if (fade_in > 0) {
use_blend = true;
- blend = cur_time / fade_in;
+ blend = (fade_in - cur_fade_in_remaining) / fade_in;
if (fade_in_curve.is_valid()) {
blend = fade_in_curve->sample(blend);
}
} else {
blend = 0; // Should not happen.
}
- } else if (!do_start && !is_fading_out && cur_remaining <= fade_out) {
- is_fading_out = true;
- cur_fade_out_remaining = cur_remaining;
- set_parameter(internal_active, false);
}
if (is_fading_out) {
@@ -451,34 +616,36 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb
}
AnimationMixer::PlaybackInfo pi = p_playback_info;
- double main_rem = 0.0;
+ NodeTimeInfo main_nti;
if (mix == MIX_MODE_ADD) {
pi.weight = 1.0;
- main_rem = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
+ main_nti = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
} else {
pi.seeked &= use_blend;
pi.weight = 1.0 - blend;
- main_rem = blend_input(0, pi, FILTER_BLEND, sync, p_test_only); // Unlike below, processing this edge is a corner case.
+ main_nti = blend_input(0, pi, FILTER_BLEND, sync, p_test_only); // Unlike below, processing this edge is a corner case.
}
+
pi = p_playback_info;
- if (os_seek) {
- pi.time = cur_time;
+ if (do_start) {
+ pi.time = 0;
+ } else if (os_seek) {
+ pi.time = cur_nti.position;
}
pi.seeked = os_seek;
pi.weight = Math::is_zero_approx(blend) ? CMP_EPSILON : blend;
- double os_rem = blend_input(1, pi, FILTER_PASS, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
- if (do_start) {
- cur_remaining = os_rem;
+ NodeTimeInfo os_nti = blend_input(1, pi, FILTER_PASS, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
+
+ if (cur_fade_in_remaining <= 0 && !do_start && !is_fading_out && os_nti.get_remain(break_loop_at_end) <= fade_out) {
+ is_fading_out = true;
+ cur_fade_out_remaining = os_nti.get_remain(break_loop_at_end);
+ cur_fade_in_remaining = 0;
+ set_parameter(internal_active, false);
}
- if (p_seek) {
- cur_time = p_time;
- } else {
- cur_time += p_time;
- cur_remaining = os_rem;
- cur_fade_out_remaining -= p_time;
- if (cur_remaining <= 0 || (is_fading_out && cur_fade_out_remaining <= 0)) {
+ if (!p_seek) {
+ if (os_nti.get_remain(break_loop_at_end) <= 0 || (is_fading_out && cur_fade_out_remaining <= 0)) {
set_parameter(internal_active, false);
set_parameter(active, false);
if (auto_restart) {
@@ -486,13 +653,17 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb
set_parameter(time_to_restart, restart_sec);
}
}
+ double d = Math::abs(os_nti.delta);
+ if (!do_start) {
+ cur_fade_in_remaining = MAX(0, cur_fade_in_remaining - d); // Don't consider seeked delta by restart.
+ }
+ cur_fade_out_remaining = MAX(0, cur_fade_out_remaining - d);
}
- set_parameter(time, cur_time);
- set_parameter(remaining, cur_remaining);
+ set_parameter(fade_in_remaining, cur_fade_in_remaining);
set_parameter(fade_out_remaining, cur_fade_out_remaining);
- return MAX(main_rem, cur_remaining);
+ return cur_internal_active ? os_nti : main_nti;
}
void AnimationNodeOneShot::_bind_methods() {
@@ -508,6 +679,9 @@ void AnimationNodeOneShot::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_fadeout_curve", "curve"), &AnimationNodeOneShot::set_fade_out_curve);
ClassDB::bind_method(D_METHOD("get_fadeout_curve"), &AnimationNodeOneShot::get_fade_out_curve);
+ ClassDB::bind_method(D_METHOD("set_break_loop_at_end", "enable"), &AnimationNodeOneShot::set_break_loop_at_end);
+ ClassDB::bind_method(D_METHOD("is_loop_broken_at_end"), &AnimationNodeOneShot::is_loop_broken_at_end);
+
ClassDB::bind_method(D_METHOD("set_autorestart", "active"), &AnimationNodeOneShot::set_auto_restart_enabled);
ClassDB::bind_method(D_METHOD("has_autorestart"), &AnimationNodeOneShot::is_auto_restart_enabled);
@@ -526,6 +700,7 @@ void AnimationNodeOneShot::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "fadein_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_fadein_curve", "get_fadein_curve");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fadeout_time", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater,suffix:s"), "set_fadeout_time", "get_fadeout_time");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "fadeout_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_fadeout_curve", "get_fadeout_curve");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "break_loop_at_end"), "set_break_loop_at_end", "is_loop_broken_at_end");
ADD_GROUP("Auto Restart", "autorestart_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autorestart"), "set_autorestart", "has_autorestart");
@@ -550,10 +725,16 @@ AnimationNodeOneShot::AnimationNodeOneShot() {
////////////////////////////////////////////////
void AnimationNodeAdd2::get_parameter_list(List<PropertyInfo> *r_list) const {
+ AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, add_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater"));
}
Variant AnimationNodeAdd2::get_parameter_default_value(const StringName &p_parameter) const {
+ Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
+ if (ret != Variant()) {
+ return ret;
+ }
+
return 0;
}
@@ -565,16 +746,16 @@ bool AnimationNodeAdd2::has_filter() const {
return true;
}
-double AnimationNodeAdd2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNodeAdd2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double amount = get_parameter(add_amount);
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = 1.0;
- double rem0 = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
+ NodeTimeInfo nti = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
pi.weight = amount;
blend_input(1, pi, FILTER_PASS, sync, p_test_only);
- return rem0;
+ return nti;
}
void AnimationNodeAdd2::_bind_methods() {
@@ -588,10 +769,16 @@ AnimationNodeAdd2::AnimationNodeAdd2() {
////////////////////////////////////////////////
void AnimationNodeAdd3::get_parameter_list(List<PropertyInfo> *r_list) const {
+ AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, add_amount, PROPERTY_HINT_RANGE, "-1,1,0.01,or_less,or_greater"));
}
Variant AnimationNodeAdd3::get_parameter_default_value(const StringName &p_parameter) const {
+ Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
+ if (ret != Variant()) {
+ return ret;
+ }
+
return 0;
}
@@ -603,18 +790,18 @@ bool AnimationNodeAdd3::has_filter() const {
return true;
}
-double AnimationNodeAdd3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNodeAdd3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double amount = get_parameter(add_amount);
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = MAX(0, -amount);
blend_input(0, pi, FILTER_PASS, sync, p_test_only);
pi.weight = 1.0;
- double rem0 = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only);
+ NodeTimeInfo nti = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only);
pi.weight = MAX(0, amount);
blend_input(2, pi, FILTER_PASS, sync, p_test_only);
- return rem0;
+ return nti;
}
void AnimationNodeAdd3::_bind_methods() {
@@ -629,10 +816,16 @@ AnimationNodeAdd3::AnimationNodeAdd3() {
/////////////////////////////////////////////
void AnimationNodeBlend2::get_parameter_list(List<PropertyInfo> *r_list) const {
+ AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, blend_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater"));
}
Variant AnimationNodeBlend2::get_parameter_default_value(const StringName &p_parameter) const {
+ Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
+ if (ret != Variant()) {
+ return ret;
+ }
+
return 0; // For blend amount.
}
@@ -640,16 +833,16 @@ String AnimationNodeBlend2::get_caption() const {
return "Blend2";
}
-double AnimationNodeBlend2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNodeBlend2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double amount = get_parameter(blend_amount);
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = 1.0 - amount;
- double rem0 = blend_input(0, pi, FILTER_BLEND, sync, p_test_only);
+ NodeTimeInfo nti0 = blend_input(0, pi, FILTER_BLEND, sync, p_test_only);
pi.weight = amount;
- double rem1 = blend_input(1, pi, FILTER_PASS, sync, p_test_only);
+ NodeTimeInfo nti1 = blend_input(1, pi, FILTER_PASS, sync, p_test_only);
- return amount > 0.5 ? rem1 : rem0; // Hacky but good enough.
+ return amount > 0.5 ? nti1 : nti0; // Hacky but good enough.
}
bool AnimationNodeBlend2::has_filter() const {
@@ -667,10 +860,16 @@ AnimationNodeBlend2::AnimationNodeBlend2() {
//////////////////////////////////////
void AnimationNodeBlend3::get_parameter_list(List<PropertyInfo> *r_list) const {
+ AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, blend_amount, PROPERTY_HINT_RANGE, "-1,1,0.01,or_less,or_greater"));
}
Variant AnimationNodeBlend3::get_parameter_default_value(const StringName &p_parameter) const {
+ Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
+ if (ret != Variant()) {
+ return ret;
+ }
+
return 0; // For blend amount.
}
@@ -678,18 +877,18 @@ String AnimationNodeBlend3::get_caption() const {
return "Blend3";
}
-double AnimationNodeBlend3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNodeBlend3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double amount = get_parameter(blend_amount);
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = MAX(0, -amount);
- double rem0 = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
+ NodeTimeInfo nti0 = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
pi.weight = 1.0 - ABS(amount);
- double rem1 = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only);
+ NodeTimeInfo nti1 = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only);
pi.weight = MAX(0, amount);
- double rem2 = blend_input(2, pi, FILTER_IGNORE, sync, p_test_only);
+ NodeTimeInfo nti2 = blend_input(2, pi, FILTER_IGNORE, sync, p_test_only);
- return amount > 0.5 ? rem2 : (amount < -0.5 ? rem0 : rem1); // Hacky but good enough.
+ return amount > 0.5 ? nti2 : (amount < -0.5 ? nti0 : nti1); // Hacky but good enough.
}
void AnimationNodeBlend3::_bind_methods() {
@@ -704,10 +903,16 @@ AnimationNodeBlend3::AnimationNodeBlend3() {
////////////////////////////////////////////////
void AnimationNodeSub2::get_parameter_list(List<PropertyInfo> *r_list) const {
+ AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, sub_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater"));
}
Variant AnimationNodeSub2::get_parameter_default_value(const StringName &p_parameter) const {
+ Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
+ if (ret != Variant()) {
+ return ret;
+ }
+
return 0;
}
@@ -719,7 +924,7 @@ bool AnimationNodeSub2::has_filter() const {
return true;
}
-double AnimationNodeSub2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNodeSub2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double amount = get_parameter(sub_amount);
AnimationMixer::PlaybackInfo pi = p_playback_info;
@@ -742,10 +947,16 @@ AnimationNodeSub2::AnimationNodeSub2() {
/////////////////////////////////
void AnimationNodeTimeScale::get_parameter_list(List<PropertyInfo> *r_list) const {
+ AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, scale, PROPERTY_HINT_RANGE, "-32,32,0.01,or_less,or_greater"));
}
Variant AnimationNodeTimeScale::get_parameter_default_value(const StringName &p_parameter) const {
+ Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
+ if (ret != Variant()) {
+ return ret;
+ }
+
return 1.0; // Initial timescale.
}
@@ -753,13 +964,13 @@ String AnimationNodeTimeScale::get_caption() const {
return "TimeScale";
}
-double AnimationNodeTimeScale::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNodeTimeScale::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double cur_scale = get_parameter(scale);
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = 1.0;
if (!pi.seeked) {
- pi.time *= cur_scale;
+ pi.delta *= cur_scale;
}
return blend_input(0, pi, FILTER_IGNORE, true, p_test_only);
@@ -775,10 +986,16 @@ AnimationNodeTimeScale::AnimationNodeTimeScale() {
////////////////////////////////////
void AnimationNodeTimeSeek::get_parameter_list(List<PropertyInfo> *r_list) const {
+ AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, seek_pos_request, PROPERTY_HINT_RANGE, "-1,3600,0.01,or_greater")); // It will be reset to -1 after seeking the position immediately.
}
Variant AnimationNodeTimeSeek::get_parameter_default_value(const StringName &p_parameter) const {
+ Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
+ if (ret != Variant()) {
+ return ret;
+ }
+
return -1.0; // Initial seek request.
}
@@ -786,7 +1003,7 @@ String AnimationNodeTimeSeek::get_caption() const {
return "TimeSeek";
}
-double AnimationNodeTimeSeek::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNodeTimeSeek::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double cur_seek_pos = get_parameter(seek_pos_request);
AnimationMixer::PlaybackInfo pi = p_playback_info;
@@ -833,6 +1050,8 @@ bool AnimationNodeTransition::_set(const StringName &p_path, const Variant &p_va
set_input_name(which, p_value);
} else if (what == "auto_advance") {
set_input_as_auto_advance(which, p_value);
+ } else if (what == "break_loop_at_end") {
+ set_input_break_loop_at_end(which, p_value);
} else if (what == "reset") {
set_input_reset(which, p_value);
} else {
@@ -858,6 +1077,8 @@ bool AnimationNodeTransition::_get(const StringName &p_path, Variant &r_ret) con
r_ret = get_input_name(which);
} else if (what == "auto_advance") {
r_ret = is_input_set_as_auto_advance(which);
+ } else if (what == "break_loop_at_end") {
+ r_ret = is_input_loop_broken_at_end(which);
} else if (what == "reset") {
r_ret = is_input_reset(which);
} else {
@@ -868,6 +1089,7 @@ bool AnimationNodeTransition::_get(const StringName &p_path, Variant &r_ret) con
}
void AnimationNodeTransition::get_parameter_list(List<PropertyInfo> *r_list) const {
+ AnimationNode::get_parameter_list(r_list);
String anims;
for (int i = 0; i < get_input_count(); i++) {
if (i > 0) {
@@ -880,12 +1102,16 @@ void AnimationNodeTransition::get_parameter_list(List<PropertyInfo> *r_list) con
r_list->push_back(PropertyInfo(Variant::STRING, transition_request, PROPERTY_HINT_ENUM, anims)); // For transition request. It will be cleared after setting the value immediately.
r_list->push_back(PropertyInfo(Variant::INT, current_index, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY)); // To avoid finding the index every frame, use this internally.
r_list->push_back(PropertyInfo(Variant::INT, prev_index, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
- r_list->push_back(PropertyInfo(Variant::FLOAT, time, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
r_list->push_back(PropertyInfo(Variant::FLOAT, prev_xfading, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
}
Variant AnimationNodeTransition::get_parameter_default_value(const StringName &p_parameter) const {
- if (p_parameter == time || p_parameter == prev_xfading) {
+ Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
+ if (ret != Variant()) {
+ return ret;
+ }
+
+ if (p_parameter == prev_xfading) {
return 0.0;
} else if (p_parameter == prev_index || p_parameter == current_index) {
return -1;
@@ -895,6 +1121,10 @@ Variant AnimationNodeTransition::get_parameter_default_value(const StringName &p
}
bool AnimationNodeTransition::is_parameter_read_only(const StringName &p_parameter) const {
+ if (AnimationNode::is_parameter_read_only(p_parameter)) {
+ return true;
+ }
+
if (p_parameter == current_state || p_parameter == current_index) {
return true;
}
@@ -947,6 +1177,16 @@ bool AnimationNodeTransition::is_input_set_as_auto_advance(int p_input) const {
return input_data[p_input].auto_advance;
}
+void AnimationNodeTransition::set_input_break_loop_at_end(int p_input, bool p_enable) {
+ ERR_FAIL_INDEX(p_input, get_input_count());
+ input_data.write[p_input].break_loop_at_end = p_enable;
+}
+
+bool AnimationNodeTransition::is_input_loop_broken_at_end(int p_input) const {
+ ERR_FAIL_INDEX_V(p_input, get_input_count(), false);
+ return input_data[p_input].break_loop_at_end;
+}
+
void AnimationNodeTransition::set_input_reset(int p_input, bool p_enable) {
ERR_FAIL_INDEX(p_input, get_input_count());
input_data.write[p_input].reset = p_enable;
@@ -981,12 +1221,12 @@ bool AnimationNodeTransition::is_allow_transition_to_self() const {
return allow_transition_to_self;
}
-double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
String cur_transition_request = get_parameter(transition_request);
int cur_current_index = get_parameter(current_index);
int cur_prev_index = get_parameter(prev_index);
- double cur_time = get_parameter(time);
+ NodeTimeInfo cur_nti = get_node_time_info();
double cur_prev_xfading = get_parameter(prev_xfading);
bool switched = false;
@@ -1052,7 +1292,6 @@ double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_pl
// Special case for restart.
if (restart) {
- set_parameter(time, 0);
pi.time = 0;
pi.seeked = true;
pi.weight = 1.0;
@@ -1061,16 +1300,12 @@ double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_pl
if (switched) {
cur_prev_xfading = xfade_time;
- cur_time = 0;
}
if (cur_current_index < 0 || cur_current_index >= get_input_count() || cur_prev_index >= get_input_count()) {
- return 0;
+ return NodeTimeInfo();
}
- double rem = 0.0;
- double abs_time = Math::abs(p_time);
-
if (sync) {
pi.weight = 0;
for (int i = 0; i < get_input_count(); i++) {
@@ -1081,20 +1316,11 @@ double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_pl
}
if (cur_prev_index < 0) { // Process current animation, check for transition.
-
pi.weight = 1.0;
- rem = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only);
-
- if (p_seek) {
- cur_time = abs_time;
- } else {
- cur_time += abs_time;
- }
-
- if (input_data[cur_current_index].auto_advance && rem <= xfade_time) {
+ cur_nti = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only);
+ if (input_data[cur_current_index].auto_advance && cur_nti.get_remain(input_data[cur_current_index].break_loop_at_end) <= xfade_time) {
set_parameter(transition_request, get_input_name((cur_current_index + 1) % get_input_count()));
}
-
} else { // Cross-fading from prev to current.
real_t blend = 0.0;
@@ -1117,33 +1343,30 @@ double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_pl
pi.time = 0;
pi.seeked = true;
}
- rem = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only);
+ cur_nti = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only);
pi = p_playback_info;
pi.seeked &= use_blend;
pi.weight = blend;
blend_input(cur_prev_index, pi, FILTER_IGNORE, true, p_test_only);
- if (p_seek) {
- cur_time = abs_time;
- } else {
- cur_time += abs_time;
- cur_prev_xfading -= abs_time;
- if (cur_prev_xfading < 0) {
+ if (!p_seek) {
+ if (cur_prev_xfading <= 0) {
set_parameter(prev_index, -1);
}
+ cur_prev_xfading -= Math::abs(p_playback_info.delta);
}
}
- set_parameter(time, cur_time);
set_parameter(prev_xfading, cur_prev_xfading);
- return rem;
+ return cur_nti;
}
void AnimationNodeTransition::_get_property_list(List<PropertyInfo> *p_list) const {
for (int i = 0; i < get_input_count(); i++) {
p_list->push_back(PropertyInfo(Variant::STRING, "input_" + itos(i) + "/name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/auto_advance", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/break_loop_at_end", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/reset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL));
}
}
@@ -1154,6 +1377,9 @@ void AnimationNodeTransition::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_input_as_auto_advance", "input", "enable"), &AnimationNodeTransition::set_input_as_auto_advance);
ClassDB::bind_method(D_METHOD("is_input_set_as_auto_advance", "input"), &AnimationNodeTransition::is_input_set_as_auto_advance);
+ ClassDB::bind_method(D_METHOD("set_input_break_loop_at_end", "input", "enable"), &AnimationNodeTransition::set_input_break_loop_at_end);
+ ClassDB::bind_method(D_METHOD("is_input_loop_broken_at_end", "input"), &AnimationNodeTransition::is_input_loop_broken_at_end);
+
ClassDB::bind_method(D_METHOD("set_input_reset", "input", "enable"), &AnimationNodeTransition::set_input_reset);
ClassDB::bind_method(D_METHOD("is_input_reset", "input"), &AnimationNodeTransition::is_input_reset);
@@ -1181,7 +1407,7 @@ String AnimationNodeOutput::get_caption() const {
return "Output";
}
-double AnimationNodeOutput::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNodeOutput::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = 1.0;
return blend_input(0, pi, FILTER_IGNORE, true, p_test_only);
@@ -1400,10 +1626,10 @@ String AnimationNodeBlendTree::get_caption() const {
return "BlendTree";
}
-double AnimationNodeBlendTree::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNodeBlendTree::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
Ref<AnimationNodeOutput> output = nodes[SceneStringNames::get_singleton()->output].node;
node_state.connections = nodes[SceneStringNames::get_singleton()->output].connections;
- ERR_FAIL_COND_V(output.is_null(), 0);
+ ERR_FAIL_COND_V(output.is_null(), NodeTimeInfo());
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = 1.0;
diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h
index cf0884b892..c7ef7ed624 100644
--- a/scene/animation/animation_blend_tree.h
+++ b/scene/animation/animation_blend_tree.h
@@ -37,7 +37,12 @@ class AnimationNodeAnimation : public AnimationRootNode {
GDCLASS(AnimationNodeAnimation, AnimationRootNode);
StringName animation;
- StringName time = "time";
+
+ bool use_custom_timeline = false;
+ double timeline_length = 1.0;
+ Animation::LoopMode loop_mode = Animation::LOOP_NONE;
+ bool stretch_time_scale = true;
+ double start_offset = 0.0;
uint64_t last_version = 0;
bool skip = false;
@@ -50,10 +55,13 @@ public:
void get_parameter_list(List<PropertyInfo> *r_list) const override;
+ virtual NodeTimeInfo get_node_time_info() const override; // Wrapper of get_parameter().
+
static Vector<String> (*get_editable_animation_list)();
virtual String get_caption() const override;
- virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
+ virtual NodeTimeInfo process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
+ virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
void set_animation(const StringName &p_name);
StringName get_animation() const;
@@ -64,6 +72,21 @@ public:
void set_backward(bool p_backward);
bool is_backward() const;
+ void set_use_custom_timeline(bool p_use_custom_timeline);
+ bool is_using_custom_timeline() const;
+
+ void set_timeline_length(double p_length);
+ double get_timeline_length() const;
+
+ void set_stretch_time_scale(bool p_strech_time_scale);
+ bool is_stretching_time_scale() const;
+
+ void set_start_offset(double p_offset);
+ double get_start_offset() const;
+
+ void set_loop_mode(Animation::LoopMode p_loop_mode);
+ Animation::LoopMode get_loop_mode() const;
+
AnimationNodeAnimation();
protected:
@@ -118,12 +141,12 @@ private:
double auto_restart_delay = 1.0;
double auto_restart_random_delay = 0.0;
MixMode mix = MIX_MODE_BLEND;
+ bool break_loop_at_end = false;
StringName request = PNAME("request");
StringName active = PNAME("active");
StringName internal_active = PNAME("internal_active");
- StringName time = "time";
- StringName remaining = "remaining";
+ StringName fade_in_remaining = "fade_in_remaining";
StringName fade_out_remaining = "fade_out_remaining";
StringName time_to_restart = "time_to_restart";
@@ -160,8 +183,11 @@ public:
void set_mix_mode(MixMode p_mix);
MixMode get_mix_mode() const;
+ void set_break_loop_at_end(bool p_enable);
+ bool is_loop_broken_at_end() const;
+
virtual bool has_filter() const override;
- virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
+ virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeOneShot();
};
@@ -184,7 +210,7 @@ public:
virtual String get_caption() const override;
virtual bool has_filter() const override;
- virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
+ virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeAdd2();
};
@@ -204,7 +230,7 @@ public:
virtual String get_caption() const override;
virtual bool has_filter() const override;
- virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
+ virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeAdd3();
};
@@ -222,7 +248,7 @@ public:
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
virtual String get_caption() const override;
- virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
+ virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual bool has_filter() const override;
AnimationNodeBlend2();
@@ -242,7 +268,7 @@ public:
virtual String get_caption() const override;
- virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
+ virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeBlend3();
};
@@ -261,7 +287,7 @@ public:
virtual String get_caption() const override;
virtual bool has_filter() const override;
- virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
+ virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeSub2();
};
@@ -280,7 +306,7 @@ public:
virtual String get_caption() const override;
- virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
+ virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeTimeScale();
};
@@ -299,7 +325,7 @@ public:
virtual String get_caption() const override;
- virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
+ virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeTimeSeek();
};
@@ -309,11 +335,11 @@ class AnimationNodeTransition : public AnimationNodeSync {
struct InputData {
bool auto_advance = false;
+ bool break_loop_at_end = false;
bool reset = true;
};
Vector<InputData> input_data;
- StringName time = "time";
StringName prev_xfading = "prev_xfading";
StringName prev_index = "prev_index";
StringName current_index = PNAME("current_index");
@@ -351,6 +377,9 @@ public:
void set_input_as_auto_advance(int p_input, bool p_enable);
bool is_input_set_as_auto_advance(int p_input) const;
+ void set_input_break_loop_at_end(int p_input, bool p_enable);
+ bool is_input_loop_broken_at_end(int p_input) const;
+
void set_input_reset(int p_input, bool p_enable);
bool is_input_reset(int p_input) const;
@@ -363,7 +392,7 @@ public:
void set_allow_transition_to_self(bool p_enable);
bool is_allow_transition_to_self() const;
- virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
+ virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeTransition();
};
@@ -373,7 +402,7 @@ class AnimationNodeOutput : public AnimationNode {
public:
virtual String get_caption() const override;
- virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
+ virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeOutput();
};
@@ -445,7 +474,7 @@ public:
void get_node_connections(List<NodeConnection> *r_connections) const;
virtual String get_caption() const override;
- virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
+ virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
void get_node_list(List<StringName> *r_list);
diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp
index ec44641484..0484694555 100644
--- a/scene/animation/animation_node_state_machine.cpp
+++ b/scene/animation/animation_node_state_machine.cpp
@@ -101,12 +101,22 @@ float AnimationNodeStateMachineTransition::get_xfade_time() const {
void AnimationNodeStateMachineTransition::set_xfade_curve(const Ref<Curve> &p_curve) {
xfade_curve = p_curve;
+ emit_changed();
}
Ref<Curve> AnimationNodeStateMachineTransition::get_xfade_curve() const {
return xfade_curve;
}
+void AnimationNodeStateMachineTransition::set_break_loop_at_end(bool p_enable) {
+ break_loop_at_end = p_enable;
+ emit_changed();
+}
+
+bool AnimationNodeStateMachineTransition::is_loop_broken_at_end() const {
+ return break_loop_at_end;
+}
+
void AnimationNodeStateMachineTransition::set_reset(bool p_reset) {
reset = p_reset;
emit_changed();
@@ -141,6 +151,9 @@ void AnimationNodeStateMachineTransition::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_xfade_curve", "curve"), &AnimationNodeStateMachineTransition::set_xfade_curve);
ClassDB::bind_method(D_METHOD("get_xfade_curve"), &AnimationNodeStateMachineTransition::get_xfade_curve);
+ ClassDB::bind_method(D_METHOD("set_break_loop_at_end", "enable"), &AnimationNodeStateMachineTransition::set_break_loop_at_end);
+ ClassDB::bind_method(D_METHOD("is_loop_broken_at_end"), &AnimationNodeStateMachineTransition::is_loop_broken_at_end);
+
ClassDB::bind_method(D_METHOD("set_reset", "reset"), &AnimationNodeStateMachineTransition::set_reset);
ClassDB::bind_method(D_METHOD("is_reset"), &AnimationNodeStateMachineTransition::is_reset);
@@ -153,6 +166,7 @@ void AnimationNodeStateMachineTransition::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "xfade_time", PROPERTY_HINT_RANGE, "0,240,0.01,suffix:s"), "set_xfade_time", "get_xfade_time");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "xfade_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_xfade_curve", "get_xfade_curve");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "break_loop_at_end"), "set_break_loop_at_end", "is_loop_broken_at_end");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset"), "set_reset", "is_reset");
ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority");
@@ -310,19 +324,19 @@ TypedArray<StringName> AnimationNodeStateMachinePlayback::_get_travel_path() con
}
float AnimationNodeStateMachinePlayback::get_current_play_pos() const {
- return pos_current;
+ return current_nti.position;
}
float AnimationNodeStateMachinePlayback::get_current_length() const {
- return len_current;
+ return current_nti.length;
}
float AnimationNodeStateMachinePlayback::get_fade_from_play_pos() const {
- return pos_fade_from;
+ return fadeing_from_nti.position;
}
float AnimationNodeStateMachinePlayback::get_fade_from_length() const {
- return len_fade_from;
+ return fadeing_from_nti.length;
}
float AnimationNodeStateMachinePlayback::get_fading_time() const {
@@ -665,21 +679,22 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree,
}
}
-double AnimationNodeStateMachinePlayback::process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
- double rem = _process(p_base_path, p_state_machine, p_playback_info, p_test_only);
+AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+ AnimationNode::NodeTimeInfo nti = _process(p_base_path, p_state_machine, p_playback_info, p_test_only);
start_request = StringName();
next_request = false;
stop_request = false;
reset_request_on_teleport = false;
- return rem;
+ return nti;
}
-double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
_set_base_path(p_base_path);
AnimationTree *tree = p_state_machine->process_state->tree;
double p_time = p_playback_info.time;
+ double p_delta = p_playback_info.delta;
bool p_seek = p_playback_info.seeked;
bool p_is_external_seeking = p_playback_info.is_external_seeking;
@@ -690,8 +705,8 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
if (p_state_machine->get_state_machine_type() != AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) {
path.clear();
_clear_path_children(tree, p_state_machine, p_test_only);
- _start(p_state_machine);
}
+ _start(p_state_machine);
reset_request = true;
} else {
// Reset current state.
@@ -705,11 +720,11 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
travel_request = StringName();
path.clear();
playing = false;
- return 0;
+ return AnimationNode::NodeTimeInfo();
}
if (!playing && start_request != StringName() && travel_request != StringName()) {
- return 0;
+ return AnimationNode::NodeTimeInfo();
}
// Process start/travel request.
@@ -732,7 +747,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
_start(p_state_machine);
} else {
StringName node = start_request;
- ERR_FAIL_V_MSG(0, "No such node: '" + node + "'");
+ ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + node + "'");
}
}
@@ -766,7 +781,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
teleport_request = true;
}
} else {
- ERR_FAIL_V_MSG(0, "No such node: '" + temp_travel_request + "'");
+ ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + temp_travel_request + "'");
}
}
}
@@ -777,16 +792,14 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
teleport_request = false;
// Clear fadeing on teleport.
fading_from = StringName();
+ fadeing_from_nti = AnimationNode::NodeTimeInfo();
fading_pos = 0;
// Init current length.
- pos_current = 0; // Overwritten suddenly in main process.
-
pi.time = 0;
pi.seeked = true;
pi.is_external_seeking = false;
pi.weight = 0;
-
- len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true);
+ current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true);
// Don't process first node if not necessary, insteads process next node.
_transition_to_next_recursive(tree, p_state_machine, p_test_only);
}
@@ -795,7 +808,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
if (!p_state_machine->states.has(current)) {
playing = false; // Current does not exist.
_set_current(p_state_machine, StringName());
- return 0;
+ return AnimationNode::NodeTimeInfo();
}
// Special case for grouped state machine Start/End to make priority with parent blend (means don't treat Start and End states as RESET animations).
@@ -813,7 +826,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
fading_from = StringName();
} else {
if (!p_seek) {
- fading_pos += p_time;
+ fading_pos += Math::abs(p_delta);
}
fade_blend = MIN(1.0, fading_pos / fading_time);
}
@@ -829,18 +842,14 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
}
// Main process.
- double rem = 0.0;
pi = p_playback_info;
pi.weight = fade_blend;
if (reset_request) {
reset_request = false;
pi.time = 0;
pi.seeked = true;
- len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only);
- rem = len_current;
- } else {
- rem = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
}
+ current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
// Cross-fade process.
if (fading_from != StringName()) {
@@ -852,7 +861,6 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
fade_blend_inv = 1.0;
}
- float fading_from_rem = 0.0;
pi = p_playback_info;
pi.weight = fade_blend_inv;
if (_reset_request_for_fading_from) {
@@ -860,57 +868,41 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
pi.time = 0;
pi.seeked = true;
}
- fading_from_rem = p_state_machine->blend_node(p_state_machine->states[fading_from].node, fading_from, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
-
- // Guess playback position.
- if (fading_from_rem > len_fade_from) { /// Weird but ok.
- len_fade_from = fading_from_rem;
- }
- pos_fade_from = len_fade_from - fading_from_rem;
+ fadeing_from_nti = p_state_machine->blend_node(p_state_machine->states[fading_from].node, fading_from, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
if (fading_pos >= fading_time) {
- fading_from = StringName(); // Finish fading.
+ // Finish fading.
+ fading_from = StringName();
+ fadeing_from_nti = AnimationNode::NodeTimeInfo();
}
}
- // Guess playback position.
- if (rem > len_current) { // Weird but ok.
- len_current = rem;
- }
- pos_current = len_current - rem;
-
// Find next and see when to transition.
_transition_to_next_recursive(tree, p_state_machine, p_test_only);
// Predict remaining time.
- double remain = rem; // If we can't predict the end of state machine, the time remaining must be INFINITY.
-
if (p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_NESTED) {
// There is no next transition.
if (!p_state_machine->has_transition_from(current)) {
if (fading_from != StringName()) {
- remain = MAX(rem, fading_time - fading_pos);
- } else {
- remain = rem;
+ return current_nti.get_remain() > fadeing_from_nti.get_remain() ? current_nti : fadeing_from_nti;
}
- return remain;
+ return current_nti;
}
}
if (current == p_state_machine->end_node) {
- if (fading_from != StringName()) {
- remain = MAX(0, fading_time - fading_pos);
- } else {
- remain = 0;
+ if (fading_from != StringName() && fadeing_from_nti.get_remain() > 0) {
+ return fadeing_from_nti;
}
- return remain;
+ return AnimationNode::NodeTimeInfo();
}
if (!is_end()) {
- return HUGE_LENGTH;
+ current_nti.is_infinity = true;
}
- return remain;
+ return current_nti;
}
bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only) {
@@ -952,6 +944,7 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT
p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only);
}
fading_from = StringName();
+ fadeing_from_nti = AnimationNode::NodeTimeInfo();
fading_time = 0;
fading_pos = 0;
}
@@ -968,11 +961,10 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT
_reset_request_for_fading_from = reset_request; // To avoid processing doubly, it must be reset in the fading process within _process().
reset_request = next.is_reset;
- pos_fade_from = pos_current;
- len_fade_from = len_current;
+ fadeing_from_nti = current_nti;
if (next.switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) {
- pi.time = MIN(pos_current, len_current);
+ pi.time = current_nti.position;
pi.seeked = true;
pi.is_external_seeking = false;
pi.weight = 0;
@@ -980,24 +972,11 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT
}
// Just get length to find next recursive.
- double rem = 0.0;
pi.time = 0;
pi.is_external_seeking = false;
pi.weight = 0;
- if (next.is_reset) {
- pi.seeked = true;
- len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process.
- rem = len_current;
- } else {
- pi.seeked = false;
- rem = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process.
- }
-
- // Guess playback position.
- if (rem > len_current) { // Weird but ok.
- len_current = rem;
- }
- pos_current = len_current - rem;
+ pi.seeked = next.is_reset;
+ current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process.
// Fading must be processed.
if (fading_time) {
@@ -1028,6 +1007,7 @@ bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationTree *p
playback->_next_main();
// Then, fadeing should be end.
fading_from = StringName();
+ fadeing_from_nti = AnimationNode::NodeTimeInfo();
fading_pos = 0;
} else {
return true;
@@ -1039,7 +1019,7 @@ bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationTree *p
}
if (current != p_state_machine->start_node && p_next.switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_AT_END) {
- return pos_current >= len_current - p_next.xfade;
+ return current_nti.get_remain(p_next.break_loop_at_end) <= p_next.xfade;
}
return true;
}
@@ -1084,6 +1064,7 @@ AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_
next.curve = ref_transition->get_xfade_curve();
next.switch_mode = ref_transition->get_switch_mode();
next.is_reset = ref_transition->is_reset();
+ next.break_loop_at_end = ref_transition->is_loop_broken_at_end();
}
}
} else {
@@ -1113,6 +1094,7 @@ AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_
next.curve = ref_transition->get_xfade_curve();
next.switch_mode = ref_transition->get_switch_mode();
next.is_reset = ref_transition->is_reset();
+ next.break_loop_at_end = ref_transition->is_loop_broken_at_end();
}
}
@@ -1233,6 +1215,7 @@ AnimationNodeStateMachinePlayback::AnimationNodeStateMachinePlayback() {
///////////////////////////////////////////////////////
void AnimationNodeStateMachine::get_parameter_list(List<PropertyInfo> *r_list) const {
+ AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::OBJECT, playback, PROPERTY_HINT_RESOURCE_TYPE, "AnimationNodeStateMachinePlayback", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ALWAYS_DUPLICATE)); // Don't store this object in .tres, it always needs to be made as unique object.
List<StringName> advance_conditions;
for (int i = 0; i < transitions.size(); i++) {
@@ -1249,6 +1232,11 @@ void AnimationNodeStateMachine::get_parameter_list(List<PropertyInfo> *r_list) c
}
Variant AnimationNodeStateMachine::get_parameter_default_value(const StringName &p_parameter) const {
+ Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
+ if (ret != Variant()) {
+ return ret;
+ }
+
if (p_parameter == playback) {
Ref<AnimationNodeStateMachinePlayback> p;
p.instantiate();
@@ -1259,6 +1247,10 @@ Variant AnimationNodeStateMachine::get_parameter_default_value(const StringName
}
bool AnimationNodeStateMachine::is_parameter_read_only(const StringName &p_parameter) const {
+ if (AnimationNode::is_parameter_read_only(p_parameter)) {
+ return true;
+ }
+
if (p_parameter == playback) {
return true;
}
@@ -1622,9 +1614,9 @@ Vector2 AnimationNodeStateMachine::get_graph_offset() const {
return graph_offset;
}
-double AnimationNodeStateMachine::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNodeStateMachine::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
Ref<AnimationNodeStateMachinePlayback> playback_new = get_parameter(playback);
- ERR_FAIL_COND_V(playback_new.is_null(), 0.0);
+ ERR_FAIL_COND_V(playback_new.is_null(), AnimationNode::NodeTimeInfo());
playback_new->_set_grouped(state_machine_type == STATE_MACHINE_TYPE_GROUPED);
if (p_test_only) {
playback_new = playback_new->duplicate(); // Don't process original when testing.
diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h
index fa0eca5877..8078ffb26d 100644
--- a/scene/animation/animation_node_state_machine.h
+++ b/scene/animation/animation_node_state_machine.h
@@ -57,6 +57,7 @@ private:
StringName advance_condition_name;
float xfade_time = 0.0;
Ref<Curve> xfade_curve;
+ bool break_loop_at_end = false;
bool reset = true;
int priority = 1;
String advance_expression;
@@ -85,6 +86,9 @@ public:
void set_xfade_time(float p_xfade);
float get_xfade_time() const;
+ void set_break_loop_at_end(bool p_enable);
+ bool is_loop_broken_at_end() const;
+
void set_reset(bool p_reset);
bool is_reset() const;
@@ -210,7 +214,7 @@ public:
void set_graph_offset(const Vector2 &p_offset);
Vector2 get_graph_offset() const;
- virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
+ virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual String get_caption() const override;
virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override;
@@ -246,6 +250,7 @@ class AnimationNodeStateMachinePlayback : public Resource {
Ref<Curve> curve;
AnimationNodeStateMachineTransition::SwitchMode switch_mode;
bool is_reset;
+ bool break_loop_at_end;
};
struct ChildStateMachineInfo {
@@ -257,18 +262,14 @@ class AnimationNodeStateMachinePlayback : public Resource {
Ref<AnimationNodeStateMachineTransition> default_transition;
String base_path;
- double len_fade_from = 0.0;
- double pos_fade_from = 0.0;
-
- double len_current = 0.0;
- double pos_current = 0.0;
-
+ AnimationNode::NodeTimeInfo current_nti;
StringName current;
Ref<Curve> current_curve;
Ref<AnimationNodeStateMachineTransition> group_start_transition;
Ref<AnimationNodeStateMachineTransition> group_end_transition;
+ AnimationNode::NodeTimeInfo fadeing_from_nti;
StringName fading_from;
float fading_time = 0.0;
float fading_pos = 0.0;
@@ -301,8 +302,8 @@ class AnimationNodeStateMachinePlayback : public Resource {
bool _travel_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_is_allow_transition_to_self, bool p_is_parent_same_state, bool p_test_only);
void _start_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_test_only);
- double process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only);
- double _process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only);
+ AnimationNode::NodeTimeInfo process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only);
+ AnimationNode::NodeTimeInfo _process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only);
bool _check_advance_condition(const Ref<AnimationNodeStateMachine> p_state_machine, const Ref<AnimationNodeStateMachineTransition> p_transition) const;
bool _transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only);
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp
index 2c2d8387f3..2d0bd9c258 100644
--- a/scene/animation/animation_tree.cpp
+++ b/scene/animation/animation_tree.cpp
@@ -46,6 +46,10 @@ void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const {
r_list->push_back(PropertyInfo::from_dict(d));
}
}
+
+ r_list->push_back(PropertyInfo(Variant::FLOAT, current_length, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_READ_ONLY));
+ r_list->push_back(PropertyInfo(Variant::FLOAT, current_position, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_READ_ONLY));
+ r_list->push_back(PropertyInfo(Variant::FLOAT, current_delta, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_READ_ONLY));
}
Variant AnimationNode::get_parameter_default_value(const StringName &p_parameter) const {
@@ -56,8 +60,15 @@ Variant AnimationNode::get_parameter_default_value(const StringName &p_parameter
bool AnimationNode::is_parameter_read_only(const StringName &p_parameter) const {
bool ret = false;
- GDVIRTUAL_CALL(_is_parameter_read_only, p_parameter, ret);
- return ret;
+ if (GDVIRTUAL_CALL(_is_parameter_read_only, p_parameter, ret) && ret) {
+ return true;
+ }
+
+ if (p_parameter == current_length || p_parameter == current_position || p_parameter == current_delta) {
+ return true;
+ }
+
+ return false;
}
void AnimationNode::set_parameter(const StringName &p_name, const Variant &p_value) {
@@ -81,6 +92,20 @@ Variant AnimationNode::get_parameter(const StringName &p_name) const {
return process_state->tree->property_map[path].first;
}
+void AnimationNode::set_node_time_info(const NodeTimeInfo &p_node_time_info) {
+ set_parameter(current_length, p_node_time_info.length);
+ set_parameter(current_position, p_node_time_info.position);
+ set_parameter(current_delta, p_node_time_info.delta);
+}
+
+AnimationNode::NodeTimeInfo AnimationNode::get_node_time_info() const {
+ NodeTimeInfo nti;
+ nti.length = get_parameter(current_length);
+ nti.position = get_parameter(current_position);
+ nti.delta = get_parameter(current_delta);
+ return nti;
+}
+
void AnimationNode::get_child_nodes(List<ChildNode> *r_child_nodes) {
Dictionary cn;
if (GDVIRTUAL_CALL(_get_child_nodes, cn)) {
@@ -101,11 +126,11 @@ void AnimationNode::blend_animation(const StringName &p_animation, AnimationMixe
process_state->tree->make_animation_instance(p_animation, p_playback_info);
}
-double AnimationNode::_pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNode::_pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
process_state = p_process_state;
- double t = process(p_playback_info, p_test_only);
+ NodeTimeInfo nti = process(p_playback_info, p_test_only);
process_state = nullptr;
- return t;
+ return nti;
}
void AnimationNode::make_invalid(const String &p_reason) {
@@ -122,11 +147,11 @@ AnimationTree *AnimationNode::get_animation_tree() const {
return process_state->tree;
}
-double AnimationNode::blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) {
- ERR_FAIL_INDEX_V(p_input, inputs.size(), 0);
+AnimationNode::NodeTimeInfo AnimationNode::blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) {
+ ERR_FAIL_INDEX_V(p_input, inputs.size(), NodeTimeInfo());
AnimationNodeBlendTree *blend_tree = Object::cast_to<AnimationNodeBlendTree>(node_state.parent);
- ERR_FAIL_NULL_V(blend_tree, 0);
+ ERR_FAIL_NULL_V(blend_tree, NodeTimeInfo());
// Update connections.
StringName current_name = blend_tree->get_node_name(Ref<AnimationNode>(this));
@@ -136,32 +161,31 @@ double AnimationNode::blend_input(int p_input, AnimationMixer::PlaybackInfo p_pl
StringName node_name = node_state.connections[p_input];
if (!blend_tree->has_node(node_name)) {
make_invalid(vformat(RTR("Nothing connected to input '%s' of node '%s'."), get_input_name(p_input), current_name));
- return 0;
+ return NodeTimeInfo();
}
Ref<AnimationNode> node = blend_tree->get_node(node_name);
- ERR_FAIL_COND_V(node.is_null(), 0);
+ ERR_FAIL_COND_V(node.is_null(), NodeTimeInfo());
real_t activity = 0.0;
Vector<AnimationTree::Activity> *activity_ptr = process_state->tree->input_activity_map.getptr(node_state.base_path);
- double ret = _blend_node(node, node_name, nullptr, p_playback_info, p_filter, p_sync, p_test_only, &activity);
+ NodeTimeInfo nti = _blend_node(node, node_name, nullptr, p_playback_info, p_filter, p_sync, p_test_only, &activity);
if (activity_ptr && p_input < activity_ptr->size()) {
activity_ptr->write[p_input].last_pass = process_state->last_pass;
activity_ptr->write[p_input].activity = activity;
}
- return ret;
+ return nti;
}
-double AnimationNode::blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) {
- ERR_FAIL_COND_V(p_node.is_null(), 0);
-
+AnimationNode::NodeTimeInfo AnimationNode::blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) {
+ ERR_FAIL_COND_V(p_node.is_null(), NodeTimeInfo());
p_node->node_state.connections.clear();
return _blend_node(p_node, p_subpath, this, p_playback_info, p_filter, p_sync, p_test_only, nullptr);
}
-double AnimationNode::_blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only, real_t *r_activity) {
- ERR_FAIL_NULL_V(process_state, 0);
+AnimationNode::NodeTimeInfo AnimationNode::_blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only, real_t *r_activity) {
+ ERR_FAIL_NULL_V(process_state, NodeTimeInfo());
int blend_count = node_state.track_weights.size();
@@ -261,7 +285,7 @@ double AnimationNode::_blend_node(Ref<AnimationNode> p_node, const StringName &p
new_parent = p_new_parent;
new_path = String(node_state.base_path) + String(p_subpath) + "/";
} else {
- ERR_FAIL_NULL_V(node_state.parent, 0);
+ ERR_FAIL_NULL_V(node_state.parent, NodeTimeInfo());
new_parent = node_state.parent;
new_path = String(new_parent->node_state.base_path) + String(p_subpath) + "/";
}
@@ -271,7 +295,7 @@ double AnimationNode::_blend_node(Ref<AnimationNode> p_node, const StringName &p
p_node->node_state.base_path = new_path;
p_node->node_state.parent = new_parent;
if (!p_playback_info.seeked && !p_sync && !any_valid) {
- p_playback_info.time = 0.0;
+ p_playback_info.delta = 0.0;
return p_node->_pre_process(process_state, p_playback_info, p_test_only);
}
return p_node->_pre_process(process_state, p_playback_info, p_test_only);
@@ -328,15 +352,31 @@ int AnimationNode::find_input(const String &p_name) const {
return idx;
}
-double AnimationNode::process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+AnimationNode::NodeTimeInfo AnimationNode::process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
process_state->is_testing = p_test_only;
- return _process(p_playback_info, p_test_only);
+
+ AnimationMixer::PlaybackInfo pi = p_playback_info;
+ if (p_playback_info.seeked) {
+ pi.delta = get_node_time_info().position - p_playback_info.time;
+ } else {
+ pi.time = get_node_time_info().position + p_playback_info.delta;
+ }
+
+ NodeTimeInfo nti = _process(pi, p_test_only);
+
+ if (!p_test_only) {
+ set_node_time_info(nti);
+ }
+
+ return nti;
}
-double AnimationNode::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
- double ret = 0;
- GDVIRTUAL_CALL(_process, p_playback_info.time, p_playback_info.seeked, p_playback_info.is_external_seeking, p_test_only, ret);
- return ret;
+AnimationNode::NodeTimeInfo AnimationNode::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+ double r_ret = 0.0;
+ GDVIRTUAL_CALL(_process, p_playback_info.time, p_playback_info.seeked, p_playback_info.is_external_seeking, p_test_only, r_ret);
+ NodeTimeInfo nti;
+ nti.delta = r_ret;
+ return nti;
}
void AnimationNode::set_filter_path(const NodePath &p_path, bool p_enable) {
@@ -432,7 +472,8 @@ double AnimationNode::blend_node_ex(const StringName &p_sub_path, Ref<AnimationN
info.seeked = p_seek;
info.is_external_seeking = p_is_external_seeking;
info.weight = p_blend;
- return blend_node(p_node, p_sub_path, info, p_filter, p_sync, p_test_only);
+ NodeTimeInfo nti = blend_node(p_node, p_sub_path, info, p_filter, p_sync, p_test_only);
+ return nti.length - nti.position;
}
double AnimationNode::blend_input_ex(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, bool p_test_only) {
@@ -441,8 +482,37 @@ double AnimationNode::blend_input_ex(int p_input, double p_time, bool p_seek, bo
info.seeked = p_seek;
info.is_external_seeking = p_is_external_seeking;
info.weight = p_blend;
- return blend_input(p_input, info, p_filter, p_sync, p_test_only);
+ NodeTimeInfo nti = blend_input(p_input, info, p_filter, p_sync, p_test_only);
+ return nti.length - nti.position;
+}
+
+#ifdef TOOLS_ENABLED
+void AnimationNode::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
+ const String pf = p_function;
+ if (p_idx == 0) {
+ if (pf == "find_input") {
+ for (const AnimationNode::Input &E : inputs) {
+ r_options->push_back(E.name.quote());
+ }
+ } else if (pf == "get_parameter" || pf == "set_parameter") {
+ bool is_setter = pf == "set_parameter";
+ List<PropertyInfo> parameters;
+ get_parameter_list(&parameters);
+ for (const PropertyInfo &E : parameters) {
+ if (is_setter && is_parameter_read_only(E.name)) {
+ continue;
+ }
+ r_options->push_back(E.name.quote());
+ }
+ } else if (pf == "set_filter_path" || pf == "is_path_filtered") {
+ for (const KeyValue<NodePath, bool> &E : filter) {
+ r_options->push_back(String(E.key).quote());
+ }
+ }
+ }
+ Resource::get_argument_options(p_function, p_idx, r_options);
}
+#endif
void AnimationNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_input", "name"), &AnimationNode::add_input);
@@ -568,11 +638,12 @@ bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const
if (started) {
// If started, seek.
pi.seeked = true;
+ pi.delta = p_delta;
root_animation_node->_pre_process(&process_state, pi, false);
started = false;
} else {
pi.seeked = false;
- pi.time = p_delta;
+ pi.delta = p_delta;
root_animation_node->_pre_process(&process_state, pi, false);
}
}
diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h
index 87928e4d20..2a58822275 100644
--- a/scene/animation/animation_tree.h
+++ b/scene/animation/animation_tree.h
@@ -63,6 +63,34 @@ public:
HashMap<NodePath, bool> filter;
bool filter_enabled = false;
+ // To propagate information from upstream for use in estimation of playback progress.
+ // These values must be taken from the result of blend_node() or blend_input() and must be essentially read-only.
+ // For example, if you want to change the position, you need to change the pi.time value of PlaybackInfo passed to blend_input(pi) and get the result.
+ struct NodeTimeInfo {
+ // Retain the previous frame values. These are stored into the AnimationTree's Map and exposing them as read-only values.
+ double length = 0.0;
+ double position = 0.0;
+ double delta = 0.0;
+
+ // Needs internally to estimate remain time, the previous frame values are not retained.
+ Animation::LoopMode loop_mode = Animation::LOOP_NONE;
+ bool is_just_looped = false; // For breaking loop, it is true when just looped.
+ bool is_infinity = false; // For unpredictable state machine's end.
+
+ bool is_looping() {
+ return loop_mode != Animation::LOOP_NONE;
+ }
+ double get_remain(bool p_break_loop = false) {
+ if ((is_looping() && !p_break_loop) || is_infinity) {
+ return HUGE_LENGTH;
+ }
+ if (p_break_loop && is_just_looped) {
+ return 0;
+ }
+ return length - position;
+ }
+ };
+
// Temporary state for blending process which needs to be stored in each AnimationNodes.
struct NodeState {
StringName base_path;
@@ -84,16 +112,23 @@ public:
Array _get_filters() const;
void _set_filters(const Array &p_filters);
friend class AnimationNodeBlendTree;
- double _blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false, real_t *r_activity = nullptr);
- double _pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false);
+
+ // The time information is passed from upstream to downstream by AnimationMixer::PlaybackInfo::p_playback_info until AnimationNodeAnimation processes it.
+ // Conversely, AnimationNodeAnimation returns the processed result as NodeTimeInfo from downstream to upstream.
+ NodeTimeInfo _blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false, real_t *r_activity = nullptr);
+ NodeTimeInfo _pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false);
protected:
- virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false);
- double process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false);
+ StringName current_length = "current_length";
+ StringName current_position = "current_position";
+ StringName current_delta = "current_delta";
+
+ virtual NodeTimeInfo process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); // To organize time information. Virtualizing for especially AnimationNodeAnimation needs to take "backward" into account.
+ virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); // Main process.
void blend_animation(const StringName &p_animation, AnimationMixer::PlaybackInfo p_playback_info);
- double blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
- double blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
+ NodeTimeInfo blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
+ NodeTimeInfo blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
// Bind-able methods to expose for compatibility, moreover AnimationMixer::PlaybackInfo is not exposed.
void blend_animation_ex(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE);
@@ -124,6 +159,9 @@ public:
void set_parameter(const StringName &p_name, const Variant &p_value);
Variant get_parameter(const StringName &p_name) const;
+ void set_node_time_info(const NodeTimeInfo &p_node_time_info); // Wrapper of set_parameter().
+ virtual NodeTimeInfo get_node_time_info() const; // Wrapper of get_parameter().
+
struct ChildNode {
StringName name;
Ref<AnimationNode> node;
@@ -151,6 +189,10 @@ public:
virtual bool has_filter() const;
+#ifdef TOOLS_ENABLED
+ virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
+#endif
+
virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) const;
Ref<AnimationNode> find_node_by_path(const String &p_name) const;
diff --git a/scene/gui/aspect_ratio_container.cpp b/scene/gui/aspect_ratio_container.cpp
index 94240ccead..711dd13781 100644
--- a/scene/gui/aspect_ratio_container.cpp
+++ b/scene/gui/aspect_ratio_container.cpp
@@ -46,8 +46,7 @@ Size2 AspectRatioContainer::get_minimum_size() const {
continue;
}
Size2 minsize = c->get_combined_minimum_size();
- ms.width = MAX(ms.width, minsize.width);
- ms.height = MAX(ms.height, minsize.height);
+ ms = ms.max(minsize);
}
return ms;
}
@@ -144,8 +143,7 @@ void AspectRatioContainer::_notification(int p_what) {
} break;
}
child_size *= scale_factor;
- child_size.x = MAX(child_size.x, child_minsize.x);
- child_size.y = MAX(child_size.y, child_minsize.y);
+ child_size = child_size.max(child_minsize);
float align_x = 0.5;
switch (alignment_horizontal) {
diff --git a/scene/gui/center_container.cpp b/scene/gui/center_container.cpp
index 7a860cdea7..acdeae289b 100644
--- a/scene/gui/center_container.cpp
+++ b/scene/gui/center_container.cpp
@@ -47,8 +47,7 @@ Size2 CenterContainer::get_minimum_size() const {
continue;
}
Size2 minsize = c->get_combined_minimum_size();
- ms.width = MAX(ms.width, minsize.width);
- ms.height = MAX(ms.height, minsize.height);
+ ms = ms.max(minsize);
}
return ms;
diff --git a/scene/gui/check_box.cpp b/scene/gui/check_box.cpp
index 46c20e4a1c..af6696834e 100644
--- a/scene/gui/check_box.cpp
+++ b/scene/gui/check_box.cpp
@@ -36,28 +36,28 @@
Size2 CheckBox::get_icon_size() const {
Size2 tex_size = Size2(0, 0);
if (!theme_cache.checked.is_null()) {
- tex_size = Size2(theme_cache.checked->get_width(), theme_cache.checked->get_height());
+ tex_size = theme_cache.checked->get_size();
}
if (!theme_cache.unchecked.is_null()) {
- tex_size = Size2(MAX(tex_size.width, theme_cache.unchecked->get_width()), MAX(tex_size.height, theme_cache.unchecked->get_height()));
+ tex_size = tex_size.max(theme_cache.unchecked->get_size());
}
if (!theme_cache.radio_checked.is_null()) {
- tex_size = Size2(MAX(tex_size.width, theme_cache.radio_checked->get_width()), MAX(tex_size.height, theme_cache.radio_checked->get_height()));
+ tex_size = tex_size.max(theme_cache.radio_checked->get_size());
}
if (!theme_cache.radio_unchecked.is_null()) {
- tex_size = Size2(MAX(tex_size.width, theme_cache.radio_unchecked->get_width()), MAX(tex_size.height, theme_cache.radio_unchecked->get_height()));
+ tex_size = tex_size.max(theme_cache.radio_unchecked->get_size());
}
if (!theme_cache.checked_disabled.is_null()) {
- tex_size = Size2(MAX(tex_size.width, theme_cache.checked_disabled->get_width()), MAX(tex_size.height, theme_cache.checked_disabled->get_height()));
+ tex_size = tex_size.max(theme_cache.checked_disabled->get_size());
}
if (!theme_cache.unchecked_disabled.is_null()) {
- tex_size = Size2(MAX(tex_size.width, theme_cache.unchecked_disabled->get_width()), MAX(tex_size.height, theme_cache.unchecked_disabled->get_height()));
+ tex_size = tex_size.max(theme_cache.unchecked_disabled->get_size());
}
if (!theme_cache.radio_checked_disabled.is_null()) {
- tex_size = Size2(MAX(tex_size.width, theme_cache.radio_checked_disabled->get_width()), MAX(tex_size.height, theme_cache.radio_checked_disabled->get_height()));
+ tex_size = tex_size.max(theme_cache.radio_checked_disabled->get_size());
}
if (!theme_cache.radio_unchecked_disabled.is_null()) {
- tex_size = Size2(MAX(tex_size.width, theme_cache.radio_unchecked_disabled->get_width()), MAX(tex_size.height, theme_cache.radio_unchecked_disabled->get_height()));
+ tex_size = tex_size.max(theme_cache.radio_unchecked_disabled->get_size());
}
return tex_size;
}
diff --git a/scene/gui/check_button.cpp b/scene/gui/check_button.cpp
index ca2ef220fd..ab3b74a3c3 100644
--- a/scene/gui/check_button.cpp
+++ b/scene/gui/check_button.cpp
@@ -57,10 +57,10 @@ Size2 CheckButton::get_icon_size() const {
Size2 tex_size = Size2(0, 0);
if (!on_tex.is_null()) {
- tex_size = Size2(on_tex->get_width(), on_tex->get_height());
+ tex_size = on_tex->get_size();
}
if (!off_tex.is_null()) {
- tex_size = Size2(MAX(tex_size.width, off_tex->get_width()), MAX(tex_size.height, off_tex->get_height()));
+ tex_size = tex_size.max(off_tex->get_size());
}
return tex_size;
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index f7eae2b772..4f90504e35 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -733,14 +733,15 @@ void CodeEdit::_backspace_internal(int p_caret) {
prev_column = cc - auto_brace_completion_pairs[idx].open_key.length();
if (_get_auto_brace_pair_close_at_pos(cl, cc) == idx) {
- remove_text(prev_line, prev_column, cl, cc + auto_brace_completion_pairs[idx].close_key.length());
- } else {
- remove_text(prev_line, prev_column, cl, cc);
+ cc += auto_brace_completion_pairs[idx].close_key.length();
}
+
+ remove_text(prev_line, prev_column, cl, cc);
+
set_caret_line(prev_line, false, true, 0, i);
set_caret_column(prev_column, i == 0, i);
- adjust_carets_after_edit(i, prev_line, prev_column, cl, cc + auto_brace_completion_pairs[idx].close_key.length());
+ adjust_carets_after_edit(i, prev_line, prev_column, cl, cc);
continue;
}
}
@@ -3397,9 +3398,16 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
int offset = option.default_value.get_type() == Variant::COLOR ? line_height : 0;
if (in_string != -1) {
+ // The completion string may have a literal behind it, which should be removed before re-quoting.
+ String literal;
+ if (option.insert_text.substr(1).is_quoted()) {
+ literal = option.display.left(1);
+ option.display = option.display.substr(1);
+ option.insert_text = option.insert_text.substr(1);
+ }
String quote = single_quote ? "'" : "\"";
- option.display = option.display.unquote().quote(quote);
- option.insert_text = option.insert_text.unquote().quote(quote);
+ option.display = literal + (option.display.unquote().quote(quote));
+ option.insert_text = literal + (option.insert_text.unquote().quote(quote));
}
if (option.display.length() == 0) {
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 974ecd1edc..62bb14459d 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -1406,15 +1406,11 @@ void Control::_set_global_position(const Point2 &p_point) {
void Control::set_global_position(const Point2 &p_point, bool p_keep_offsets) {
ERR_MAIN_THREAD_GUARD;
-
- Transform2D global_transform_cache = get_global_transform();
- if (p_point == global_transform_cache.get_origin()) {
- return; // Edge case, but avoids calculation.
- }
-
- Point2 internal_position = global_transform_cache.affine_inverse().xform(p_point);
-
- set_position(internal_position + data.pos_cache, p_keep_offsets);
+ // (parent_global_transform * T(new_position) * internal_transform).origin == new_global_position
+ // (T(new_position) * internal_transform).origin == new_position_in_parent_space
+ // new_position == new_position_in_parent_space - internal_transform.origin
+ Point2 position_in_parent_space = data.parent_canvas_item ? data.parent_canvas_item->get_global_transform().affine_inverse().xform(p_point) : p_point;
+ set_position(position_in_parent_space - _get_internal_transform().get_origin(), p_keep_offsets);
}
Point2 Control::get_global_position() const {
@@ -1657,8 +1653,7 @@ Size2 Control::get_custom_minimum_size() const {
void Control::_update_minimum_size_cache() {
Size2 minsize = get_minimum_size();
- minsize.x = MAX(minsize.x, data.custom_minimum_size.x);
- minsize.y = MAX(minsize.y, data.custom_minimum_size.y);
+ minsize = minsize.max(data.custom_minimum_size);
data.minimum_size_cache = minsize;
data.minimum_size_valid = true;
@@ -3694,6 +3689,8 @@ void Control::_bind_methods() {
Control::Control() {
data.theme_owner = memnew(ThemeOwner(this));
+
+ set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF);
}
Control::~Control() {
diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp
index aa67f0d200..c13a2e281a 100644
--- a/scene/gui/dialogs.cpp
+++ b/scene/gui/dialogs.cpp
@@ -252,8 +252,7 @@ Size2 AcceptDialog::_get_contents_minimum_size() const {
}
Size2 child_minsize = c->get_combined_minimum_size();
- content_minsize.x = MAX(child_minsize.x, content_minsize.x);
- content_minsize.y = MAX(child_minsize.y, content_minsize.y);
+ content_minsize = child_minsize.max(content_minsize);
}
// Then we take the background panel as it provides the offsets,
@@ -317,7 +316,7 @@ Button *AcceptDialog::add_button(const String &p_text, bool p_right, const Strin
Button *AcceptDialog::add_cancel_button(const String &p_cancel) {
String c = p_cancel;
if (p_cancel.is_empty()) {
- c = "Cancel";
+ c = ETR("Cancel");
}
Button *b = swap_cancel_ok ? add_button(c, true) : add_button(c);
@@ -419,13 +418,13 @@ AcceptDialog::AcceptDialog() {
buttons_hbox->add_spacer();
ok_button = memnew(Button);
- ok_button->set_text("OK");
+ ok_button->set_text(ETR("OK"));
buttons_hbox->add_child(ok_button);
buttons_hbox->add_spacer();
ok_button->connect("pressed", callable_mp(this, &AcceptDialog::_ok_pressed));
- set_title(TTRC("Alert!"));
+ set_title(ETR("Alert!"));
}
AcceptDialog::~AcceptDialog() {
@@ -454,7 +453,7 @@ Button *ConfirmationDialog::get_cancel_button() {
}
ConfirmationDialog::ConfirmationDialog() {
- set_title(TTRC("Please Confirm..."));
+ set_title(ETR("Please Confirm..."));
set_min_size(Size2(200, 70));
cancel = add_cancel_button();
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index a53dffd0d2..40ac87160c 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -671,8 +671,8 @@ void FileDialog::update_file_list() {
item = dir_access->get_next();
}
- dirs.sort_custom<NaturalNoCaseComparator>();
- files.sort_custom<NaturalNoCaseComparator>();
+ dirs.sort_custom<FileNoCaseComparator>();
+ files.sort_custom<FileNoCaseComparator>();
while (!dirs.is_empty()) {
String &dir_name = dirs.front()->get();
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 056872a4fe..8bce5c0caa 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -181,8 +181,7 @@ void GraphEditMinimap::gui_input(const Ref<InputEvent> &p_ev) {
if (is_resizing) {
// Prevent setting minimap wider than GraphEdit.
Vector2 new_minimap_size;
- new_minimap_size.width = MIN(get_size().width - mm->get_relative().x, ge->get_size().width - 2.0 * minimap_padding.x);
- new_minimap_size.height = MIN(get_size().height - mm->get_relative().y, ge->get_size().height - 2.0 * minimap_padding.y);
+ new_minimap_size = (get_size() - mm->get_relative()).min(ge->get_size() - 2.0 * minimap_padding);
ge->set_minimap_size(new_minimap_size);
queue_redraw();
diff --git a/scene/gui/graph_element.cpp b/scene/gui/graph_element.cpp
index 7fa5b0ceec..9eb9578b2c 100644
--- a/scene/gui/graph_element.cpp
+++ b/scene/gui/graph_element.cpp
@@ -74,8 +74,7 @@ Size2 GraphElement::get_minimum_size() const {
Size2i size = child->get_combined_minimum_size();
- minsize.width = MAX(minsize.width, size.width);
- minsize.height = MAX(minsize.height, size.height);
+ minsize = minsize.max(size);
}
return minsize;
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
index 56da1332e7..42b4e56b48 100644
--- a/scene/gui/label.cpp
+++ b/scene/gui/label.cpp
@@ -316,7 +316,7 @@ inline void draw_glyph(const Glyph &p_gl, const RID &p_canvas, const Color &p_fo
}
}
-inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Color &p_font_shadow_color, const Color &p_font_outline_color, const int &p_shadow_outline_size, const int &p_outline_size, const Vector2 &p_ofs, const Vector2 &shadow_ofs) {
+inline void draw_glyph_shadow(const Glyph &p_gl, const RID &p_canvas, const Color &p_font_shadow_color, int p_shadow_outline_size, const Vector2 &p_ofs, const Vector2 &shadow_ofs) {
if (p_gl.font_rid != RID()) {
if (p_font_shadow_color.a > 0) {
TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color);
@@ -324,6 +324,11 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col
if (p_font_shadow_color.a > 0 && p_shadow_outline_size > 0) {
TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color);
}
+ }
+}
+
+inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Color &p_font_outline_color, int p_outline_size, const Vector2 &p_ofs) {
+ if (p_gl.font_rid != RID()) {
if (p_font_outline_color.a != 0.0 && p_outline_size > 0) {
TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_outline_color);
}
@@ -537,25 +542,36 @@ void Label::_notification(int p_what) {
const Glyph *ellipsis_glyphs = TS->shaped_text_get_ellipsis_glyphs(lines_rid[i]);
int ellipsis_gl_size = TS->shaped_text_get_ellipsis_glyph_count(lines_rid[i]);
- // Draw outline. Note: Do not merge this into the single loop with the main text, to prevent overlaps.
- int processed_glyphs_ol = processed_glyphs;
- if ((outline_size > 0 && font_outline_color.a != 0) || (font_shadow_color.a != 0)) {
- Vector2 offset = ofs;
+ // Draw shadow, outline and text. Note: Do not merge this into the single loop iteration, to prevent overlaps.
+ for (int step = DRAW_STEP_SHADOW; step < DRAW_STEP_MAX; step++) {
+ if (step == DRAW_STEP_SHADOW && (font_shadow_color.a == 0)) {
+ continue;
+ }
+ if (step == DRAW_STEP_OUTLINE && (outline_size <= 0 || font_outline_color.a == 0)) {
+ continue;
+ }
+
+ int processed_glyphs_step = processed_glyphs;
+ Vector2 offset_step = ofs;
// Draw RTL ellipsis string when necessary.
if (rtl && ellipsis_pos >= 0) {
for (int gl_idx = ellipsis_gl_size - 1; gl_idx >= 0; gl_idx--) {
for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) {
- bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs));
- //Draw glyph outlines and shadow.
+ bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs));
if (!skip) {
- draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs);
+ if (step == DRAW_STEP_SHADOW) {
+ draw_glyph_shadow(ellipsis_glyphs[gl_idx], ci, font_shadow_color, shadow_outline_size, offset_step, shadow_ofs);
+ } else if (step == DRAW_STEP_OUTLINE) {
+ draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_outline_color, outline_size, offset_step);
+ } else if (step == DRAW_STEP_TEXT) {
+ draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, offset_step);
+ }
}
- processed_glyphs_ol++;
- offset.x += ellipsis_glyphs[gl_idx].advance;
+ processed_glyphs_step++;
+ offset_step.x += ellipsis_glyphs[gl_idx].advance;
}
}
}
-
// Draw main text.
for (int j = 0; j < gl_size; j++) {
// Trim when necessary.
@@ -571,85 +587,37 @@ void Label::_notification(int p_what) {
}
}
for (int k = 0; k < glyphs[j].repeat; k++) {
- bool skip = (trim_chars && glyphs[j].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs));
-
- // Draw glyph outlines and shadow.
+ bool skip = (trim_chars && glyphs[j].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs));
if (!skip) {
- draw_glyph_outline(glyphs[j], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs);
+ if (step == DRAW_STEP_SHADOW) {
+ draw_glyph_shadow(glyphs[j], ci, font_shadow_color, shadow_outline_size, offset_step, shadow_ofs);
+ } else if (step == DRAW_STEP_OUTLINE) {
+ draw_glyph_outline(glyphs[j], ci, font_outline_color, outline_size, offset_step);
+ } else if (step == DRAW_STEP_TEXT) {
+ draw_glyph(glyphs[j], ci, font_color, offset_step);
+ }
}
- processed_glyphs_ol++;
- offset.x += glyphs[j].advance;
+ processed_glyphs_step++;
+ offset_step.x += glyphs[j].advance;
}
}
// Draw LTR ellipsis string when necessary.
if (!rtl && ellipsis_pos >= 0) {
for (int gl_idx = 0; gl_idx < ellipsis_gl_size; gl_idx++) {
for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) {
- bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs));
- //Draw glyph outlines and shadow.
+ bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs));
if (!skip) {
- draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs);
+ if (step == DRAW_STEP_SHADOW) {
+ draw_glyph_shadow(ellipsis_glyphs[gl_idx], ci, font_shadow_color, shadow_outline_size, offset_step, shadow_ofs);
+ } else if (step == DRAW_STEP_OUTLINE) {
+ draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_outline_color, outline_size, offset_step);
+ } else if (step == DRAW_STEP_TEXT) {
+ draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, offset_step);
+ }
}
- processed_glyphs_ol++;
- offset.x += ellipsis_glyphs[gl_idx].advance;
- }
- }
- }
- }
-
- // Draw main text. Note: Do not merge this into the single loop with the outline, to prevent overlaps.
-
- // Draw RTL ellipsis string when necessary.
- if (rtl && ellipsis_pos >= 0) {
- for (int gl_idx = ellipsis_gl_size - 1; gl_idx >= 0; gl_idx--) {
- for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) {
- bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs < total_glyphs - visible_glyphs));
- //Draw glyph outlines and shadow.
- if (!skip) {
- draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, ofs);
- }
- processed_glyphs++;
- ofs.x += ellipsis_glyphs[gl_idx].advance;
- }
- }
- }
-
- // Draw main text.
- for (int j = 0; j < gl_size; j++) {
- // Trim when necessary.
- if (trim_pos >= 0) {
- if (rtl) {
- if (j < trim_pos) {
- continue;
- }
- } else {
- if (j >= trim_pos) {
- break;
- }
- }
- }
- for (int k = 0; k < glyphs[j].repeat; k++) {
- bool skip = (trim_chars && glyphs[j].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs < total_glyphs - visible_glyphs));
-
- // Draw glyph outlines and shadow.
- if (!skip) {
- draw_glyph(glyphs[j], ci, font_color, ofs);
- }
- processed_glyphs++;
- ofs.x += glyphs[j].advance;
- }
- }
- // Draw LTR ellipsis string when necessary.
- if (!rtl && ellipsis_pos >= 0) {
- for (int gl_idx = 0; gl_idx < ellipsis_gl_size; gl_idx++) {
- for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) {
- bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs < total_glyphs - visible_glyphs));
- //Draw glyph outlines and shadow.
- if (!skip) {
- draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, ofs);
+ processed_glyphs_step++;
+ offset_step.x += ellipsis_glyphs[gl_idx].advance;
}
- processed_glyphs++;
- ofs.x += ellipsis_glyphs[gl_idx].advance;
}
}
}
diff --git a/scene/gui/label.h b/scene/gui/label.h
index 4bd0e53605..e0ebca944a 100644
--- a/scene/gui/label.h
+++ b/scene/gui/label.h
@@ -38,6 +38,13 @@ class Label : public Control {
GDCLASS(Label, Control);
private:
+ enum LabelDrawStep {
+ DRAW_STEP_SHADOW,
+ DRAW_STEP_OUTLINE,
+ DRAW_STEP_TEXT,
+ DRAW_STEP_MAX,
+ };
+
HorizontalAlignment horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT;
VerticalAlignment vertical_alignment = VERTICAL_ALIGNMENT_TOP;
String text;
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
index a808a3b1b5..8eb455d0ac 100644
--- a/scene/gui/menu_bar.cpp
+++ b/scene/gui/menu_bar.cpp
@@ -192,7 +192,7 @@ bool MenuBar::is_native_menu() const {
}
#endif
- return (NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU) && is_native);
+ return (NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU) && prefer_native);
}
void MenuBar::bind_global_menu() {
@@ -767,9 +767,9 @@ int MenuBar::get_start_index() const {
}
void MenuBar::set_prefer_global_menu(bool p_enabled) {
- if (is_native != p_enabled) {
- is_native = p_enabled;
- if (is_native) {
+ if (prefer_native != p_enabled) {
+ prefer_native = p_enabled;
+ if (prefer_native) {
bind_global_menu();
} else {
unbind_global_menu();
@@ -778,7 +778,7 @@ void MenuBar::set_prefer_global_menu(bool p_enabled) {
}
bool MenuBar::is_prefer_global_menu() const {
- return is_native;
+ return prefer_native;
}
Size2 MenuBar::get_minimum_size() const {
diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h
index 962eea593f..631b791e1b 100644
--- a/scene/gui/menu_bar.h
+++ b/scene/gui/menu_bar.h
@@ -41,7 +41,7 @@ class MenuBar : public Control {
bool switch_on_hover = true;
bool disable_shortcuts = false;
- bool is_native = true;
+ bool prefer_native = true;
bool flat = false;
int start_index = -1;
diff --git a/scene/gui/panel_container.cpp b/scene/gui/panel_container.cpp
index 306ef4936f..ef85919859 100644
--- a/scene/gui/panel_container.cpp
+++ b/scene/gui/panel_container.cpp
@@ -44,8 +44,7 @@ Size2 PanelContainer::get_minimum_size() const {
}
Size2 minsize = c->get_combined_minimum_size();
- ms.width = MAX(ms.width, minsize.width);
- ms.height = MAX(ms.height, minsize.height);
+ ms = ms.max(minsize);
}
if (theme_cache.panel_style.is_valid()) {
diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp
index 0c5a882044..0927404947 100644
--- a/scene/gui/popup.cpp
+++ b/scene/gui/popup.cpp
@@ -224,8 +224,7 @@ Size2 PopupPanel::_get_contents_minimum_size() const {
}
Size2 cms = c->get_combined_minimum_size();
- ms.x = MAX(cms.x, ms.x);
- ms.y = MAX(cms.y, ms.y);
+ ms = cms.max(ms);
}
return ms + theme_cache.panel_style->get_minimum_size();
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index dfe0649d0f..56346f5edc 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -82,7 +82,7 @@ RID PopupMenu::bind_global_menu() {
return RID();
}
#endif
- if (!NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU)) {
+ if (!NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_POPUP_MENU)) {
return RID();
}
@@ -92,7 +92,7 @@ RID PopupMenu::bind_global_menu() {
NativeMenu *nmenu = NativeMenu::get_singleton();
- if (system_menu_id != NativeMenu::INVALID_MENU_ID) {
+ if (system_menu_id != NativeMenu::INVALID_MENU_ID && nmenu->has_system_menu(system_menu_id)) {
if (system_menus.has(system_menu_id)) {
WARN_PRINT(vformat("Attempting to bind PopupMenu to the system menu %s, but another menu is already bound to it. This menu: %s, current menu: %s", nmenu->get_system_menu_name(system_menu_id), get_description(), system_menus[system_menu_id]->get_description()));
global_menu = nmenu->create_menu();
@@ -105,6 +105,7 @@ RID PopupMenu::bind_global_menu() {
global_menu = nmenu->create_menu();
}
+ nmenu->set_interface_direction(global_menu, control->is_layout_rtl());
nmenu->set_popup_open_callback(global_menu, callable_mp(this, &PopupMenu::_about_to_popup));
nmenu->set_popup_close_callback(global_menu, callable_mp(this, &PopupMenu::_about_to_close));
for (int i = 0; i < items.size(); i++) {
@@ -1027,6 +1028,9 @@ void PopupMenu::_notification(int p_what) {
case NOTIFICATION_TRANSLATION_CHANGED: {
NativeMenu *nmenu = NativeMenu::get_singleton();
bool is_global = global_menu.is_valid();
+ if (is_global) {
+ nmenu->set_interface_direction(global_menu, control->is_layout_rtl());
+ }
for (int i = 0; i < items.size(); i++) {
Item &item = items.write[i];
item.xl_text = atr(item.text);
@@ -2289,6 +2293,21 @@ void PopupMenu::scroll_to_item(int p_idx) {
}
}
+void PopupMenu::set_prefer_native_menu(bool p_enabled) {
+ if (prefer_native != p_enabled) {
+ prefer_native = p_enabled;
+ if (prefer_native) {
+ bind_global_menu();
+ } else {
+ unbind_global_menu();
+ }
+ }
+}
+
+bool PopupMenu::is_prefer_native_menu() const {
+ return prefer_native;
+}
+
bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only) {
ERR_FAIL_COND_V(p_event.is_null(), false);
Key code = Key::NONE;
@@ -2616,6 +2635,9 @@ bool PopupMenu::_set(const StringName &p_name, const Variant &p_value) {
void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("activate_item_by_event", "event", "for_global_only"), &PopupMenu::activate_item_by_event, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("set_prefer_native_menu", "enabled"), &PopupMenu::set_prefer_native_menu);
+ ClassDB::bind_method(D_METHOD("is_prefer_native_menu"), &PopupMenu::is_prefer_native_menu);
+
ClassDB::bind_method(D_METHOD("add_item", "label", "id", "accel"), &PopupMenu::add_item, DEFVAL(-1), DEFVAL(0));
ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_item, DEFVAL(-1), DEFVAL(0));
ClassDB::bind_method(D_METHOD("add_check_item", "label", "id", "accel"), &PopupMenu::add_check_item, DEFVAL(-1), DEFVAL(0));
@@ -2722,7 +2744,8 @@ void PopupMenu::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_state_item_selection"), "set_hide_on_state_item_selection", "is_hide_on_state_item_selection");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "submenu_popup_delay", PROPERTY_HINT_NONE, "suffix:s"), "set_submenu_popup_delay", "get_submenu_popup_delay");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "system_menu_id", PROPERTY_HINT_ENUM, "Application Menu:2,Window Menu:3,Help Menu:4,Dock:5"), "set_system_menu", "get_system_menu");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "system_menu_id", PROPERTY_HINT_ENUM, "None:0,Application Menu:2,Window Menu:3,Help Menu:4,Dock:5"), "set_system_menu", "get_system_menu");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "prefer_native_menu"), "set_prefer_native_menu", "is_prefer_native_menu");
ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "item_");
@@ -2786,9 +2809,37 @@ void PopupMenu::_bind_methods() {
}
void PopupMenu::popup(const Rect2i &p_bounds) {
- moved = Vector2();
- popup_time_msec = OS::get_singleton()->get_ticks_msec();
- Popup::popup(p_bounds);
+ bool native = global_menu.is_valid();
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ native = false;
+ }
+#endif
+
+ if (native) {
+ NativeMenu::get_singleton()->popup(global_menu, (p_bounds != Rect2i()) ? p_bounds.position : get_position());
+ } else {
+ moved = Vector2();
+ popup_time_msec = OS::get_singleton()->get_ticks_msec();
+ Popup::popup(p_bounds);
+ }
+}
+
+void PopupMenu::set_visible(bool p_visible) {
+ bool native = global_menu.is_valid();
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ native = false;
+ }
+#endif
+
+ if (native) {
+ if (p_visible) {
+ NativeMenu::get_singleton()->popup(global_menu, get_position());
+ }
+ } else {
+ Popup::set_visible(p_visible);
+ }
}
PopupMenu::PopupMenu() {
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index 602d5466a9..f7097fffaf 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -100,6 +100,7 @@ class PopupMenu : public Popup {
RID global_menu;
RID system_menu;
NativeMenu::SystemMenus system_menu_id = NativeMenu::INVALID_MENU_ID;
+ bool prefer_native = false;
bool close_allowed = false;
bool activated_by_keyboard = false;
@@ -322,6 +323,9 @@ public:
void set_item_count(int p_count);
int get_item_count() const;
+ void set_prefer_native_menu(bool p_enabled);
+ bool is_prefer_native_menu() const;
+
void scroll_to_item(int p_idx);
bool activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only = false);
@@ -357,6 +361,7 @@ public:
bool get_allow_search() const;
virtual void popup(const Rect2i &p_bounds = Rect2i()) override;
+ virtual void set_visible(bool p_visible) override;
void take_mouse_focus();
diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp
index c66b30f130..b2617e6fc7 100644
--- a/scene/gui/progress_bar.cpp
+++ b/scene/gui/progress_bar.cpp
@@ -35,15 +35,13 @@
Size2 ProgressBar::get_minimum_size() const {
Size2 minimum_size = theme_cache.background_style->get_minimum_size();
- minimum_size.height = MAX(minimum_size.height, theme_cache.fill_style->get_minimum_size().height);
- minimum_size.width = MAX(minimum_size.width, theme_cache.fill_style->get_minimum_size().width);
+ minimum_size = minimum_size.max(theme_cache.fill_style->get_minimum_size());
if (show_percentage) {
String txt = "100%";
TextLine tl = TextLine(txt, theme_cache.font, theme_cache.font_size);
minimum_size.height = MAX(minimum_size.height, theme_cache.background_style->get_minimum_size().height + tl.get_size().y);
} else { // this is needed, else the progressbar will collapse
- minimum_size.width = MAX(minimum_size.width, 1);
- minimum_size.height = MAX(minimum_size.height, 1);
+ minimum_size = minimum_size.max(Size2(1, 1));
}
return minimum_size;
}
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index cb7ccb583e..1baf71dd07 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -951,7 +951,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
case ITEM_IMAGE: {
ItemImage *img = static_cast<ItemImage *>(it);
if (img->pad) {
- Size2 pad_size = Size2(MIN(rect.size.x, img->image->get_width()), MIN(rect.size.y, img->image->get_height()));
+ Size2 pad_size = rect.size.min(img->image->get_size());
Vector2 pad_off = (rect.size - pad_size) / 2;
img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off + pad_off, pad_size), false, img->color);
} else {
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index 89d308de3f..1447d2f002 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -56,8 +56,7 @@ Size2 ScrollContainer::get_minimum_size() const {
Size2 child_min_size = c->get_combined_minimum_size();
- largest_child_min_size.x = MAX(largest_child_min_size.x, child_min_size.x);
- largest_child_min_size.y = MAX(largest_child_min_size.y, child_min_size.y);
+ largest_child_min_size = largest_child_min_size.max(child_min_size);
}
if (horizontal_scroll_mode == SCROLL_MODE_DISABLED) {
diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp
index 0d33774e20..f6cfe6ab18 100644
--- a/scene/gui/subviewport_container.cpp
+++ b/scene/gui/subviewport_container.cpp
@@ -45,8 +45,7 @@ Size2 SubViewportContainer::get_minimum_size() const {
}
Size2 minsize = c->get_size();
- ms.width = MAX(ms.width, minsize.width);
- ms.height = MAX(ms.height, minsize.height);
+ ms = ms.max(minsize);
}
return ms;
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index 741b38a35a..df3c9631f9 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -871,8 +871,7 @@ Size2 TabContainer::get_minimum_size() const {
}
Size2 cms = c->get_combined_minimum_size();
- largest_child_min_size.x = MAX(largest_child_min_size.x, cms.x);
- largest_child_min_size.y = MAX(largest_child_min_size.y, cms.y);
+ largest_child_min_size = largest_child_min_size.max(cms);
}
ms.y += largest_child_min_size.y;
diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp
index dcbb25c41d..0b197c8c02 100644
--- a/scene/gui/texture_button.cpp
+++ b/scene/gui/texture_button.cpp
@@ -103,8 +103,8 @@ bool TextureButton::has_point(const Point2 &p_point) const {
point *= scale;
// finally, we need to check if the point is inside a rectangle with a position >= 0,0 and a size <= mask_size
- rect.position = Point2(MAX(0, _texture_region.position.x), MAX(0, _texture_region.position.y));
- rect.size = Size2(MIN(mask_size.x, _texture_region.size.x), MIN(mask_size.y, _texture_region.size.y));
+ rect.position = Point2().max(_texture_region.position);
+ rect.size = mask_size.min(_texture_region.size);
}
if (!rect.has_point(point)) {
diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp
index 248260a7d2..5261cbe3eb 100644
--- a/scene/gui/texture_progress_bar.cpp
+++ b/scene/gui/texture_progress_bar.cpp
@@ -249,8 +249,7 @@ Point2 TextureProgressBar::get_relative_center() {
p += rad_center_off;
p.x /= progress->get_width();
p.y /= progress->get_height();
- p.x = CLAMP(p.x, 0, 1);
- p.y = CLAMP(p.y, 0, 1);
+ p = p.clamp(Point2(), Point2(1, 1));
return p;
}
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index 2d0da075ba..768c83954b 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -336,6 +336,19 @@ void CanvasItem::_notification(int p_what) {
get_parent()->connect(SNAME("child_order_changed"), callable_mp(get_viewport(), &Viewport::canvas_parent_mark_dirty).bind(get_parent()), CONNECT_REFERENCE_COUNTED);
}
+ // If using physics interpolation, reset for this node only,
+ // as a helper, as in most cases, users will want items reset when
+ // adding to the tree.
+ // In cases where they move immediately after adding,
+ // there will be little cost in having two resets as these are cheap,
+ // and it is worth it for convenience.
+ // Do not propagate to children, as each child of an added branch
+ // receives its own NOTIFICATION_ENTER_TREE, and this would
+ // cause unnecessary duplicate resets.
+ if (is_physics_interpolated_and_enabled()) {
+ notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
+ }
+
} break;
case NOTIFICATION_EXIT_TREE: {
ERR_MAIN_THREAD_GUARD;
@@ -360,6 +373,12 @@ void CanvasItem::_notification(int p_what) {
}
} break;
+ case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+ if (is_visible_in_tree() && is_physics_interpolated()) {
+ RenderingServer::get_singleton()->canvas_item_reset_physics_interpolation(canvas_item);
+ }
+ } break;
+
case NOTIFICATION_VISIBILITY_CHANGED: {
ERR_MAIN_THREAD_GUARD;
@@ -921,6 +940,10 @@ void CanvasItem::_notify_transform(CanvasItem *p_node) {
}
}
+void CanvasItem::_physics_interpolated_changed() {
+ RenderingServer::get_singleton()->canvas_item_set_interpolated(canvas_item, is_physics_interpolated());
+}
+
Rect2 CanvasItem::get_viewport_rect() const {
ERR_READ_THREAD_GUARD_V(Rect2());
ERR_FAIL_COND_V(!is_inside_tree(), Rect2());
diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h
index 03b01f7ef7..383edeec93 100644
--- a/scene/main/canvas_item.h
+++ b/scene/main/canvas_item.h
@@ -140,6 +140,8 @@ private:
void _notify_transform(CanvasItem *p_node);
+ virtual void _physics_interpolated_changed() override;
+
static CanvasItem *current_item_drawn;
friend class Viewport;
void _refresh_texture_repeat_cache() const;
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 906c397b5c..142736e388 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -99,6 +99,14 @@ void Node::_notification(int p_notification) {
}
}
+ if (data.physics_interpolation_mode == PHYSICS_INTERPOLATION_MODE_INHERIT) {
+ bool interpolate = true; // Root node default is for interpolation to be on.
+ if (data.parent) {
+ interpolate = data.parent->is_physics_interpolated();
+ }
+ _propagate_physics_interpolated(interpolate);
+ }
+
// Update auto translate mode.
if (data.auto_translate_mode == AUTO_TRANSLATE_MODE_INHERIT && !data.parent) {
ERR_PRINT("The root node can't be set to Inherit auto translate mode, reverting to Always instead.");
@@ -395,6 +403,36 @@ void Node::_propagate_exit_tree() {
data.depth = -1;
}
+void Node::_propagate_physics_interpolated(bool p_interpolated) {
+ switch (data.physics_interpolation_mode) {
+ case PHYSICS_INTERPOLATION_MODE_INHERIT:
+ // Keep the parent p_interpolated.
+ break;
+ case PHYSICS_INTERPOLATION_MODE_OFF: {
+ p_interpolated = false;
+ } break;
+ case PHYSICS_INTERPOLATION_MODE_ON: {
+ p_interpolated = true;
+ } break;
+ }
+
+ // No change? No need to propagate further.
+ if (data.physics_interpolated == p_interpolated) {
+ return;
+ }
+
+ data.physics_interpolated = p_interpolated;
+
+ // Allow a call to the RenderingServer etc. in derived classes.
+ _physics_interpolated_changed();
+
+ data.blocked++;
+ for (KeyValue<StringName, Node *> &K : data.children) {
+ K.value->_propagate_physics_interpolated(p_interpolated);
+ }
+ data.blocked--;
+}
+
void Node::move_child(Node *p_child, int p_index) {
ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Moving child node positions inside the SceneTree is only allowed from the main thread. Use call_deferred(\"move_child\",child,index).");
ERR_FAIL_NULL(p_child);
@@ -507,6 +545,8 @@ void Node::move_child_notify(Node *p_child) {
void Node::owner_changed_notify() {
}
+void Node::_physics_interpolated_changed() {}
+
void Node::set_physics_process(bool p_process) {
ERR_THREAD_GUARD
if (data.physics_process == p_process) {
@@ -821,6 +861,42 @@ bool Node::_can_process(bool p_paused) const {
}
}
+void Node::set_physics_interpolation_mode(PhysicsInterpolationMode p_mode) {
+ ERR_THREAD_GUARD
+ if (data.physics_interpolation_mode == p_mode) {
+ return;
+ }
+
+ data.physics_interpolation_mode = p_mode;
+
+ bool interpolate = true; // Default for root node.
+
+ switch (p_mode) {
+ case PHYSICS_INTERPOLATION_MODE_INHERIT: {
+ if (is_inside_tree() && data.parent) {
+ interpolate = data.parent->is_physics_interpolated();
+ }
+ } break;
+ case PHYSICS_INTERPOLATION_MODE_OFF: {
+ interpolate = false;
+ } break;
+ case PHYSICS_INTERPOLATION_MODE_ON: {
+ interpolate = true;
+ } break;
+ }
+
+ // If swapping from interpolated to non-interpolated, use this as an extra means to cause a reset.
+ if (is_physics_interpolated() && !interpolate) {
+ reset_physics_interpolation();
+ }
+
+ _propagate_physics_interpolated(interpolate);
+}
+
+void Node::reset_physics_interpolation() {
+ propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
+}
+
bool Node::_is_enabled() const {
ProcessMode process_mode;
@@ -2737,25 +2813,9 @@ Node *Node::_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap) c
}
} else {
- // If property points to a node which is owned by a node we are duplicating, update its path.
- if (value.get_type() == Variant::OBJECT) {
- Node *property_node = Object::cast_to<Node>(value);
- if (property_node && is_ancestor_of(property_node)) {
- value = current_node->get_node_or_null(get_path_to(property_node));
- }
- } else if (value.get_type() == Variant::ARRAY) {
- Array arr = value;
- if (arr.get_typed_builtin() == Variant::OBJECT) {
- for (int i = 0; i < arr.size(); i++) {
- Node *property_node = Object::cast_to<Node>(arr[i]);
- if (property_node && is_ancestor_of(property_node)) {
- arr[i] = current_node->get_node_or_null(get_path_to(property_node));
- }
- }
- value = arr;
- }
+ if (value.get_type() != Variant::OBJECT && (value.get_type() != Variant::ARRAY || static_cast<Array>(value).get_typed_builtin() != Variant::OBJECT)) {
+ current_node->set(name, value);
}
- current_node->set(name, value);
}
}
}
@@ -2771,6 +2831,8 @@ Node *Node::duplicate(int p_flags) const {
_duplicate_signals(this, dupe);
}
+ _duplicate_properties_node(this, this, dupe);
+
return dupe;
}
@@ -2792,6 +2854,8 @@ Node *Node::duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap, con
// if the emitter node comes later in tree order than the receiver
_duplicate_signals(this, dupe);
+ _duplicate_properties_node(this, this, dupe);
+
return dupe;
}
@@ -2844,6 +2908,44 @@ void Node::remap_nested_resources(Ref<Resource> p_resource, const HashMap<Ref<Re
}
#endif
+// Duplicates properties that store a Node.
+// This has to be called after nodes have been duplicated since
+// only then do we get a full picture of how the duplicated node tree looks like.
+void Node::_duplicate_properties_node(const Node *p_root, const Node *p_original, Node *p_copy) const {
+ List<PropertyInfo> props;
+ p_copy->get_property_list(&props);
+ for (const PropertyInfo &E : props) {
+ if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
+ continue;
+ }
+ String name = E.name;
+ Variant value = p_original->get(name).duplicate(true);
+ if (value.get_type() == Variant::OBJECT) {
+ Node *property_node = Object::cast_to<Node>(value);
+ if (property_node && (p_root == property_node || p_root->is_ancestor_of(property_node))) {
+ value = p_copy->get_node_or_null(p_original->get_path_to(property_node));
+ p_copy->set(name, value);
+ }
+ } else if (value.get_type() == Variant::ARRAY) {
+ Array arr = value;
+ if (arr.get_typed_builtin() == Variant::OBJECT) {
+ for (int i = 0; i < arr.size(); i++) {
+ Node *property_node = Object::cast_to<Node>(arr[i]);
+ if (property_node && (p_root == property_node || p_root->is_ancestor_of(property_node))) {
+ arr[i] = p_copy->get_node_or_null(p_original->get_path_to(property_node));
+ }
+ }
+ value = arr;
+ p_copy->set(name, value);
+ }
+ }
+ }
+
+ for (int i = 0; i < p_copy->get_child_count(); i++) {
+ _duplicate_properties_node(p_root, p_original->get_child(i), p_copy->get_child(i));
+ }
+}
+
// Duplication of signals must happen after all the node descendants have been copied,
// because re-targeting of connections from some descendant to another is not possible
// if the emitter node comes later in tree order than the receiver
@@ -3489,6 +3591,12 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_physics_process_internal", "enable"), &Node::set_physics_process_internal);
ClassDB::bind_method(D_METHOD("is_physics_processing_internal"), &Node::is_physics_processing_internal);
+ ClassDB::bind_method(D_METHOD("set_physics_interpolation_mode", "mode"), &Node::set_physics_interpolation_mode);
+ ClassDB::bind_method(D_METHOD("get_physics_interpolation_mode"), &Node::get_physics_interpolation_mode);
+ ClassDB::bind_method(D_METHOD("is_physics_interpolated"), &Node::is_physics_interpolated);
+ ClassDB::bind_method(D_METHOD("is_physics_interpolated_and_enabled"), &Node::is_physics_interpolated_and_enabled);
+ ClassDB::bind_method(D_METHOD("reset_physics_interpolation"), &Node::reset_physics_interpolation);
+
ClassDB::bind_method(D_METHOD("set_auto_translate_mode", "mode"), &Node::set_auto_translate_mode);
ClassDB::bind_method(D_METHOD("get_auto_translate_mode"), &Node::get_auto_translate_mode);
@@ -3594,6 +3702,7 @@ void Node::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_POST_ENTER_TREE);
BIND_CONSTANT(NOTIFICATION_DISABLED);
BIND_CONSTANT(NOTIFICATION_ENABLED);
+ BIND_CONSTANT(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
BIND_CONSTANT(NOTIFICATION_EDITOR_PRE_SAVE);
BIND_CONSTANT(NOTIFICATION_EDITOR_POST_SAVE);
@@ -3633,6 +3742,10 @@ void Node::_bind_methods() {
BIND_BITFIELD_FLAG(FLAG_PROCESS_THREAD_MESSAGES_PHYSICS);
BIND_BITFIELD_FLAG(FLAG_PROCESS_THREAD_MESSAGES_ALL);
+ BIND_ENUM_CONSTANT(PHYSICS_INTERPOLATION_MODE_INHERIT);
+ BIND_ENUM_CONSTANT(PHYSICS_INTERPOLATION_MODE_ON);
+ BIND_ENUM_CONSTANT(PHYSICS_INTERPOLATION_MODE_OFF);
+
BIND_ENUM_CONSTANT(DUPLICATE_SIGNALS);
BIND_ENUM_CONSTANT(DUPLICATE_GROUPS);
BIND_ENUM_CONSTANT(DUPLICATE_SCRIPTS);
@@ -3674,6 +3787,9 @@ void Node::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_thread_group_order"), "set_process_thread_group_order", "get_process_thread_group_order");
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_thread_messages", PROPERTY_HINT_FLAGS, "Process,Physics Process"), "set_process_thread_messages", "get_process_thread_messages");
+ ADD_GROUP("Physics Interpolation", "physics_interpolation_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_interpolation_mode", PROPERTY_HINT_ENUM, "Inherit,Off,On"), "set_physics_interpolation_mode", "get_physics_interpolation_mode");
+
ADD_GROUP("Auto Translate", "auto_translate_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "auto_translate_mode", PROPERTY_HINT_ENUM, "Inherit,Always,Disabled"), "set_auto_translate_mode", "get_auto_translate_mode");
@@ -3708,6 +3824,35 @@ String Node::_get_name_num_separator() {
Node::Node() {
orphan_node_count++;
+
+ // Default member initializer for bitfield is a C++20 extension, so:
+
+ data.process_mode = PROCESS_MODE_INHERIT;
+ data.physics_interpolation_mode = PHYSICS_INTERPOLATION_MODE_INHERIT;
+
+ data.physics_process = false;
+ data.process = false;
+
+ data.physics_process_internal = false;
+ data.process_internal = false;
+
+ data.input = false;
+ data.shortcut_input = false;
+ data.unhandled_input = false;
+ data.unhandled_key_input = false;
+
+ data.physics_interpolated = false;
+
+ data.parent_owned = false;
+ data.in_constructor = true;
+ data.use_placeholder = false;
+
+ data.display_folded = false;
+ data.editable_instance = false;
+
+ data.inside_tree = false;
+ data.ready_notified = false; // This is a small hack, so if a node is added during _ready() to the tree, it correctly gets the _ready() notification.
+ data.ready_first = true;
}
Node::~Node() {
diff --git a/scene/main/node.h b/scene/main/node.h
index 7cc0f7b370..f49eeec9cd 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -57,7 +57,7 @@ protected:
MTFlag() :
mt{} {}
};
- template <class T>
+ template <typename T>
union MTNumeric {
SafeNumeric<T> mt;
T st;
@@ -66,7 +66,9 @@ protected:
};
public:
- enum ProcessMode {
+ // N.B. Any enum stored as a bitfield should be specified as UNSIGNED to work around
+ // some compilers trying to store it as signed, and requiring 1 more bit than necessary.
+ enum ProcessMode : unsigned int {
PROCESS_MODE_INHERIT, // same as parent node
PROCESS_MODE_PAUSABLE, // process only if not paused
PROCESS_MODE_WHEN_PAUSED, // process only if paused
@@ -86,6 +88,12 @@ public:
FLAG_PROCESS_THREAD_MESSAGES_ALL = 3,
};
+ enum PhysicsInterpolationMode : unsigned int {
+ PHYSICS_INTERPOLATION_MODE_INHERIT,
+ PHYSICS_INTERPOLATION_MODE_ON,
+ PHYSICS_INTERPOLATION_MODE_OFF,
+ };
+
enum DuplicateFlags {
DUPLICATE_SIGNALS = 1,
DUPLICATE_GROUPS = 2,
@@ -170,9 +178,7 @@ private:
int blocked = 0; // Safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed.
StringName name;
SceneTree *tree = nullptr;
- bool inside_tree = false;
- bool ready_notified = false; // This is a small hack, so if a node is added during _ready() to the tree, it correctly gets the _ready() notification.
- bool ready_first = true;
+
#ifdef TOOLS_ENABLED
NodePath import_path; // Path used when imported, used by scene editors to keep tracking.
#endif
@@ -184,7 +190,6 @@ private:
List<Node *>::Element *OW = nullptr; // Owned element.
List<Node *> owned;
- ProcessMode process_mode = PROCESS_MODE_INHERIT;
Node *process_owner = nullptr;
ProcessThreadGroup process_thread_group = PROCESS_THREAD_GROUP_INHERIT;
Node *process_thread_group_owner = nullptr;
@@ -196,26 +201,39 @@ private:
Variant rpc_config;
// Variables used to properly sort the node when processing, ignored otherwise.
- // TODO: Should move all the stuff below to bits.
- bool physics_process = false;
- bool process = false;
int process_priority = 0;
int physics_process_priority = 0;
- bool physics_process_internal = false;
- bool process_internal = false;
+ // Keep bitpacked values together to get better packing.
+ ProcessMode process_mode : 3;
+ PhysicsInterpolationMode physics_interpolation_mode : 2;
+
+ bool physics_process : 1;
+ bool process : 1;
+
+ bool physics_process_internal : 1;
+ bool process_internal : 1;
+
+ bool input : 1;
+ bool shortcut_input : 1;
+ bool unhandled_input : 1;
+ bool unhandled_key_input : 1;
- bool input = false;
- bool shortcut_input = false;
- bool unhandled_input = false;
- bool unhandled_key_input = false;
+ // Physics interpolation can be turned on and off on a per node basis.
+ // This only takes effect when the SceneTree (or project setting) physics interpolation
+ // is switched on.
+ bool physics_interpolated : 1;
- bool parent_owned = false;
- bool in_constructor = true;
- bool use_placeholder = false;
+ bool parent_owned : 1;
+ bool in_constructor : 1;
+ bool use_placeholder : 1;
- bool display_folded = false;
- bool editable_instance = false;
+ bool display_folded : 1;
+ bool editable_instance : 1;
+
+ bool inside_tree : 1;
+ bool ready_notified : 1;
+ bool ready_first : 1;
AutoTranslateMode auto_translate_mode = AUTO_TRANSLATE_MODE_INHERIT;
mutable bool is_auto_translating = true;
@@ -243,10 +261,12 @@ private:
void _propagate_ready();
void _propagate_exit_tree();
void _propagate_after_exit_tree();
+ void _propagate_physics_interpolated(bool p_interpolated);
void _propagate_process_owner(Node *p_owner, int p_pause_notification, int p_enabled_notification);
void _propagate_groups_dirty();
Array _get_node_and_resource(const NodePath &p_path);
+ void _duplicate_properties_node(const Node *p_root, const Node *p_original, Node *p_copy) const;
void _duplicate_signals(const Node *p_original, Node *p_copy) const;
Node *_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap = nullptr) const;
@@ -295,6 +315,8 @@ protected:
void _notification(int p_notification);
+ virtual void _physics_interpolated_changed();
+
virtual void add_child_notify(Node *p_child);
virtual void remove_child_notify(Node *p_child);
virtual void move_child_notify(Node *p_child);
@@ -339,7 +361,7 @@ protected:
public:
enum {
- // you can make your own, but don't use the same numbers as other notifications in other nodes
+ // You can make your own, but don't use the same numbers as other notifications in other nodes.
NOTIFICATION_ENTER_TREE = 10,
NOTIFICATION_EXIT_TREE = 11,
NOTIFICATION_MOVED_IN_PARENT = 12,
@@ -360,8 +382,8 @@ public:
NOTIFICATION_POST_ENTER_TREE = 27,
NOTIFICATION_DISABLED = 28,
NOTIFICATION_ENABLED = 29,
- //keep these linked to node
-
+ NOTIFICATION_RESET_PHYSICS_INTERPOLATION = 2001, // A GodotSpace Odyssey.
+ // Keep these linked to Node.
NOTIFICATION_WM_MOUSE_ENTER = 1002,
NOTIFICATION_WM_MOUSE_EXIT = 1003,
NOTIFICATION_WM_WINDOW_FOCUS_IN = 1004,
@@ -613,6 +635,13 @@ public:
ProcessMode get_process_mode() const;
bool can_process() const;
bool can_process_notification(int p_what) const;
+
+ void set_physics_interpolation_mode(PhysicsInterpolationMode p_mode);
+ PhysicsInterpolationMode get_physics_interpolation_mode() const { return data.physics_interpolation_mode; }
+ _FORCE_INLINE_ bool is_physics_interpolated() const { return data.physics_interpolated; }
+ _FORCE_INLINE_ bool is_physics_interpolated_and_enabled() const { return is_inside_tree() && get_tree()->is_physics_interpolation_enabled() && is_physics_interpolated(); }
+ void reset_physics_interpolation();
+
bool is_enabled() const;
bool is_ready() const;
@@ -742,6 +771,7 @@ VARIANT_ENUM_CAST(Node::ProcessMode);
VARIANT_ENUM_CAST(Node::ProcessThreadGroup);
VARIANT_BITFIELD_CAST(Node::ProcessThreadMessages);
VARIANT_ENUM_CAST(Node::InternalMode);
+VARIANT_ENUM_CAST(Node::PhysicsInterpolationMode);
VARIANT_ENUM_CAST(Node::AutoTranslateMode);
typedef HashSet<Node *, Node::Comparator> NodeSet;
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index cb16f425b5..0c77a60af6 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -451,6 +451,30 @@ void SceneTree::initialize() {
root->_set_tree(this);
}
+void SceneTree::set_physics_interpolation_enabled(bool p_enabled) {
+ // We never want interpolation in the editor.
+ if (Engine::get_singleton()->is_editor_hint()) {
+ p_enabled = false;
+ }
+
+ if (p_enabled == _physics_interpolation_enabled) {
+ return;
+ }
+
+ _physics_interpolation_enabled = p_enabled;
+ RenderingServer::get_singleton()->set_physics_interpolation_enabled(p_enabled);
+}
+
+bool SceneTree::is_physics_interpolation_enabled() const {
+ return _physics_interpolation_enabled;
+}
+
+void SceneTree::iteration_prepare() {
+ if (_physics_interpolation_enabled) {
+ RenderingServer::get_singleton()->tick();
+ }
+}
+
bool SceneTree::physics_process(double p_time) {
root_lock++;
@@ -1606,6 +1630,9 @@ void SceneTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_frame"), &SceneTree::get_frame);
ClassDB::bind_method(D_METHOD("quit", "exit_code"), &SceneTree::quit, DEFVAL(EXIT_SUCCESS));
+ ClassDB::bind_method(D_METHOD("set_physics_interpolation_enabled", "enabled"), &SceneTree::set_physics_interpolation_enabled);
+ ClassDB::bind_method(D_METHOD("is_physics_interpolation_enabled"), &SceneTree::is_physics_interpolation_enabled);
+
ClassDB::bind_method(D_METHOD("queue_delete", "obj"), &SceneTree::queue_delete);
MethodInfo mi;
@@ -1657,6 +1684,7 @@ void SceneTree::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "current_scene", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_current_scene", "get_current_scene");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "", "get_root");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multiplayer_poll"), "set_multiplayer_poll_enabled", "is_multiplayer_poll_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "physics_interpolation"), "set_physics_interpolation_enabled", "is_physics_interpolation_enabled");
ADD_SIGNAL(MethodInfo("tree_changed"));
ADD_SIGNAL(MethodInfo("tree_process_mode_changed")); //editor only signal, but due to API hash it can't be removed in run-time
@@ -1748,6 +1776,8 @@ SceneTree::SceneTree() {
root->set_as_audio_listener_3d(true);
#endif // _3D_DISABLED
+ set_physics_interpolation_enabled(GLOBAL_DEF("physics/common/physics_interpolation", false));
+
// Initialize network state.
set_multiplayer(MultiplayerAPI::create_default_interface());
diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h
index a7515f0243..603438cc5f 100644
--- a/scene/main/scene_tree.h
+++ b/scene/main/scene_tree.h
@@ -139,6 +139,8 @@ private:
HashMap<StringName, Group> group_map;
bool _quit = false;
+ bool _physics_interpolation_enabled = false;
+
StringName tree_changed_name = "tree_changed";
StringName node_added_name = "node_added";
StringName node_removed_name = "node_removed";
@@ -313,6 +315,8 @@ public:
virtual void initialize() override;
+ virtual void iteration_prepare() override;
+
virtual bool physics_process(double p_time) override;
virtual bool process(double p_time) override;
@@ -425,6 +429,9 @@ public:
void set_disable_node_threading(bool p_disable);
//default texture settings
+ void set_physics_interpolation_enabled(bool p_enabled);
+ bool is_physics_interpolation_enabled() const;
+
SceneTree();
~SceneTree();
};
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index c8d2d71c2a..41f3ff108c 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -2747,8 +2747,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) {
Size2i min_size = gui.currently_dragged_subwindow->get_min_size();
Size2i min_size_clamped = gui.currently_dragged_subwindow->get_clamped_minimum_size();
- min_size_clamped.x = MAX(min_size_clamped.x, 1);
- min_size_clamped.y = MAX(min_size_clamped.y, 1);
+ min_size_clamped = min_size_clamped.max(Size2i(1, 1));
Rect2i r = gui.subwindow_resize_from_rect;
@@ -2809,8 +2808,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) {
Size2i max_size = gui.currently_dragged_subwindow->get_max_size();
if ((max_size.x > 0 || max_size.y > 0) && (max_size.x >= min_size.x && max_size.y >= min_size.y)) {
- max_size.x = MAX(max_size.x, 1);
- max_size.y = MAX(max_size.y, 1);
+ max_size = max_size.max(Size2i(1, 1));
if (r.size.x > max_size.x) {
r.size.x = max_size.x;
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 6632dd9033..bced493d98 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -1033,8 +1033,7 @@ void Window::_update_window_size() {
}
if (embedder) {
- size.x = MAX(size.x, 1);
- size.y = MAX(size.y, 1);
+ size = size.max(Size2i(1, 1));
embedder->_sub_window_update(this);
} else if (window_id != DisplayServer::INVALID_WINDOW_ID) {
@@ -1545,8 +1544,7 @@ Size2 Window::_get_contents_minimum_size() const {
Point2i pos = c->get_position();
Size2i min = c->get_combined_minimum_size();
- max.x = MAX(pos.x + min.x, max.x);
- max.y = MAX(pos.y + min.y, max.y);
+ max = max.max(pos + min);
}
}
@@ -1703,7 +1701,7 @@ void Window::popup_centered_clamped(const Size2i &p_size, float p_fallback_ratio
Vector2i size_ratio = parent_rect.size * p_fallback_ratio;
Rect2i popup_rect;
- popup_rect.size = Vector2i(MIN(size_ratio.x, expected_size.x), MIN(size_ratio.y, expected_size.y));
+ popup_rect.size = size_ratio.min(expected_size);
popup_rect.size = _clamp_window_size(popup_rect.size);
if (parent_rect != Rect2()) {
diff --git a/scene/resources/2d/polygon_path_finder.cpp b/scene/resources/2d/polygon_path_finder.cpp
index 617a53f0a3..3aa292431d 100644
--- a/scene/resources/2d/polygon_path_finder.cpp
+++ b/scene/resources/2d/polygon_path_finder.cpp
@@ -64,8 +64,7 @@ void PolygonPathFinder::setup(const Vector<Vector2> &p_points, const Vector<int>
points.write[i].pos = p_points[i];
points.write[i].penalty = 0;
- outside_point.x = i == 0 ? p_points[0].x : (MAX(p_points[i].x, outside_point.x));
- outside_point.y = i == 0 ? p_points[0].y : (MAX(p_points[i].y, outside_point.y));
+ outside_point = i == 0 ? p_points[0] : p_points[i].max(outside_point);
if (i == 0) {
bounds.position = points[i].pos;
diff --git a/scene/resources/2d/tile_set.cpp b/scene/resources/2d/tile_set.cpp
index 5ac7f127b0..66d97398b9 100644
--- a/scene/resources/2d/tile_set.cpp
+++ b/scene/resources/2d/tile_set.cpp
@@ -4650,7 +4650,7 @@ Ref<Texture2D> TileSetAtlasSource::get_texture() const {
void TileSetAtlasSource::set_margins(Vector2i p_margins) {
if (p_margins.x < 0 || p_margins.y < 0) {
WARN_PRINT("Atlas source margins should be positive.");
- margins = Vector2i(MAX(0, p_margins.x), MAX(0, p_margins.y));
+ margins = p_margins.max(Vector2i());
} else {
margins = p_margins;
}
@@ -4666,7 +4666,7 @@ Vector2i TileSetAtlasSource::get_margins() const {
void TileSetAtlasSource::set_separation(Vector2i p_separation) {
if (p_separation.x < 0 || p_separation.y < 0) {
WARN_PRINT("Atlas source separation should be positive.");
- separation = Vector2i(MAX(0, p_separation.x), MAX(0, p_separation.y));
+ separation = p_separation.max(Vector2i());
} else {
separation = p_separation;
}
@@ -4682,7 +4682,7 @@ Vector2i TileSetAtlasSource::get_separation() const {
void TileSetAtlasSource::set_texture_region_size(Vector2i p_tile_size) {
if (p_tile_size.x <= 0 || p_tile_size.y <= 0) {
WARN_PRINT("Atlas source tile_size should be strictly positive.");
- texture_region_size = Vector2i(MAX(1, p_tile_size.x), MAX(1, p_tile_size.y));
+ texture_region_size = p_tile_size.max(Vector2i(1, 1));
} else {
texture_region_size = p_tile_size;
}
@@ -4981,7 +4981,7 @@ void TileSetAtlasSource::create_tile(const Vector2i p_atlas_coords, const Vector
_create_coords_mapping_cache(p_atlas_coords);
_queue_update_padded_texture();
- emit_signal(SNAME("changed"));
+ _try_emit_changed();
}
void TileSetAtlasSource::remove_tile(Vector2i p_atlas_coords) {
@@ -5002,7 +5002,7 @@ void TileSetAtlasSource::remove_tile(Vector2i p_atlas_coords) {
_queue_update_padded_texture();
- emit_signal(SNAME("changed"));
+ _try_emit_changed();
}
bool TileSetAtlasSource::has_tile(Vector2i p_atlas_coords) const {
@@ -5032,7 +5032,7 @@ void TileSetAtlasSource::set_tile_animation_columns(const Vector2i p_atlas_coord
_create_coords_mapping_cache(p_atlas_coords);
_queue_update_padded_texture();
- emit_signal(SNAME("changed"));
+ _try_emit_changed();
}
int TileSetAtlasSource::get_tile_animation_columns(const Vector2i p_atlas_coords) const {
@@ -5055,7 +5055,7 @@ void TileSetAtlasSource::set_tile_animation_separation(const Vector2i p_atlas_co
_create_coords_mapping_cache(p_atlas_coords);
_queue_update_padded_texture();
- emit_signal(SNAME("changed"));
+ _try_emit_changed();
}
Vector2i TileSetAtlasSource::get_tile_animation_separation(const Vector2i p_atlas_coords) const {
@@ -5069,7 +5069,7 @@ void TileSetAtlasSource::set_tile_animation_speed(const Vector2i p_atlas_coords,
tiles[p_atlas_coords].animation_speed = p_speed;
- emit_signal(SNAME("changed"));
+ _try_emit_changed();
}
real_t TileSetAtlasSource::get_tile_animation_speed(const Vector2i p_atlas_coords) const {
@@ -5082,7 +5082,7 @@ void TileSetAtlasSource::set_tile_animation_mode(const Vector2i p_atlas_coords,
tiles[p_atlas_coords].animation_mode = p_mode;
- emit_signal(SNAME("changed"));
+ _try_emit_changed();
}
TileSetAtlasSource::TileAnimationMode TileSetAtlasSource::get_tile_animation_mode(const Vector2i p_atlas_coords) const {
@@ -5116,7 +5116,7 @@ void TileSetAtlasSource::set_tile_animation_frames_count(const Vector2i p_atlas_
notify_property_list_changed();
- emit_signal(SNAME("changed"));
+ _try_emit_changed();
}
int TileSetAtlasSource::get_tile_animation_frames_count(const Vector2i p_atlas_coords) const {
@@ -5131,7 +5131,7 @@ void TileSetAtlasSource::set_tile_animation_frame_duration(const Vector2i p_atla
tiles[p_atlas_coords].animation_frames_durations[p_frame_index] = p_duration;
- emit_signal(SNAME("changed"));
+ _try_emit_changed();
}
real_t TileSetAtlasSource::get_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index) const {
@@ -5341,7 +5341,7 @@ void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_
_create_coords_mapping_cache(new_atlas_coords);
_queue_update_padded_texture();
- emit_signal(SNAME("changed"));
+ _try_emit_changed();
}
int TileSetAtlasSource::create_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_id_override) {
@@ -5359,7 +5359,7 @@ int TileSetAtlasSource::create_alternative_tile(const Vector2i p_atlas_coords, i
tiles[p_atlas_coords].alternatives_ids.sort();
_compute_next_alternative_id(p_atlas_coords);
- emit_signal(SNAME("changed"));
+ _try_emit_changed();
return new_alternative_id;
}
@@ -5375,7 +5375,7 @@ void TileSetAtlasSource::remove_alternative_tile(const Vector2i p_atlas_coords,
tiles[p_atlas_coords].alternatives_ids.erase(p_alternative_tile);
tiles[p_atlas_coords].alternatives_ids.sort();
- emit_signal(SNAME("changed"));
+ _try_emit_changed();
}
void TileSetAtlasSource::set_alternative_tile_id(const Vector2i p_atlas_coords, int p_alternative_tile, int p_new_id) {
@@ -5393,7 +5393,7 @@ void TileSetAtlasSource::set_alternative_tile_id(const Vector2i p_atlas_coords,
tiles[p_atlas_coords].alternatives_ids.erase(p_alternative_tile);
tiles[p_atlas_coords].alternatives_ids.sort();
- emit_signal(SNAME("changed"));
+ _try_emit_changed();
}
bool TileSetAtlasSource::has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const {
@@ -5427,6 +5427,12 @@ TileData *TileSetAtlasSource::get_tile_data(const Vector2i p_atlas_coords, int p
return tiles[p_atlas_coords].alternatives[p_alternative_tile];
}
+void TileSetAtlasSource::_notification(int p_notification) {
+ if (p_notification == NOTIFICATION_POSTINITIALIZE) {
+ initializing = false;
+ }
+}
+
void TileSetAtlasSource::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_texture", "texture"), &TileSetAtlasSource::set_texture);
ClassDB::bind_method(D_METHOD("get_texture"), &TileSetAtlasSource::get_texture);
@@ -5569,6 +5575,9 @@ void TileSetAtlasSource::_create_coords_mapping_cache(Vector2i p_atlas_coords) {
}
void TileSetAtlasSource::_queue_update_padded_texture() {
+ if (padded_texture_needs_update) {
+ return;
+ }
padded_texture_needs_update = true;
callable_mp(this, &TileSetAtlasSource::_update_padded_texture).call_deferred();
}
@@ -5677,6 +5686,12 @@ void TileSetAtlasSource::_update_padded_texture() {
emit_changed();
}
+void TileSetAtlasSource::_try_emit_changed() {
+ if (!initializing) {
+ emit_changed();
+ }
+}
+
/////////////////////////////// TileSetScenesCollectionSource //////////////////////////////////////
void TileSetScenesCollectionSource::_compute_next_alternative_id() {
@@ -5685,6 +5700,12 @@ void TileSetScenesCollectionSource::_compute_next_alternative_id() {
};
}
+void TileSetScenesCollectionSource::_try_emit_changed() {
+ if (!initializing) {
+ emit_changed();
+ }
+}
+
int TileSetScenesCollectionSource::get_tiles_count() const {
return 1;
}
@@ -5725,7 +5746,7 @@ int TileSetScenesCollectionSource::create_scene_tile(Ref<PackedScene> p_packed_s
set_scene_tile_scene(new_scene_id, p_packed_scene);
_compute_next_alternative_id();
- emit_signal(SNAME("changed"));
+ _try_emit_changed();
return new_scene_id;
}
@@ -5745,7 +5766,7 @@ void TileSetScenesCollectionSource::set_scene_tile_id(int p_id, int p_new_id) {
scenes.erase(p_id);
scenes_ids.erase(p_id);
- emit_signal(SNAME("changed"));
+ _try_emit_changed();
}
void TileSetScenesCollectionSource::set_scene_tile_scene(int p_id, Ref<PackedScene> p_packed_scene) {
@@ -5769,7 +5790,7 @@ void TileSetScenesCollectionSource::set_scene_tile_scene(int p_id, Ref<PackedSce
} else {
scenes[p_id].scene = Ref<PackedScene>();
}
- emit_signal(SNAME("changed"));
+ _try_emit_changed();
}
Ref<PackedScene> TileSetScenesCollectionSource::get_scene_tile_scene(int p_id) const {
@@ -5782,7 +5803,7 @@ void TileSetScenesCollectionSource::set_scene_tile_display_placeholder(int p_id,
scenes[p_id].display_placeholder = p_display_placeholder;
- emit_signal(SNAME("changed"));
+ _try_emit_changed();
}
bool TileSetScenesCollectionSource::get_scene_tile_display_placeholder(int p_id) const {
@@ -5795,7 +5816,7 @@ void TileSetScenesCollectionSource::remove_scene_tile(int p_id) {
scenes.erase(p_id);
scenes_ids.erase(p_id);
- emit_signal(SNAME("changed"));
+ _try_emit_changed();
}
int TileSetScenesCollectionSource::get_next_scene_tile_id() const {
@@ -5854,6 +5875,12 @@ void TileSetScenesCollectionSource::_get_property_list(List<PropertyInfo> *p_lis
}
}
+void TileSetScenesCollectionSource::_notification(int p_notification) {
+ if (p_notification == NOTIFICATION_POSTINITIALIZE) {
+ initializing = false;
+ }
+}
+
void TileSetScenesCollectionSource::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_scene_tiles_count"), &TileSetScenesCollectionSource::get_scene_tiles_count);
ClassDB::bind_method(D_METHOD("get_scene_tile_id", "index"), &TileSetScenesCollectionSource::get_scene_tile_id);
diff --git a/scene/resources/2d/tile_set.h b/scene/resources/2d/tile_set.h
index c17241a436..f1a8d42b95 100644
--- a/scene/resources/2d/tile_set.h
+++ b/scene/resources/2d/tile_set.h
@@ -637,6 +637,8 @@ private:
int next_alternative_id = 1;
};
+ bool initializing = true;
+
Ref<Texture2D> texture;
Vector2i margins;
Vector2i separation;
@@ -660,12 +662,14 @@ private:
void _queue_update_padded_texture();
Ref<ImageTexture> _create_padded_image_texture(const Ref<Texture2D> &p_source);
void _update_padded_texture();
+ void _try_emit_changed();
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
+ void _notification(int p_notification);
static void _bind_methods();
public:
@@ -779,13 +783,17 @@ private:
HashMap<int, SceneData> scenes;
int next_scene_id = 1;
+ bool initializing = true;
+
void _compute_next_alternative_id();
+ void _try_emit_changed();
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
+ void _notification(int p_notification);
static void _bind_methods();
public:
diff --git a/scene/resources/3d/primitive_meshes.cpp b/scene/resources/3d/primitive_meshes.cpp
index c2b2a6d68b..ee772f960a 100644
--- a/scene/resources/3d/primitive_meshes.cpp
+++ b/scene/resources/3d/primitive_meshes.cpp
@@ -2845,10 +2845,8 @@ void TextMesh::_generate_glyph_mesh_data(const GlyphMeshKey &p_key, const Glyph
for (int j = 0; j < gl_data.contours[i].size(); j++) {
int next = (j + 1 == gl_data.contours[i].size()) ? 0 : (j + 1);
- gl_data.min_p.x = MIN(gl_data.min_p.x, gl_data.contours[i][j].point.x);
- gl_data.min_p.y = MIN(gl_data.min_p.y, gl_data.contours[i][j].point.y);
- gl_data.max_p.x = MAX(gl_data.max_p.x, gl_data.contours[i][j].point.x);
- gl_data.max_p.y = MAX(gl_data.max_p.y, gl_data.contours[i][j].point.y);
+ gl_data.min_p = gl_data.min_p.min(gl_data.contours[i][j].point);
+ gl_data.max_p = gl_data.max_p.max(gl_data.contours[i][j].point);
length += (gl_data.contours[i][next].point - gl_data.contours[i][j].point).length();
inp.GetPoint(j) = gl_data.contours[i][j].point;
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index 079101f679..cd530f100e 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -1041,7 +1041,7 @@ bool Animation::track_get_interpolation_loop_wrap(int p_track) const {
return tracks[p_track]->loop_wrap;
}
-template <class T, class V>
+template <typename T, typename V>
int Animation::_insert(double p_time, T &p_keys, const V &p_value) {
int idx = p_keys.size();
@@ -1065,7 +1065,7 @@ int Animation::_insert(double p_time, T &p_keys, const V &p_value) {
return -1;
}
-template <class T>
+template <typename T>
void Animation::_clear(T &p_keys) {
p_keys.clear();
}
@@ -2331,7 +2331,7 @@ void Animation::track_set_key_transition(int p_track, int p_key_idx, real_t p_tr
emit_changed();
}
-template <class K>
+template <typename K>
int Animation::_find(const Vector<K> &p_keys, double p_time, bool p_backward, bool p_limit) const {
int len = p_keys.size();
if (len == 0) {
@@ -2451,7 +2451,7 @@ Variant Animation::_cubic_interpolate_angle_in_time(const Variant &p_pre_a, cons
return _cubic_interpolate_in_time(p_pre_a, p_a, p_b, p_post_b, p_c, p_pre_a_t, p_b_t, p_post_b_t);
}
-template <class T>
+template <typename T>
T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok, bool p_backward) const {
int len = _find(p_keys, length) + 1; // try to find last key (there may be more past the end)
@@ -2693,7 +2693,7 @@ Animation::UpdateMode Animation::value_track_get_update_mode(int p_track) const
return vt->update_mode;
}
-template <class T>
+template <typename T>
void Animation::_track_get_key_indices_in_range(const Vector<T> &p_array, double from_time, double to_time, List<int> *p_indices, bool p_is_backward) const {
int len = p_array.size();
if (len == 0) {
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index 5b27958005..c72327e464 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -75,6 +75,7 @@ public:
LOOP_PINGPONG,
};
+ // LoopedFlag is used in Animataion to "process the keys at both ends correct".
enum LoopedFlag {
LOOPED_FLAG_NONE,
LOOPED_FLAG_END,
@@ -120,7 +121,7 @@ private:
};
// Transform key holds either Vector3 or Quaternion.
- template <class T>
+ template <typename T>
struct TKey : public Key {
T value;
};
@@ -187,6 +188,7 @@ private:
};
/* BEZIER TRACK */
+
struct BezierKey {
Vector2 in_handle; // Relative (x always <0)
Vector2 out_handle; // Relative (x always >0)
@@ -223,7 +225,7 @@ private:
}
};
- /* AUDIO TRACK */
+ /* ANIMATION TRACK */
struct AnimationTrack : public Track {
Vector<TKey<StringName>> values;
@@ -235,13 +237,13 @@ private:
Vector<Track *> tracks;
- template <class T>
+ template <typename T>
void _clear(T &p_keys);
- template <class T, class V>
+ template <typename T, typename V>
int _insert(double p_time, T &p_keys, const V &p_value);
- template <class K>
+ template <typename K>
inline int _find(const Vector<K> &p_keys, double p_time, bool p_backward = false, bool p_limit = false) const;
@@ -257,10 +259,10 @@ private:
_FORCE_INLINE_ real_t _cubic_interpolate_in_time(const real_t &p_pre_a, const real_t &p_a, const real_t &p_b, const real_t &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const;
_FORCE_INLINE_ Variant _cubic_interpolate_angle_in_time(const Variant &p_pre_a, const Variant &p_a, const Variant &p_b, const Variant &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const;
- template <class T>
+ template <typename T>
_FORCE_INLINE_ T _interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok, bool p_backward = false) const;
- template <class T>
+ template <typename T>
_FORCE_INLINE_ void _track_get_key_indices_in_range(const Vector<T> &p_array, double from_time, double to_time, List<int> *p_indices, bool p_is_backward) const;
double length = 1.0;
diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp
index b6e6493b68..0185c6ef85 100644
--- a/scene/resources/audio_stream_wav.cpp
+++ b/scene/resources/audio_stream_wav.cpp
@@ -86,7 +86,7 @@ void AudioStreamPlaybackWAV::seek(double p_time) {
offset = uint64_t(p_time * base->mix_rate) << MIX_FRAC_BITS;
}
-template <class Depth, bool is_stereo, bool is_ima_adpcm>
+template <typename Depth, bool is_stereo, bool is_ima_adpcm>
void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int32_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm) {
// this function will be compiled branchless by any decent compiler
diff --git a/scene/resources/audio_stream_wav.h b/scene/resources/audio_stream_wav.h
index f150a17d21..959d1ceca0 100644
--- a/scene/resources/audio_stream_wav.h
+++ b/scene/resources/audio_stream_wav.h
@@ -60,7 +60,7 @@ class AudioStreamPlaybackWAV : public AudioStreamPlayback {
friend class AudioStreamWAV;
Ref<AudioStreamWAV> base;
- template <class Depth, bool is_stereo, bool is_ima_adpcm>
+ template <typename Depth, bool is_stereo, bool is_ima_adpcm>
void do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int32_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm);
public:
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index 322a8a853e..b381096df8 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -661,20 +661,19 @@ void BaseMaterial3D::_update_shader() {
}
if (flags[FLAG_USE_TEXTURE_REPEAT]) {
- texfilter_str += ",repeat_enable";
- texfilter_height_str += ",repeat_enable";
+ texfilter_str += ", repeat_enable";
+ texfilter_height_str += ", repeat_enable";
} else {
- texfilter_str += ",repeat_disable";
- texfilter_height_str += ",repeat_disable";
+ texfilter_str += ", repeat_disable";
+ texfilter_height_str += ", repeat_disable";
}
- //must create a shader!
-
// Add a comment to describe the shader origin (useful when converting to ShaderMaterial).
String code = vformat(
"// NOTE: Shader automatically converted from " VERSION_NAME " " VERSION_FULL_CONFIG "'s %s.\n\n",
orm ? "ORMMaterial3D" : "StandardMaterial3D");
+ // Define shader type and render mode based on property values.
code += "shader_type spatial;\nrender_mode ";
switch (blend_mode) {
case BLEND_MODE_MIX:
@@ -700,13 +699,13 @@ void BaseMaterial3D::_update_shader() {
switch (ddm) {
case DEPTH_DRAW_OPAQUE_ONLY:
- code += ",depth_draw_opaque";
+ code += ", depth_draw_opaque";
break;
case DEPTH_DRAW_ALWAYS:
- code += ",depth_draw_always";
+ code += ", depth_draw_always";
break;
case DEPTH_DRAW_DISABLED:
- code += ",depth_draw_never";
+ code += ", depth_draw_never";
break;
case DEPTH_DRAW_MAX:
break; // Internal value, skip.
@@ -714,238 +713,291 @@ void BaseMaterial3D::_update_shader() {
switch (cull_mode) {
case CULL_BACK:
- code += ",cull_back";
+ code += ", cull_back";
break;
case CULL_FRONT:
- code += ",cull_front";
+ code += ", cull_front";
break;
case CULL_DISABLED:
- code += ",cull_disabled";
+ code += ", cull_disabled";
break;
case CULL_MAX:
break; // Internal value, skip.
}
switch (diffuse_mode) {
case DIFFUSE_BURLEY:
- code += ",diffuse_burley";
+ code += ", diffuse_burley";
break;
case DIFFUSE_LAMBERT:
- code += ",diffuse_lambert";
+ code += ", diffuse_lambert";
break;
case DIFFUSE_LAMBERT_WRAP:
- code += ",diffuse_lambert_wrap";
+ code += ", diffuse_lambert_wrap";
break;
case DIFFUSE_TOON:
- code += ",diffuse_toon";
+ code += ", diffuse_toon";
break;
case DIFFUSE_MAX:
break; // Internal value, skip.
}
switch (specular_mode) {
case SPECULAR_SCHLICK_GGX:
- code += ",specular_schlick_ggx";
+ code += ", specular_schlick_ggx";
break;
case SPECULAR_TOON:
- code += ",specular_toon";
+ code += ", specular_toon";
break;
case SPECULAR_DISABLED:
- code += ",specular_disabled";
+ code += ", specular_disabled";
break;
case SPECULAR_MAX:
break; // Internal value, skip.
}
if (features[FEATURE_SUBSURFACE_SCATTERING] && flags[FLAG_SUBSURFACE_MODE_SKIN]) {
- code += ",sss_mode_skin";
+ code += ", sss_mode_skin";
}
if (shading_mode == SHADING_MODE_UNSHADED) {
- code += ",unshaded";
+ code += ", unshaded";
}
if (flags[FLAG_DISABLE_DEPTH_TEST]) {
- code += ",depth_test_disabled";
+ code += ", depth_test_disabled";
}
if (flags[FLAG_PARTICLE_TRAILS_MODE]) {
- code += ",particle_trails";
+ code += ", particle_trails";
}
if (shading_mode == SHADING_MODE_PER_VERTEX) {
- code += ",vertex_lighting";
+ code += ", vertex_lighting";
}
if (flags[FLAG_DONT_RECEIVE_SHADOWS]) {
- code += ",shadows_disabled";
+ code += ", shadows_disabled";
}
if (flags[FLAG_DISABLE_AMBIENT_LIGHT]) {
- code += ",ambient_light_disabled";
+ code += ", ambient_light_disabled";
}
if (flags[FLAG_USE_SHADOW_TO_OPACITY]) {
- code += ",shadow_to_opacity";
+ code += ", shadow_to_opacity";
}
if (flags[FLAG_DISABLE_FOG]) {
- code += ",fog_disabled";
+ code += ", fog_disabled";
}
if (transparency == TRANSPARENCY_ALPHA_DEPTH_PRE_PASS) {
- code += ",depth_prepass_alpha";
+ code += ", depth_prepass_alpha";
}
- // Although its technically possible to do alpha antialiasing without using alpha hash or alpha scissor,
+ // Although it's technically possible to do alpha antialiasing without using alpha hash or alpha scissor,
// it is restricted in the base material because it has no use, and abusing it with regular Alpha blending can
- // saturate the MSAA mask
+ // saturate the MSAA mask.
if (transparency == TRANSPARENCY_ALPHA_HASH || transparency == TRANSPARENCY_ALPHA_SCISSOR) {
- // alpha antialiasing is only useful in ALPHA_HASH or ALPHA_SCISSOR
+ // Alpha antialiasing is only useful with ALPHA_HASH or ALPHA_SCISSOR.
if (alpha_antialiasing_mode == ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE) {
- code += ",alpha_to_coverage";
+ code += ", alpha_to_coverage";
} else if (alpha_antialiasing_mode == ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE_AND_TO_ONE) {
- code += ",alpha_to_coverage_and_one";
+ code += ", alpha_to_coverage_and_one";
}
}
code += ";\n";
- code += "uniform vec4 albedo : source_color;\n";
- code += "uniform sampler2D texture_albedo : source_color," + texfilter_str + ";\n";
+ // Generate list of uniforms.
+ code += vformat(R"(
+uniform vec4 albedo : source_color;
+uniform sampler2D texture_albedo : source_color, %s;
+)",
+ texfilter_str);
+
if (grow_enabled) {
- code += "uniform float grow;\n";
+ code += "uniform float grow : hint_range(-16.0, 16.0, 0.001);\n";
}
if (proximity_fade_enabled) {
- code += "uniform float proximity_fade_distance;\n";
+ code += "uniform float proximity_fade_distance : hint_range(0.0, 4096.0, 0.01);\n";
}
if (distance_fade != DISTANCE_FADE_DISABLED) {
- code += "uniform float distance_fade_min;\n";
- code += "uniform float distance_fade_max;\n";
+ code += R"(
+uniform float distance_fade_min : hint_range(0.0, 4096.0, 0.01);
+uniform float distance_fade_max : hint_range(0.0, 4096.0, 0.01);
+)";
}
if (flags[FLAG_ALBEDO_TEXTURE_MSDF]) {
- code += "uniform float msdf_pixel_range;\n";
- code += "uniform float msdf_outline_size;\n";
+ code += R"(
+uniform float msdf_pixel_range : hint_range(1.0, 100.0, 1.0);
+uniform float msdf_outline_size : hint_range(0.0, 250.0, 1.0);
+)";
}
- // alpha scissor is only valid if there is not antialiasing edge
- // alpha hash is valid whenever, but not with alpha scissor
+ // Alpha scissor is only valid if there is no antialiasing edge.
+ // Alpha hash is valid whenever, but not with alpha scissor.
if (transparency == TRANSPARENCY_ALPHA_SCISSOR) {
- code += "uniform float alpha_scissor_threshold;\n";
+ code += "uniform float alpha_scissor_threshold : hint_range(0.0, 1.0, 0.001);\n";
} else if (transparency == TRANSPARENCY_ALPHA_HASH) {
- code += "uniform float alpha_hash_scale;\n";
+ code += "uniform float alpha_hash_scale : hint_range(0.0, 2.0, 0.01);\n";
}
- // if alpha antialiasing isn't off, add in the edge variable
+ // If alpha antialiasing isn't off, add in the edge variable.
if (alpha_antialiasing_mode != ALPHA_ANTIALIASING_OFF &&
(transparency == TRANSPARENCY_ALPHA_SCISSOR || transparency == TRANSPARENCY_ALPHA_HASH)) {
- code += "uniform float alpha_antialiasing_edge;\n";
- code += "uniform ivec2 albedo_texture_size;\n";
+ code += R"(
+uniform float alpha_antialiasing_edge : hint_range(0.0, 1.0, 0.01);
+uniform ivec2 albedo_texture_size;
+)";
}
- code += "uniform float point_size : hint_range(0,128);\n";
+ code += "uniform float point_size : hint_range(0.1, 128.0, 0.1);\n";
- //TODO ALL HINTS
if (!orm) {
- code += "uniform float roughness : hint_range(0,1);\n";
- code += "uniform sampler2D texture_metallic : hint_default_white," + texfilter_str + ";\n";
- code += "uniform vec4 metallic_texture_channel;\n";
+ code += vformat(R"(
+uniform float roughness : hint_range(0.0, 1.0);
+uniform sampler2D texture_metallic : hint_default_white, %s;
+uniform vec4 metallic_texture_channel;
+)",
+ texfilter_str);
switch (roughness_texture_channel) {
case TEXTURE_CHANNEL_RED: {
- code += "uniform sampler2D texture_roughness : hint_roughness_r," + texfilter_str + ";\n";
+ code += vformat("uniform sampler2D texture_roughness : hint_roughness_r, %s;\n", texfilter_str);
} break;
case TEXTURE_CHANNEL_GREEN: {
- code += "uniform sampler2D texture_roughness : hint_roughness_g," + texfilter_str + ";\n";
+ code += vformat("uniform sampler2D texture_roughness : hint_roughness_g, %s;\n", texfilter_str);
} break;
case TEXTURE_CHANNEL_BLUE: {
- code += "uniform sampler2D texture_roughness : hint_roughness_b," + texfilter_str + ";\n";
+ code += vformat("uniform sampler2D texture_roughness : hint_roughness_b, %s;\n", texfilter_str);
} break;
case TEXTURE_CHANNEL_ALPHA: {
- code += "uniform sampler2D texture_roughness : hint_roughness_a," + texfilter_str + ";\n";
+ code += vformat("uniform sampler2D texture_roughness : hint_roughness_a, %s;\n", texfilter_str);
} break;
case TEXTURE_CHANNEL_GRAYSCALE: {
- code += "uniform sampler2D texture_roughness : hint_roughness_gray," + texfilter_str + ";\n";
+ code += vformat("uniform sampler2D texture_roughness : hint_roughness_gray, %s;\n", texfilter_str);
} break;
case TEXTURE_CHANNEL_MAX:
break; // Internal value, skip.
}
- code += "uniform float specular;\n";
- code += "uniform float metallic;\n";
+ code += R"(
+uniform float specular : hint_range(0.0, 1.0, 0.01);
+uniform float metallic : hint_range(0.0, 1.0, 0.01);
+)";
} else {
- code += "uniform sampler2D texture_orm : hint_roughness_g," + texfilter_str + ";\n";
+ code += "uniform sampler2D texture_orm : hint_roughness_g, " + texfilter_str + ";\n";
}
if (billboard_mode == BILLBOARD_PARTICLES) {
- code += "uniform int particles_anim_h_frames;\n";
- code += "uniform int particles_anim_v_frames;\n";
- code += "uniform bool particles_anim_loop;\n";
+ code += R"(
+uniform int particles_anim_h_frames : hint_range(1, 128);
+uniform int particles_anim_v_frames : hint_range(1, 128);
+uniform bool particles_anim_loop;
+)";
}
if (features[FEATURE_EMISSION]) {
- code += "uniform sampler2D texture_emission : source_color, hint_default_black," + texfilter_str + ";\n";
- code += "uniform vec4 emission : source_color;\n";
- code += "uniform float emission_energy;\n";
+ code += vformat(R"(
+uniform sampler2D texture_emission : source_color, hint_default_black, %s;
+uniform vec4 emission : source_color;
+uniform float emission_energy : hint_range(0.0, 100.0, 0.01);
+)",
+ texfilter_str);
}
if (features[FEATURE_REFRACTION]) {
- code += "uniform sampler2D texture_refraction : " + texfilter_str + ";\n";
- code += "uniform float refraction : hint_range(-16,16);\n";
- code += "uniform vec4 refraction_texture_channel;\n";
+ code += vformat(R"(
+uniform sampler2D texture_refraction : %s;
+uniform float refraction : hint_range(-1.0, 1.0, 0.001);
+uniform vec4 refraction_texture_channel;
+)",
+ texfilter_str);
}
if (features[FEATURE_REFRACTION]) {
- code += "uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear_mipmap;";
+ code += "uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear_mipmap;\n";
}
if (proximity_fade_enabled) {
- code += "uniform sampler2D depth_texture : hint_depth_texture, repeat_disable, filter_nearest;";
+ code += "uniform sampler2D depth_texture : hint_depth_texture, repeat_disable, filter_nearest;\n";
}
if (features[FEATURE_NORMAL_MAPPING]) {
- code += "uniform sampler2D texture_normal : hint_roughness_normal," + texfilter_str + ";\n";
- code += "uniform float normal_scale : hint_range(-16,16);\n";
+ code += vformat(R"(
+uniform sampler2D texture_normal : hint_roughness_normal, %s;
+uniform float normal_scale : hint_range(-16.0, 16.0);
+)",
+ texfilter_str);
}
if (features[FEATURE_RIM]) {
- code += "uniform float rim : hint_range(0,1);\n";
- code += "uniform float rim_tint : hint_range(0,1);\n";
- code += "uniform sampler2D texture_rim : hint_default_white," + texfilter_str + ";\n";
+ code += vformat(R"(
+uniform float rim : hint_range(0.0, 1.0, 0.01);
+uniform float rim_tint : hint_range(0.0, 1.0, 0.01);
+uniform sampler2D texture_rim : hint_default_white, %s;
+)",
+ texfilter_str);
}
if (features[FEATURE_CLEARCOAT]) {
- code += "uniform float clearcoat : hint_range(0,1);\n";
- code += "uniform float clearcoat_roughness : hint_range(0,1);\n";
- code += "uniform sampler2D texture_clearcoat : hint_default_white," + texfilter_str + ";\n";
+ code += vformat(R"(
+uniform float clearcoat : hint_range(0.0, 1.0, 0.01);
+uniform float clearcoat_roughness : hint_range(0.0, 1.0, 0.01);
+uniform sampler2D texture_clearcoat : hint_default_white, %s;
+)",
+ texfilter_str);
}
if (features[FEATURE_ANISOTROPY]) {
- code += "uniform float anisotropy_ratio : hint_range(0,256);\n";
- code += "uniform sampler2D texture_flowmap : hint_anisotropy," + texfilter_str + ";\n";
+ code += vformat(R"(
+uniform float anisotropy_ratio : hint_range(0.0, 1.0, 0.01);
+uniform sampler2D texture_flowmap : hint_anisotropy, %s;
+)",
+ texfilter_str);
}
if (features[FEATURE_AMBIENT_OCCLUSION]) {
- code += "uniform sampler2D texture_ambient_occlusion : hint_default_white, " + texfilter_str + ";\n";
- code += "uniform vec4 ao_texture_channel;\n";
- code += "uniform float ao_light_affect;\n";
+ code += vformat(R"(
+uniform sampler2D texture_ambient_occlusion : hint_default_white, %s;
+uniform vec4 ao_texture_channel;
+uniform float ao_light_affect : hint_range(0.0, 1.0, 0.01);
+)",
+ texfilter_str);
}
if (features[FEATURE_DETAIL]) {
- code += "uniform sampler2D texture_detail_albedo : source_color," + texfilter_str + ";\n";
- code += "uniform sampler2D texture_detail_normal : hint_normal," + texfilter_str + ";\n";
- code += "uniform sampler2D texture_detail_mask : hint_default_white," + texfilter_str + ";\n";
+ code += vformat(R"(
+uniform sampler2D texture_detail_albedo : source_color, %s;
+uniform sampler2D texture_detail_normal : hint_normal, %s;
+uniform sampler2D texture_detail_mask : hint_default_white, %s;
+)",
+ texfilter_str, texfilter_str, texfilter_str);
}
if (features[FEATURE_SUBSURFACE_SCATTERING]) {
- code += "uniform float subsurface_scattering_strength : hint_range(0,1);\n";
- code += "uniform sampler2D texture_subsurface_scattering : hint_default_white," + texfilter_str + ";\n";
+ code += vformat(R"(
+uniform float subsurface_scattering_strength : hint_range(0.0, 1.0, 0.01);
+uniform sampler2D texture_subsurface_scattering : hint_default_white, %s;
+)",
+ texfilter_str);
}
if (features[FEATURE_SUBSURFACE_TRANSMITTANCE]) {
- code += "uniform vec4 transmittance_color : source_color;\n";
- code += "uniform float transmittance_depth;\n";
- code += "uniform sampler2D texture_subsurface_transmittance : hint_default_white," + texfilter_str + ";\n";
- code += "uniform float transmittance_boost;\n";
+ code += vformat(R"(
+uniform vec4 transmittance_color : source_color;
+uniform float transmittance_depth : hint_range(0.001, 8.0, 0.001);
+uniform sampler2D texture_subsurface_transmittance : hint_default_white, %s;
+uniform float transmittance_boost : hint_range(0.0, 1.0, 0.01);
+)",
+ texfilter_str);
}
if (features[FEATURE_BACKLIGHT]) {
- code += "uniform vec4 backlight : source_color;\n";
- code += "uniform sampler2D texture_backlight : hint_default_black," + texfilter_str + ";\n";
+ code += vformat(R"(
+uniform vec4 backlight : source_color;
+uniform sampler2D texture_backlight : hint_default_black, %s;
+)",
+ texfilter_str);
}
if (features[FEATURE_HEIGHT_MAPPING]) {
- code += "uniform sampler2D texture_heightmap : hint_default_black," + texfilter_height_str + ";\n";
- code += "uniform float heightmap_scale;\n";
- code += "uniform int heightmap_min_layers;\n";
- code += "uniform int heightmap_max_layers;\n";
- code += "uniform vec2 heightmap_flip;\n";
+ code += vformat(R"(
+uniform sampler2D texture_heightmap : hint_default_black, %s;
+uniform float heightmap_scale : hint_range(-16.0, 16.0, 0.001);
+uniform int heightmap_min_layers : hint_range(1, 64);
+uniform int heightmap_max_layers : hint_range(1, 64);
+uniform vec2 heightmap_flip;
+)",
+ texfilter_height_str);
}
if (flags[FLAG_UV1_USE_TRIPLANAR]) {
code += "varying vec3 uv1_triplanar_pos;\n";
@@ -954,132 +1006,216 @@ void BaseMaterial3D::_update_shader() {
code += "varying vec3 uv2_triplanar_pos;\n";
}
if (flags[FLAG_UV1_USE_TRIPLANAR]) {
- code += "uniform float uv1_blend_sharpness;\n";
- code += "varying vec3 uv1_power_normal;\n";
+ code += R"(
+uniform float uv1_blend_sharpness : hint_range(0.0, 150.0, 0.001);
+varying vec3 uv1_power_normal;
+)";
}
if (flags[FLAG_UV2_USE_TRIPLANAR]) {
- code += "uniform float uv2_blend_sharpness;\n";
- code += "varying vec3 uv2_power_normal;\n";
+ code += R"(uniform float uv2_blend_sharpness : hint_range(0.0, 150.0, 0.001);
+varying vec3 uv2_power_normal;
+)";
}
- code += "uniform vec3 uv1_scale;\n";
- code += "uniform vec3 uv1_offset;\n";
- code += "uniform vec3 uv2_scale;\n";
- code += "uniform vec3 uv2_offset;\n";
-
- code += "\n\n";
+ code += R"(
+uniform vec3 uv1_scale;
+uniform vec3 uv1_offset;
+uniform vec3 uv2_scale;
+uniform vec3 uv2_offset;
+)";
- code += "void vertex() {\n";
+ // Generate vertex shader.
+ code += R"(
+void vertex() {)";
if (flags[FLAG_SRGB_VERTEX_COLOR]) {
- code += " if (!OUTPUT_IS_SRGB) {\n";
- code += " COLOR.rgb = mix(pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb * (1.0 / 12.92), lessThan(COLOR.rgb, vec3(0.04045)));\n";
- code += " }\n";
+ code += R"(
+ // Vertex Color is sRGB: Enabled
+ if (!OUTPUT_IS_SRGB) {
+ COLOR.rgb = mix(
+ pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)),
+ COLOR.rgb * (1.0 / 12.92),
+ lessThan(COLOR.rgb, vec3(0.04045)));
+ }
+)";
}
if (flags[FLAG_USE_POINT_SIZE]) {
- code += " POINT_SIZE=point_size;\n";
+ code += R"(
+ // Use Point Size: Enabled
+ POINT_SIZE = point_size;
+)";
}
if (shading_mode == SHADING_MODE_PER_VERTEX) {
- code += " ROUGHNESS=roughness;\n";
+ code += R"(
+ // Shading Mode: Per Vertex
+ ROUGHNESS = roughness;
+)";
}
if (!flags[FLAG_UV1_USE_TRIPLANAR]) {
- code += " UV=UV*uv1_scale.xy+uv1_offset.xy;\n";
+ code += R"(
+ UV = UV * uv1_scale.xy + uv1_offset.xy;
+)";
+ }
+
+ if (detail_uv == DETAIL_UV_2 && !flags[FLAG_UV2_USE_TRIPLANAR]) {
+ // Don't add a newline if the UV assignment above is already performed,
+ // so that UV1 and UV2 are closer to each other.
+ if (flags[FLAG_UV1_USE_TRIPLANAR]) {
+ code += "\n";
+ }
+ code += R"( // Detail UV Layer: UV2
+ UV2 = UV2 * uv2_scale.xy + uv2_offset.xy;
+)";
}
switch (billboard_mode) {
case BILLBOARD_DISABLED: {
} break;
case BILLBOARD_ENABLED: {
- // MAIN_CAM_INV_VIEW_MATRIX is inverse of the camera, even on shadow passes: this ensures the billboard faces the camera when casting shadows.
- code += " MODELVIEW_MATRIX = VIEW_MATRIX * mat4(MAIN_CAM_INV_VIEW_MATRIX[0], MAIN_CAM_INV_VIEW_MATRIX[1], MAIN_CAM_INV_VIEW_MATRIX[2], MODEL_MATRIX[3]);\n";
-
+ // `MAIN_CAM_INV_VIEW_MATRIX` is inverse of the camera, even on shadow passes.
+ // This ensures the billboard faces the camera when casting shadows.
+ code += R"(
+ // Billboard Mode: Enabled
+ MODELVIEW_MATRIX = VIEW_MATRIX * mat4(
+ MAIN_CAM_INV_VIEW_MATRIX[0],
+ MAIN_CAM_INV_VIEW_MATRIX[1],
+ MAIN_CAM_INV_VIEW_MATRIX[2],
+ MODEL_MATRIX[3]);
+)";
if (flags[FLAG_BILLBOARD_KEEP_SCALE]) {
- code += " MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(length(MODEL_MATRIX[0].xyz), 0.0, 0.0, 0.0), vec4(0.0, length(MODEL_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, length(MODEL_MATRIX[2].xyz), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n";
+ code += R"(
+ // Billboard Keep Scale: Enabled
+ MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(
+ vec4(length(MODEL_MATRIX[0].xyz), 0.0, 0.0, 0.0),
+ vec4(0.0, length(MODEL_MATRIX[1].xyz), 0.0, 0.0),
+ vec4(0.0, 0.0, length(MODEL_MATRIX[2].xyz), 0.0),
+ vec4(0.0, 0.0, 0.0, 1.0));
+)";
}
code += " MODELVIEW_NORMAL_MATRIX = mat3(MODELVIEW_MATRIX);\n";
} break;
case BILLBOARD_FIXED_Y: {
- // MAIN_CAM_INV_VIEW_MATRIX is inverse of the camera, even on shadow passes: this ensures the billboard faces the camera when casting shadows.
- code += " MODELVIEW_MATRIX = VIEW_MATRIX * mat4(vec4(normalize(cross(vec3(0.0, 1.0, 0.0), MAIN_CAM_INV_VIEW_MATRIX[2].xyz)), 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(normalize(cross(MAIN_CAM_INV_VIEW_MATRIX[0].xyz, vec3(0.0, 1.0, 0.0))), 0.0), MODEL_MATRIX[3]);\n";
-
+ // `MAIN_CAM_INV_VIEW_MATRIX` is inverse of the camera, even on shadow passes.
+ // This ensures the billboard faces the camera when casting shadows.
+ code += R"(
+ // Billboard Mode: Y-Billboard
+ MODELVIEW_MATRIX = VIEW_MATRIX * mat4(
+ vec4(normalize(cross(vec3(0.0, 1.0, 0.0), MAIN_CAM_INV_VIEW_MATRIX[2].xyz)), 0.0),
+ vec4(0.0, 1.0, 0.0, 0.0),
+ vec4(normalize(cross(MAIN_CAM_INV_VIEW_MATRIX[0].xyz, vec3(0.0, 1.0, 0.0))), 0.0),
+ MODEL_MATRIX[3]);
+)";
if (flags[FLAG_BILLBOARD_KEEP_SCALE]) {
- code += " MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(length(MODEL_MATRIX[0].xyz), 0.0, 0.0, 0.0), vec4(0.0, length(MODEL_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, length(MODEL_MATRIX[2].xyz), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n";
+ code += R"(
+ // Billboard Keep Scale: Enabled
+ MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(
+ vec4(length(MODEL_MATRIX[0].xyz), 0.0, 0.0, 0.0),
+ vec4(0.0, length(MODEL_MATRIX[1].xyz), 0.0, 0.0),
+ vec4(0.0, 0.0, length(MODEL_MATRIX[2].xyz), 0.0),
+ vec4(0.0, 0.0, 0.0, 1.0));
+)";
}
code += " MODELVIEW_NORMAL_MATRIX = mat3(MODELVIEW_MATRIX);\n";
} break;
case BILLBOARD_PARTICLES: {
- //make billboard
- code += " mat4 mat_world = mat4(normalize(INV_VIEW_MATRIX[0]), normalize(INV_VIEW_MATRIX[1]) ,normalize(INV_VIEW_MATRIX[2]), MODEL_MATRIX[3]);\n";
- //rotate by rotation
- code += " mat_world = mat_world * mat4(vec4(cos(INSTANCE_CUSTOM.x), -sin(INSTANCE_CUSTOM.x), 0.0, 0.0), vec4(sin(INSTANCE_CUSTOM.x), cos(INSTANCE_CUSTOM.x), 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n";
- //set modelview
+ // Make billboard and rotated by rotation.
+ code += R"(
+ // Billboard Mode: Particles
+ mat4 mat_world = mat4(
+ normalize(INV_VIEW_MATRIX[0]),
+ normalize(INV_VIEW_MATRIX[1]),
+ normalize(INV_VIEW_MATRIX[2]),
+ MODEL_MATRIX[3]);
+ mat_world = mat_world * mat4(
+ vec4(cos(INSTANCE_CUSTOM.x), -sin(INSTANCE_CUSTOM.x), 0.0, 0.0),
+ vec4(sin(INSTANCE_CUSTOM.x), cos(INSTANCE_CUSTOM.x), 0.0, 0.0),
+ vec4(0.0, 0.0, 1.0, 0.0),
+ vec4(0.0, 0.0, 0.0, 1.0));
+)";
+ // Set modelview.
code += " MODELVIEW_MATRIX = VIEW_MATRIX * mat_world;\n";
if (flags[FLAG_BILLBOARD_KEEP_SCALE]) {
- code += " MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(length(MODEL_MATRIX[0].xyz), 0.0, 0.0, 0.0),vec4(0.0, length(MODEL_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, length(MODEL_MATRIX[2].xyz), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n";
+ code += R"(
+ // Billboard Keep Scale: Enabled
+ MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(
+ vec4(length(MODEL_MATRIX[0].xyz), 0.0, 0.0, 0.0),
+ vec4(0.0, length(MODEL_MATRIX[1].xyz), 0.0, 0.0),
+ vec4(0.0, 0.0, length(MODEL_MATRIX[2].xyz), 0.0),
+ vec4(0.0, 0.0, 0.0, 1.0));
+)";
}
- //set modelview normal
- code += " MODELVIEW_NORMAL_MATRIX = mat3(MODELVIEW_MATRIX);\n";
-
- //handle animation
- code += " float h_frames = float(particles_anim_h_frames);\n";
- code += " float v_frames = float(particles_anim_v_frames);\n";
- code += " float particle_total_frames = float(particles_anim_h_frames * particles_anim_v_frames);\n";
- code += " float particle_frame = floor(INSTANCE_CUSTOM.z * float(particle_total_frames));\n";
- code += " if (!particles_anim_loop) {\n";
- code += " particle_frame = clamp(particle_frame, 0.0, particle_total_frames - 1.0);\n";
- code += " } else {\n";
- code += " particle_frame = mod(particle_frame, particle_total_frames);\n";
- code += " }\n";
- code += " UV /= vec2(h_frames, v_frames);\n";
- code += " UV += vec2(mod(particle_frame, h_frames) / h_frames, floor((particle_frame + 0.5) / h_frames) / v_frames);\n";
+ // Set modelview normal and handle animation.
+ code += R"(
+ MODELVIEW_NORMAL_MATRIX = mat3(MODELVIEW_MATRIX);
+
+ float h_frames = float(particles_anim_h_frames);
+ float v_frames = float(particles_anim_v_frames);
+ float particle_total_frames = float(particles_anim_h_frames * particles_anim_v_frames);
+ float particle_frame = floor(INSTANCE_CUSTOM.z * float(particle_total_frames));
+ if (!particles_anim_loop) {
+ particle_frame = clamp(particle_frame, 0.0, particle_total_frames - 1.0);
+ } else {
+ particle_frame = mod(particle_frame, particle_total_frames);
+ }
+ UV /= vec2(h_frames, v_frames);
+ UV += vec2(mod(particle_frame, h_frames) / h_frames, floor((particle_frame + 0.5) / h_frames) / v_frames);
+)";
} break;
case BILLBOARD_MAX:
break; // Internal value, skip.
}
if (flags[FLAG_FIXED_SIZE]) {
- code += " if (PROJECTION_MATRIX[3][3] != 0.0) {\n";
- //orthogonal matrix, try to do about the same
- //with viewport size
- code += " float h = abs(1.0 / (2.0 * PROJECTION_MATRIX[1][1]));\n";
- code += " float sc = (h * 2.0); //consistent with Y-fov\n";
- code += " MODELVIEW_MATRIX[0]*=sc;\n";
- code += " MODELVIEW_MATRIX[1]*=sc;\n";
- code += " MODELVIEW_MATRIX[2]*=sc;\n";
- code += " } else {\n";
- //just scale by depth
- code += " float sc = -(MODELVIEW_MATRIX)[3].z;\n";
- code += " MODELVIEW_MATRIX[0]*=sc;\n";
- code += " MODELVIEW_MATRIX[1]*=sc;\n";
- code += " MODELVIEW_MATRIX[2]*=sc;\n";
- code += " }\n";
+ code += R"(
+ // Fixed Size: Enabled
+ if (PROJECTION_MATRIX[3][3] != 0.0) {
+ // Orthogonal matrix; try to do about the same with viewport size.
+ float h = abs(1.0 / (2.0 * PROJECTION_MATRIX[1][1]));
+ // Consistent with vertical FOV (Keep Height).
+ float sc = (h * 2.0);
+ MODELVIEW_MATRIX[0] *= sc;
+ MODELVIEW_MATRIX[1] *= sc;
+ MODELVIEW_MATRIX[2] *= sc;
+ } else {
+ // Scale by depth.
+ float sc = -(MODELVIEW_MATRIX)[3].z;
+ MODELVIEW_MATRIX[0] *= sc;
+ MODELVIEW_MATRIX[1] *= sc;
+ MODELVIEW_MATRIX[2] *= sc;
}
-
- if (detail_uv == DETAIL_UV_2 && !flags[FLAG_UV2_USE_TRIPLANAR]) {
- code += " UV2=UV2*uv2_scale.xy+uv2_offset.xy;\n";
+)";
}
+
if (flags[FLAG_UV1_USE_TRIPLANAR] || flags[FLAG_UV2_USE_TRIPLANAR]) {
- //generate tangent and binormal in world space
+ // Generate tangent and binormal in world space.
if (flags[FLAG_UV1_USE_WORLD_TRIPLANAR]) {
- code += " vec3 normal = MODEL_NORMAL_MATRIX * NORMAL;\n";
+ code += R"(
+ vec3 normal = MODEL_NORMAL_MATRIX * NORMAL;
+)";
} else {
- code += " vec3 normal = NORMAL;\n";
- }
- code += " TANGENT = vec3(0.0,0.0,-1.0) * abs(normal.x);\n";
- code += " TANGENT+= vec3(1.0,0.0,0.0) * abs(normal.y);\n";
- code += " TANGENT+= vec3(1.0,0.0,0.0) * abs(normal.z);\n";
+ code += R"(
+ vec3 normal = NORMAL;
+)";
+ }
+ code += R"(
+ TANGENT = vec3(0.0, 0.0, -1.0) * abs(normal.x);
+ TANGENT += vec3(1.0, 0.0, 0.0) * abs(normal.y);
+ TANGENT += vec3(1.0, 0.0, 0.0) * abs(normal.z);
+)";
if (flags[FLAG_UV1_USE_WORLD_TRIPLANAR]) {
code += " TANGENT = inverse(MODEL_NORMAL_MATRIX) * normalize(TANGENT);\n";
} else {
code += " TANGENT = normalize(TANGENT);\n";
}
- code += " BINORMAL = vec3(0.0,1.0,0.0) * abs(normal.x);\n";
- code += " BINORMAL+= vec3(0.0,0.0,-1.0) * abs(normal.y);\n";
- code += " BINORMAL+= vec3(0.0,1.0,0.0) * abs(normal.z);\n";
+ code += R"(
+ BINORMAL = vec3(0.0, 1.0, 0.0) * abs(normal.x);
+ BINORMAL += vec3(0.0, 0.0, -1.0) * abs(normal.y);
+ BINORMAL += vec3(0.0, 1.0, 0.0) * abs(normal.z);
+)";
if (flags[FLAG_UV1_USE_WORLD_TRIPLANAR]) {
code += " BINORMAL = inverse(MODEL_NORMAL_MATRIX) * normalize(BINORMAL);\n";
} else {
@@ -1089,58 +1225,89 @@ void BaseMaterial3D::_update_shader() {
if (flags[FLAG_UV1_USE_TRIPLANAR]) {
if (flags[FLAG_UV1_USE_WORLD_TRIPLANAR]) {
- code += " uv1_power_normal=pow(abs(normal),vec3(uv1_blend_sharpness));\n";
- code += " uv1_triplanar_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0f)).xyz * uv1_scale + uv1_offset;\n";
+ code += R"(
+ // UV1 Triplanar: Enabled (with World Triplanar)
+ uv1_power_normal = pow(abs(normal), vec3(uv1_blend_sharpness));
+ uv1_triplanar_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz * uv1_scale + uv1_offset;
+)";
} else {
- code += " uv1_power_normal=pow(abs(NORMAL),vec3(uv1_blend_sharpness));\n";
- code += " uv1_triplanar_pos = VERTEX * uv1_scale + uv1_offset;\n";
+ code += R"(
+ // UV1 Triplanar: Enabled
+ uv1_power_normal = pow(abs(NORMAL), vec3(uv1_blend_sharpness));
+ uv1_triplanar_pos = VERTEX * uv1_scale + uv1_offset;
+)";
}
- code += " uv1_power_normal/=dot(uv1_power_normal,vec3(1.0));\n";
- code += " uv1_triplanar_pos *= vec3(1.0,-1.0, 1.0);\n";
+ code += R"( uv1_power_normal /= dot(uv1_power_normal, vec3(1.0));
+ uv1_triplanar_pos *= vec3(1.0, -1.0, 1.0);
+)";
}
if (flags[FLAG_UV2_USE_TRIPLANAR]) {
if (flags[FLAG_UV2_USE_WORLD_TRIPLANAR]) {
- code += " uv2_power_normal=pow(abs(mat3(MODEL_MATRIX) * NORMAL), vec3(uv2_blend_sharpness));\n";
- code += " uv2_triplanar_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0f)).xyz * uv2_scale + uv2_offset;\n";
+ code += R"(
+ // UV2 Triplanar: Enabled (with World Triplanar)
+ uv2_power_normal = pow(abs(mat3(MODEL_MATRIX) * NORMAL), vec3(uv2_blend_sharpness));
+ uv2_triplanar_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz * uv2_scale + uv2_offset;
+)";
} else {
- code += " uv2_power_normal=pow(abs(NORMAL), vec3(uv2_blend_sharpness));\n";
- code += " uv2_triplanar_pos = VERTEX * uv2_scale + uv2_offset;\n";
+ code += R"(
+ // UV2 Triplanar: Enabled
+ uv2_power_normal = pow(abs(NORMAL), vec3(uv2_blend_sharpness));
+ uv2_triplanar_pos = VERTEX * uv2_scale + uv2_offset;
+)";
}
- code += " uv2_power_normal/=dot(uv2_power_normal,vec3(1.0));\n";
- code += " uv2_triplanar_pos *= vec3(1.0,-1.0, 1.0);\n";
+ code += R"( uv2_power_normal /= dot(uv2_power_normal, vec3(1.0));
+ uv2_triplanar_pos *= vec3(1.0, -1.0, 1.0);
+)";
}
if (grow_enabled) {
- code += " VERTEX+=NORMAL*grow;\n";
+ code += R"(
+ // Grow: Enabled
+ VERTEX += NORMAL * grow;
+)";
}
code += "}\n";
- code += "\n\n";
+
if (flags[FLAG_ALBEDO_TEXTURE_MSDF]) {
- code += "float msdf_median(float r, float g, float b, float a) {\n";
- code += " return min(max(min(r, g), min(max(r, g), b)), a);\n";
- code += "}\n";
+ code += R"(
+float msdf_median(float r, float g, float b, float a) {
+ return min(max(min(r, g), min(max(r, g), b)), a);
+}
+)";
}
- code += "\n\n";
+
if (flags[FLAG_UV1_USE_TRIPLANAR] || flags[FLAG_UV2_USE_TRIPLANAR]) {
- code += "vec4 triplanar_texture(sampler2D p_sampler,vec3 p_weights,vec3 p_triplanar_pos) {\n";
- code += " vec4 samp=vec4(0.0);\n";
- code += " samp+= texture(p_sampler,p_triplanar_pos.xy) * p_weights.z;\n";
- code += " samp+= texture(p_sampler,p_triplanar_pos.xz) * p_weights.y;\n";
- code += " samp+= texture(p_sampler,p_triplanar_pos.zy * vec2(-1.0,1.0)) * p_weights.x;\n";
- code += " return samp;\n";
- code += "}\n";
+ code += R"(
+vec4 triplanar_texture(sampler2D p_sampler, vec3 p_weights, vec3 p_triplanar_pos) {
+ vec4 samp = vec4(0.0);
+ samp += texture(p_sampler, p_triplanar_pos.xy) * p_weights.z;
+ samp += texture(p_sampler, p_triplanar_pos.xz) * p_weights.y;
+ samp += texture(p_sampler, p_triplanar_pos.zy * vec2(-1.0, 1.0)) * p_weights.x;
+ return samp;
+}
+)";
}
- code += "\n\n";
- code += "void fragment() {\n";
+
+ // Generate fragment shader.
+ code += R"(
+void fragment() {)";
if (!flags[FLAG_UV1_USE_TRIPLANAR]) {
- code += " vec2 base_uv = UV;\n";
+ code += R"(
+ vec2 base_uv = UV;
+)";
}
if ((features[FEATURE_DETAIL] && detail_uv == DETAIL_UV_2) || (features[FEATURE_AMBIENT_OCCLUSION] && flags[FLAG_AO_ON_UV2]) || (features[FEATURE_EMISSION] && flags[FLAG_EMISSION_ON_UV2])) {
- code += " vec2 base_uv2 = UV2;\n";
+ // Don't add a newline if the UV assignment above is already performed,
+ // so that UV1 and UV2 are closer to each other.
+ if (flags[FLAG_UV1_USE_TRIPLANAR]) {
+ code += "\n";
+ }
+ code += R"( vec2 base_uv2 = UV2;
+)";
}
if (features[FEATURE_HEIGHT_MAPPING] && flags[FLAG_UV1_USE_TRIPLANAR]) {
@@ -1157,42 +1324,57 @@ void BaseMaterial3D::_update_shader() {
}
}
- if (!RenderingServer::get_singleton()->is_low_end() && features[FEATURE_HEIGHT_MAPPING] && !flags[FLAG_UV1_USE_TRIPLANAR]) { //heightmap not supported with triplanar
- code += " {\n";
- code += " vec3 view_dir = normalize(normalize(-VERTEX + EYE_OFFSET) * mat3(TANGENT * heightmap_flip.x, -BINORMAL * heightmap_flip.y, NORMAL));\n"; // binormal is negative due to mikktspace, flip 'unflips' it ;-)
+ // Heightmapping isn't supported at the same time as triplanar mapping.
+ if (!RenderingServer::get_singleton()->is_low_end() && features[FEATURE_HEIGHT_MAPPING] && !flags[FLAG_UV1_USE_TRIPLANAR]) {
+ // Binormal is negative due to mikktspace. Flipping it "unflips" it.
+ code += R"(
+ {
+ // Height: Enabled
+ vec3 view_dir = normalize(normalize(-VERTEX + EYE_OFFSET) * mat3(TANGENT * heightmap_flip.x, -BINORMAL * heightmap_flip.y, NORMAL));
+)";
if (deep_parallax) {
- code += " float num_layers = mix(float(heightmap_max_layers),float(heightmap_min_layers), abs(dot(vec3(0.0, 0.0, 1.0), view_dir)));\n";
- code += " float layer_depth = 1.0 / num_layers;\n";
- code += " float current_layer_depth = 0.0;\n";
// Multiply the heightmap scale by 0.01 to improve heightmap scale usability.
- code += " vec2 P = view_dir.xy * heightmap_scale * 0.01;\n";
- code += " vec2 delta = P / num_layers;\n";
- code += " vec2 ofs = base_uv;\n";
+ code += R"(
+ // Height Deep Parallax: Enabled
+ float num_layers = mix(float(heightmap_max_layers), float(heightmap_min_layers), abs(dot(vec3(0.0, 0.0, 1.0), view_dir)));
+ float layer_depth = 1.0 / num_layers;
+ float current_layer_depth = 0.0;
+ vec2 p = view_dir.xy * heightmap_scale * 0.01;
+ vec2 delta = p / num_layers;
+ vec2 ofs = base_uv;
+)";
if (flags[FLAG_INVERT_HEIGHTMAP]) {
code += " float depth = texture(texture_heightmap, ofs).r;\n";
} else {
code += " float depth = 1.0 - texture(texture_heightmap, ofs).r;\n";
}
- code += " float current_depth = 0.0;\n";
- code += " while(current_depth < depth) {\n";
- code += " ofs -= delta;\n";
+ code += R"(
+ float current_depth = 0.0;
+ while (current_depth < depth) {
+ ofs -= delta;
+)";
if (flags[FLAG_INVERT_HEIGHTMAP]) {
code += " depth = texture(texture_heightmap, ofs).r;\n";
} else {
code += " depth = 1.0 - texture(texture_heightmap, ofs).r;\n";
}
- code += " current_depth += layer_depth;\n";
- code += " }\n";
- code += " vec2 prev_ofs = ofs + delta;\n";
- code += " float after_depth = depth - current_depth;\n";
+ code += R"(
+ current_depth += layer_depth;
+ }
+
+ vec2 prev_ofs = ofs + delta;
+ float after_depth = depth - current_depth;
+)";
if (flags[FLAG_INVERT_HEIGHTMAP]) {
code += " float before_depth = texture(texture_heightmap, prev_ofs).r - current_depth + layer_depth;\n";
} else {
- code += " float before_depth = ( 1.0 - texture(texture_heightmap, prev_ofs).r ) - current_depth + layer_depth;\n";
+ code += " float before_depth = (1.0 - texture(texture_heightmap, prev_ofs).r) - current_depth + layer_depth;\n";
}
- code += " float weight = after_depth / (after_depth - before_depth);\n";
- code += " ofs = mix(ofs,prev_ofs,weight);\n";
+ code += R"(
+ float weight = after_depth / (after_depth - before_depth);
+ ofs = mix(ofs, prev_ofs, weight);
+)";
} else {
if (flags[FLAG_INVERT_HEIGHTMAP]) {
@@ -1206,28 +1388,41 @@ void BaseMaterial3D::_update_shader() {
code += " vec2 ofs = base_uv - view_dir.xy * depth * heightmap_scale * 0.01;\n";
}
- code += " base_uv=ofs;\n";
+ code += " base_uv = ofs;\n";
if (features[FEATURE_DETAIL] && detail_uv == DETAIL_UV_2) {
- code += " base_uv2-=ofs;\n";
+ code += " base_uv2 -= ofs;\n";
}
code += " }\n";
}
if (flags[FLAG_USE_POINT_SIZE]) {
- code += " vec4 albedo_tex = texture(texture_albedo,POINT_COORD);\n";
+ code += R"(
+ // Use Point Size: Enabled
+ vec4 albedo_tex = texture(texture_albedo, POINT_COORD);
+)";
} else {
if (flags[FLAG_UV1_USE_TRIPLANAR]) {
- code += " vec4 albedo_tex = triplanar_texture(texture_albedo,uv1_power_normal,uv1_triplanar_pos);\n";
+ code += R"(
+ vec4 albedo_tex = triplanar_texture(texture_albedo, uv1_power_normal, uv1_triplanar_pos);
+)";
} else {
- code += " vec4 albedo_tex = texture(texture_albedo,base_uv);\n";
+ code += R"(
+ vec4 albedo_tex = texture(texture_albedo, base_uv);
+)";
}
}
if (flags[FLAG_ALBEDO_TEXTURE_MSDF]) {
- code += " {\n";
- code += " albedo_tex.rgb = mix(vec3(1.0 + 0.055) * pow(albedo_tex.rgb, vec3(1.0 / 2.4)) - vec3(0.055), vec3(12.92) * albedo_tex.rgb.rgb, lessThan(albedo_tex.rgb, vec3(0.0031308)));\n";
- code += " vec2 msdf_size = vec2(msdf_pixel_range) / vec2(textureSize(texture_albedo, 0));\n";
+ code += R"(
+ {
+ // Albedo Texture MSDF: Enabled
+ albedo_tex.rgb = mix(
+ vec3(1.0 + 0.055) * pow(albedo_tex.rgb, vec3(1.0 / 2.4)) - vec3(0.055),
+ vec3(12.92) * albedo_tex.rgb,
+ lessThan(albedo_tex.rgb, vec3(0.0031308)));
+ vec2 msdf_size = vec2(msdf_pixel_range) / vec2(textureSize(texture_albedo, 0));
+)";
if (flags[FLAG_USE_POINT_SIZE]) {
code += " vec2 dest_size = vec2(1.0) / fwidth(POINT_COORD);\n";
} else {
@@ -1237,120 +1432,175 @@ void BaseMaterial3D::_update_shader() {
code += " vec2 dest_size = vec2(1.0) / fwidth(base_uv);\n";
}
}
- code += " float px_size = max(0.5 * dot(msdf_size, dest_size), 1.0);\n";
- code += " float d = msdf_median(albedo_tex.r, albedo_tex.g, albedo_tex.b, albedo_tex.a) - 0.5;\n";
- code += " if (msdf_outline_size > 0.0) {\n";
- code += " float cr = clamp(msdf_outline_size, 0.0, msdf_pixel_range / 2.0) / msdf_pixel_range;\n";
- code += " albedo_tex.a = clamp((d + cr) * px_size, 0.0, 1.0);\n";
- code += " } else {\n";
- code += " albedo_tex.a = clamp(d * px_size + 0.5, 0.0, 1.0);\n";
- code += " }\n";
- code += " albedo_tex.rgb = vec3(1.0);\n";
- code += " }\n";
+ code += R"(
+ float px_size = max(0.5 * dot(msdf_size, dest_size), 1.0);
+ float d = msdf_median(albedo_tex.r, albedo_tex.g, albedo_tex.b, albedo_tex.a) - 0.5;
+ if (msdf_outline_size > 0.0) {
+ float cr = clamp(msdf_outline_size, 0.0, msdf_pixel_range / 2.0) / msdf_pixel_range;
+ albedo_tex.a = clamp((d + cr) * px_size, 0.0, 1.0);
+ } else {
+ albedo_tex.a = clamp(d * px_size + 0.5, 0.0, 1.0);
+ }
+ albedo_tex.rgb = vec3(1.0);
+ }
+)";
} else if (flags[FLAG_ALBEDO_TEXTURE_FORCE_SRGB]) {
- code += " albedo_tex.rgb = mix(pow((albedo_tex.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)),vec3(2.4)),albedo_tex.rgb.rgb * (1.0 / 12.92),lessThan(albedo_tex.rgb,vec3(0.04045)));\n";
+ code += R"(
+ // Albedo Texture Force sRGB: Enabled
+ albedo_tex.rgb = mix(
+ pow((albedo_tex.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)),
+ albedo_tex.rgb.rgb * (1.0 / 12.92),
+ lessThan(albedo_tex.rgb, vec3(0.04045)));
+)";
}
if (flags[FLAG_ALBEDO_FROM_VERTEX_COLOR]) {
- code += " albedo_tex *= COLOR;\n";
+ code += R"(
+ // Vertex Color Use as Albedo: Enabled
+ albedo_tex *= COLOR;
+
+)";
}
code += " ALBEDO = albedo.rgb * albedo_tex.rgb;\n";
if (!orm) {
if (flags[FLAG_UV1_USE_TRIPLANAR]) {
- code += " float metallic_tex = dot(triplanar_texture(texture_metallic,uv1_power_normal,uv1_triplanar_pos),metallic_texture_channel);\n";
+ code += R"(
+ float metallic_tex = dot(triplanar_texture(texture_metallic, uv1_power_normal, uv1_triplanar_pos), metallic_texture_channel);
+)";
} else {
- code += " float metallic_tex = dot(texture(texture_metallic,base_uv),metallic_texture_channel);\n";
+ code += R"(
+ float metallic_tex = dot(texture(texture_metallic, base_uv), metallic_texture_channel);
+)";
}
- code += " METALLIC = metallic_tex * metallic;\n";
+ code += R"( METALLIC = metallic_tex * metallic;
+ SPECULAR = specular;
+)";
switch (roughness_texture_channel) {
case TEXTURE_CHANNEL_RED: {
- code += " vec4 roughness_texture_channel = vec4(1.0,0.0,0.0,0.0);\n";
+ code += R"(
+ vec4 roughness_texture_channel = vec4(1.0, 0.0, 0.0, 0.0);
+)";
} break;
case TEXTURE_CHANNEL_GREEN: {
- code += " vec4 roughness_texture_channel = vec4(0.0,1.0,0.0,0.0);\n";
+ code += R"(
+ vec4 roughness_texture_channel = vec4(0.0, 1.0, 0.0, 0.0);
+)";
} break;
case TEXTURE_CHANNEL_BLUE: {
- code += " vec4 roughness_texture_channel = vec4(0.0,0.0,1.0,0.0);\n";
+ code += R"(
+ vec4 roughness_texture_channel = vec4(0.0, 0.0, 1.0, 0.0);
+)";
} break;
case TEXTURE_CHANNEL_ALPHA: {
- code += " vec4 roughness_texture_channel = vec4(0.0,0.0,0.0,1.0);\n";
+ code += R"(
+ vec4 roughness_texture_channel = vec4(0.0, 0.0, 0.0, 1.0);
+)";
} break;
case TEXTURE_CHANNEL_GRAYSCALE: {
- code += " vec4 roughness_texture_channel = vec4(0.333333,0.333333,0.333333,0.0);\n";
+ code += R"(
+ vec4 roughness_texture_channel = vec4(0.333333, 0.333333, 0.333333, 0.0);
+)";
} break;
case TEXTURE_CHANNEL_MAX:
break; // Internal value, skip.
}
if (flags[FLAG_UV1_USE_TRIPLANAR]) {
- code += " float roughness_tex = dot(triplanar_texture(texture_roughness,uv1_power_normal,uv1_triplanar_pos),roughness_texture_channel);\n";
+ code += " float roughness_tex = dot(triplanar_texture(texture_roughness, uv1_power_normal, uv1_triplanar_pos), roughness_texture_channel);\n";
} else {
- code += " float roughness_tex = dot(texture(texture_roughness,base_uv),roughness_texture_channel);\n";
+ code += " float roughness_tex = dot(texture(texture_roughness, base_uv), roughness_texture_channel);\n";
}
- code += " ROUGHNESS = roughness_tex * roughness;\n";
- code += " SPECULAR = specular;\n";
+ code += R"( ROUGHNESS = roughness_tex * roughness;
+)";
} else {
if (flags[FLAG_UV1_USE_TRIPLANAR]) {
- code += " vec4 orm_tex = triplanar_texture(texture_orm,uv1_power_normal,uv1_triplanar_pos);\n";
+ code += R"(
+ vec4 orm_tex = triplanar_texture(texture_orm, uv1_power_normal, uv1_triplanar_pos);
+)";
} else {
- code += " vec4 orm_tex = texture(texture_orm,base_uv);\n";
+ code += R"(
+ vec4 orm_tex = texture(texture_orm, base_uv);
+)";
}
- code += " ROUGHNESS = orm_tex.g;\n";
- code += " METALLIC = orm_tex.b;\n";
+ code += R"( ROUGHNESS = orm_tex.g;
+ METALLIC = orm_tex.b;
+)";
}
if (features[FEATURE_NORMAL_MAPPING]) {
+ code += R"(
+ // Normal Map: Enabled
+)";
if (flags[FLAG_UV1_USE_TRIPLANAR]) {
- code += " NORMAL_MAP = triplanar_texture(texture_normal,uv1_power_normal,uv1_triplanar_pos).rgb;\n";
+ code += " NORMAL_MAP = triplanar_texture(texture_normal, uv1_power_normal, uv1_triplanar_pos).rgb;\n";
} else {
- code += " NORMAL_MAP = texture(texture_normal,base_uv).rgb;\n";
+ code += " NORMAL_MAP = texture(texture_normal, base_uv).rgb;\n";
}
code += " NORMAL_MAP_DEPTH = normal_scale;\n";
}
if (features[FEATURE_EMISSION]) {
+ code += R"(
+ // Emission: Enabled
+)";
if (flags[FLAG_EMISSION_ON_UV2]) {
if (flags[FLAG_UV2_USE_TRIPLANAR]) {
- code += " vec3 emission_tex = triplanar_texture(texture_emission,uv2_power_normal,uv2_triplanar_pos).rgb;\n";
+ code += " vec3 emission_tex = triplanar_texture(texture_emission, uv2_power_normal, uv2_triplanar_pos).rgb;\n";
} else {
- code += " vec3 emission_tex = texture(texture_emission,base_uv2).rgb;\n";
+ code += " vec3 emission_tex = texture(texture_emission, base_uv2).rgb;\n";
}
} else {
if (flags[FLAG_UV1_USE_TRIPLANAR]) {
- code += " vec3 emission_tex = triplanar_texture(texture_emission,uv1_power_normal,uv1_triplanar_pos).rgb;\n";
+ code += " vec3 emission_tex = triplanar_texture(texture_emission, uv1_power_normal, uv1_triplanar_pos).rgb;\n";
} else {
- code += " vec3 emission_tex = texture(texture_emission,base_uv).rgb;\n";
+ code += " vec3 emission_tex = texture(texture_emission, base_uv).rgb;\n";
}
}
if (emission_op == EMISSION_OP_ADD) {
- code += " EMISSION = (emission.rgb+emission_tex)*emission_energy;\n";
+ code += R"( // Emission Operator: Add
+ EMISSION = (emission.rgb + emission_tex) * emission_energy;
+)";
} else {
- code += " EMISSION = (emission.rgb*emission_tex)*emission_energy;\n";
+ code += R"( // Emission Operator: Multiply
+ EMISSION = (emission.rgb * emission_tex) * emission_energy;
+)";
}
}
if (features[FEATURE_REFRACTION]) {
if (features[FEATURE_NORMAL_MAPPING]) {
- code += " vec3 unpacked_normal = NORMAL_MAP;\n";
- code += " unpacked_normal.xy = unpacked_normal.xy * 2.0 - 1.0;\n";
- code += " unpacked_normal.z = sqrt(max(0.0, 1.0 - dot(unpacked_normal.xy, unpacked_normal.xy)));\n";
- code += " vec3 ref_normal = normalize( mix(NORMAL,TANGENT * unpacked_normal.x + BINORMAL * unpacked_normal.y + NORMAL * unpacked_normal.z,NORMAL_MAP_DEPTH) );\n";
+ code += R"(
+ // Refraction: Enabled (with normal map texture)
+ vec3 unpacked_normal = NORMAL_MAP;
+ unpacked_normal.xy = unpacked_normal.xy * 2.0 - 1.0;
+ unpacked_normal.z = sqrt(max(0.0, 1.0 - dot(unpacked_normal.xy, unpacked_normal.xy)));
+ vec3 ref_normal = normalize(mix(
+ NORMAL,
+ TANGENT * unpacked_normal.x + BINORMAL * unpacked_normal.y + NORMAL * unpacked_normal.z,
+ NORMAL_MAP_DEPTH));
+)";
} else {
- code += " vec3 ref_normal = NORMAL;\n";
+ code += R"(
+ // Refraction: Enabled
+ vec3 ref_normal = NORMAL;
+)";
}
if (flags[FLAG_UV1_USE_TRIPLANAR]) {
- code += " vec2 ref_ofs = SCREEN_UV - ref_normal.xy * dot(triplanar_texture(texture_refraction,uv1_power_normal,uv1_triplanar_pos),refraction_texture_channel) * refraction;\n";
+ code += " vec2 ref_ofs = SCREEN_UV - ref_normal.xy * dot(triplanar_texture(texture_refraction, uv1_power_normal, uv1_triplanar_pos), refraction_texture_channel) * refraction;\n";
} else {
- code += " vec2 ref_ofs = SCREEN_UV - ref_normal.xy * dot(texture(texture_refraction,base_uv),refraction_texture_channel) * refraction;\n";
+ code += " vec2 ref_ofs = SCREEN_UV - ref_normal.xy * dot(texture(texture_refraction, base_uv), refraction_texture_channel) * refraction;\n";
}
- code += " float ref_amount = 1.0 - albedo.a * albedo_tex.a;\n";
- code += " EMISSION += textureLod(screen_texture,ref_ofs,ROUGHNESS * 8.0).rgb * ref_amount * EXPOSURE;\n";
- code += " ALBEDO *= 1.0 - ref_amount;\n";
- code += " ALPHA = 1.0;\n";
+ code += R"(
+ float ref_amount = 1.0 - albedo.a * albedo_tex.a;
+ EMISSION += textureLod(screen_texture, ref_ofs, ROUGHNESS * 8.0).rgb * ref_amount * EXPOSURE;
+ ALBEDO *= 1.0 - ref_amount;
+ // Force transparency on the material (required for refraction).
+ ALPHA = 1.0;
+)";
} else if (transparency != TRANSPARENCY_DISABLED || flags[FLAG_USE_SHADOW_TO_OPACITY] || (distance_fade == DISTANCE_FADE_PIXEL_ALPHA) || proximity_fade_enabled) {
code += " ALPHA *= albedo.a * albedo_tex.a;\n";
@@ -1366,10 +1616,13 @@ void BaseMaterial3D::_update_shader() {
}
if (proximity_fade_enabled) {
- code += " float depth_tex = textureLod(depth_texture,SCREEN_UV,0.0).r;\n";
- code += " vec4 world_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV*2.0-1.0,depth_tex,1.0);\n";
- code += " world_pos.xyz/=world_pos.w;\n";
- code += " ALPHA*=clamp(1.0-smoothstep(world_pos.z+proximity_fade_distance,world_pos.z,VERTEX.z),0.0,1.0);\n";
+ code += R"(
+ // Proximity Fade: Enabled
+ float depth_tex = textureLod(depth_texture, SCREEN_UV, 0.0).r;
+ vec4 world_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, depth_tex, 1.0);
+ world_pos.xyz /= world_pos.w;
+ ALPHA *= clamp(1.0 - smoothstep(world_pos.z + proximity_fade_distance, world_pos.z, VERTEX.z), 0.0, 1.0);
+)";
}
if (distance_fade != DISTANCE_FADE_DISABLED) {
@@ -1377,72 +1630,96 @@ void BaseMaterial3D::_update_shader() {
// (Z distance), so that the fade is always the same regardless of the camera angle.
if ((distance_fade == DISTANCE_FADE_OBJECT_DITHER || distance_fade == DISTANCE_FADE_PIXEL_DITHER)) {
if (!RenderingServer::get_singleton()->is_low_end()) {
- code += " {\n";
+ code += "\n {";
if (distance_fade == DISTANCE_FADE_OBJECT_DITHER) {
- code += " float fade_distance = length((VIEW_MATRIX * MODEL_MATRIX[3]));\n";
+ code += R"(
+ // Distance Fade: Object Dither
+ float fade_distance = length((VIEW_MATRIX * MODEL_MATRIX[3]));
+)";
} else {
- code += " float fade_distance = length(VERTEX);\n";
+ code += R"(
+ // Distance Fade: Pixel Dither
+ float fade_distance = length(VERTEX);
+)";
}
- // Use interleaved gradient noise, which is fast but still looks good.
- code += " const vec3 magic = vec3(0.06711056f, 0.00583715f, 52.9829189f);";
- code += " float fade = clamp(smoothstep(distance_fade_min, distance_fade_max, fade_distance), 0.0, 1.0);\n";
- // Use a hard cap to prevent a few stray pixels from remaining when past the fade-out distance.
- code += " if (fade < 0.001 || fade < fract(magic.z * fract(dot(FRAGCOORD.xy, magic.xy)))) {\n";
- code += " discard;\n";
- code += " }\n";
-
- code += " }\n\n";
+ code += R"(
+ // Use interleaved gradient noise, which is fast but still looks good.
+ const vec3 magic = vec3(0.06711056, 0.00583715, 52.9829189);
+ float fade = clamp(smoothstep(distance_fade_min, distance_fade_max, fade_distance), 0.0, 1.0);
+ // Use a hard cap to prevent a few stray pixels from remaining when past the fade-out distance.
+ if (fade < 0.001 || fade < fract(magic.z * fract(dot(FRAGCOORD.xy, magic.xy)))) {
+ discard;
+ }
+ }
+)";
}
-
} else {
- code += " ALPHA *= clamp(smoothstep(distance_fade_min, distance_fade_max, length(VERTEX)), 0.0, 1.0);\n";
+ code += R"(
+ // Distance Fade: Pixel Alpha
+ ALPHA *= clamp(smoothstep(distance_fade_min, distance_fade_max, length(VERTEX)), 0.0, 1.0);
+)";
}
}
if (features[FEATURE_RIM]) {
+ code += R"(
+ // Rim: Enabled
+)";
if (flags[FLAG_UV1_USE_TRIPLANAR]) {
- code += " vec2 rim_tex = triplanar_texture(texture_rim,uv1_power_normal,uv1_triplanar_pos).xy;\n";
+ code += " vec2 rim_tex = triplanar_texture(texture_rim, uv1_power_normal, uv1_triplanar_pos).xy;\n";
} else {
- code += " vec2 rim_tex = texture(texture_rim,base_uv).xy;\n";
+ code += " vec2 rim_tex = texture(texture_rim, base_uv).xy;\n";
}
- code += " RIM = rim*rim_tex.x;";
- code += " RIM_TINT = rim_tint*rim_tex.y;\n";
+ code += R"( RIM = rim * rim_tex.x;
+ RIM_TINT = rim_tint * rim_tex.y;
+)";
}
if (features[FEATURE_CLEARCOAT]) {
+ code += R"(
+ // Clearcoat: Enabled
+)";
if (flags[FLAG_UV1_USE_TRIPLANAR]) {
- code += " vec2 clearcoat_tex = triplanar_texture(texture_clearcoat,uv1_power_normal,uv1_triplanar_pos).xy;\n";
+ code += " vec2 clearcoat_tex = triplanar_texture(texture_clearcoat, uv1_power_normal, uv1_triplanar_pos).xy;\n";
} else {
- code += " vec2 clearcoat_tex = texture(texture_clearcoat,base_uv).xy;\n";
+ code += " vec2 clearcoat_tex = texture(texture_clearcoat, base_uv).xy;\n";
}
- code += " CLEARCOAT = clearcoat*clearcoat_tex.x;";
- code += " CLEARCOAT_ROUGHNESS = clearcoat_roughness*clearcoat_tex.y;\n";
+ code += R"( CLEARCOAT = clearcoat * clearcoat_tex.x;
+ CLEARCOAT_ROUGHNESS = clearcoat_roughness * clearcoat_tex.y;
+)";
}
if (features[FEATURE_ANISOTROPY]) {
+ code += R"(
+ // Anisotropy: Enabled
+)";
if (flags[FLAG_UV1_USE_TRIPLANAR]) {
- code += " vec3 anisotropy_tex = triplanar_texture(texture_flowmap,uv1_power_normal,uv1_triplanar_pos).rga;\n";
+ code += " vec3 anisotropy_tex = triplanar_texture(texture_flowmap, uv1_power_normal, uv1_triplanar_pos).rga;\n";
} else {
- code += " vec3 anisotropy_tex = texture(texture_flowmap,base_uv).rga;\n";
+ code += " vec3 anisotropy_tex = texture(texture_flowmap, base_uv).rga;\n";
}
- code += " ANISOTROPY = anisotropy_ratio*anisotropy_tex.b;\n";
- code += " ANISOTROPY_FLOW = anisotropy_tex.rg*2.0-1.0;\n";
+ code += R"( ANISOTROPY = anisotropy_ratio * anisotropy_tex.b;
+ ANISOTROPY_FLOW = anisotropy_tex.rg * 2.0 - 1.0;
+)";
}
if (features[FEATURE_AMBIENT_OCCLUSION]) {
+ code += R"(
+ // Ambient Occlusion: Enabled
+)";
if (!orm) {
if (flags[FLAG_AO_ON_UV2]) {
if (flags[FLAG_UV2_USE_TRIPLANAR]) {
- code += " AO = dot(triplanar_texture(texture_ambient_occlusion,uv2_power_normal,uv2_triplanar_pos),ao_texture_channel);\n";
+ code += " AO = dot(triplanar_texture(texture_ambient_occlusion, uv2_power_normal, uv2_triplanar_pos), ao_texture_channel);\n";
} else {
- code += " AO = dot(texture(texture_ambient_occlusion,base_uv2),ao_texture_channel);\n";
+ code += " AO = dot(texture(texture_ambient_occlusion, base_uv2), ao_texture_channel);\n";
}
} else {
if (flags[FLAG_UV1_USE_TRIPLANAR]) {
- code += " AO = dot(triplanar_texture(texture_ambient_occlusion,uv1_power_normal,uv1_triplanar_pos),ao_texture_channel);\n";
+ code += " AO = dot(triplanar_texture(texture_ambient_occlusion, uv1_power_normal, uv1_triplanar_pos), ao_texture_channel);\n";
} else {
- code += " AO = dot(texture(texture_ambient_occlusion,base_uv),ao_texture_channel);\n";
+ code += " AO = dot(texture(texture_ambient_occlusion, base_uv), ao_texture_channel);\n";
}
}
} else {
@@ -1453,75 +1730,103 @@ void BaseMaterial3D::_update_shader() {
}
if (features[FEATURE_SUBSURFACE_SCATTERING]) {
+ code += R"(
+ // Subsurface Scattering: Enabled
+)";
if (flags[FLAG_UV1_USE_TRIPLANAR]) {
- code += " float sss_tex = triplanar_texture(texture_subsurface_scattering,uv1_power_normal,uv1_triplanar_pos).r;\n";
+ code += " float sss_tex = triplanar_texture(texture_subsurface_scattering, uv1_power_normal, uv1_triplanar_pos).r;\n";
} else {
- code += " float sss_tex = texture(texture_subsurface_scattering,base_uv).r;\n";
+ code += " float sss_tex = texture(texture_subsurface_scattering, base_uv).r;\n";
}
- code += " SSS_STRENGTH=subsurface_scattering_strength*sss_tex;\n";
+ code += " SSS_STRENGTH = subsurface_scattering_strength * sss_tex;\n";
}
if (features[FEATURE_SUBSURFACE_TRANSMITTANCE]) {
+ code += R"(
+ // Subsurface Scattering Transmittance: Enabled
+)";
if (flags[FLAG_UV1_USE_TRIPLANAR]) {
- code += " vec4 trans_color_tex = triplanar_texture(texture_subsurface_transmittance,uv1_power_normal,uv1_triplanar_pos);\n";
+ code += " vec4 trans_color_tex = triplanar_texture(texture_subsurface_transmittance, uv1_power_normal, uv1_triplanar_pos);\n";
} else {
- code += " vec4 trans_color_tex = texture(texture_subsurface_transmittance,base_uv);\n";
+ code += " vec4 trans_color_tex = texture(texture_subsurface_transmittance, base_uv);\n";
}
- code += " SSS_TRANSMITTANCE_COLOR=transmittance_color*trans_color_tex;\n";
+ code += " SSS_TRANSMITTANCE_COLOR = transmittance_color * trans_color_tex;\n";
- code += " SSS_TRANSMITTANCE_DEPTH=transmittance_depth;\n";
- code += " SSS_TRANSMITTANCE_BOOST=transmittance_boost;\n";
+ code += R"( SSS_TRANSMITTANCE_DEPTH = transmittance_depth;
+ SSS_TRANSMITTANCE_BOOST = transmittance_boost;
+)";
}
if (features[FEATURE_BACKLIGHT]) {
+ code += R"(
+ // Backlight: Enabled
+)";
if (flags[FLAG_UV1_USE_TRIPLANAR]) {
- code += " vec3 backlight_tex = triplanar_texture(texture_backlight,uv1_power_normal,uv1_triplanar_pos).rgb;\n";
+ code += " vec3 backlight_tex = triplanar_texture(texture_backlight, uv1_power_normal, uv1_triplanar_pos).rgb;\n";
} else {
- code += " vec3 backlight_tex = texture(texture_backlight,base_uv).rgb;\n";
+ code += " vec3 backlight_tex = texture(texture_backlight, base_uv).rgb;\n";
}
- code += " BACKLIGHT = (backlight.rgb+backlight_tex);\n";
+ code += " BACKLIGHT = (backlight.rgb + backlight_tex);\n";
}
if (features[FEATURE_DETAIL]) {
- bool triplanar = (flags[FLAG_UV1_USE_TRIPLANAR] && detail_uv == DETAIL_UV_1) || (flags[FLAG_UV2_USE_TRIPLANAR] && detail_uv == DETAIL_UV_2);
-
+ code += R"(
+ // Detail: Enabled
+)";
+ const bool triplanar = (flags[FLAG_UV1_USE_TRIPLANAR] && detail_uv == DETAIL_UV_1) || (flags[FLAG_UV2_USE_TRIPLANAR] && detail_uv == DETAIL_UV_2);
if (triplanar) {
- String tp_uv = detail_uv == DETAIL_UV_1 ? "uv1" : "uv2";
- code += " vec4 detail_tex = triplanar_texture(texture_detail_albedo," + tp_uv + "_power_normal," + tp_uv + "_triplanar_pos);\n";
- code += " vec4 detail_norm_tex = triplanar_texture(texture_detail_normal," + tp_uv + "_power_normal," + tp_uv + "_triplanar_pos);\n";
-
+ const String tp_uv = detail_uv == DETAIL_UV_1 ? "uv1" : "uv2";
+ code += vformat(R"( vec4 detail_tex = triplanar_texture(texture_detail_albedo, %s_power_normal, %s_triplanar_pos);
+ vec4 detail_norm_tex = triplanar_texture(texture_detail_normal, %s_power_normal, %s_triplanar_pos);
+)",
+ tp_uv, tp_uv, tp_uv, tp_uv);
} else {
- String det_uv = detail_uv == DETAIL_UV_1 ? "base_uv" : "base_uv2";
- code += " vec4 detail_tex = texture(texture_detail_albedo," + det_uv + ");\n";
- code += " vec4 detail_norm_tex = texture(texture_detail_normal," + det_uv + ");\n";
+ const String det_uv = detail_uv == DETAIL_UV_1 ? "base_uv" : "base_uv2";
+ code += vformat(R"( vec4 detail_tex = texture(texture_detail_albedo, %s);
+ vec4 detail_norm_tex = texture(texture_detail_normal, %s);
+)",
+ det_uv, det_uv);
}
if (flags[FLAG_UV1_USE_TRIPLANAR]) {
- code += " vec4 detail_mask_tex = triplanar_texture(texture_detail_mask,uv1_power_normal,uv1_triplanar_pos);\n";
+ code += " vec4 detail_mask_tex = triplanar_texture(texture_detail_mask, uv1_power_normal, uv1_triplanar_pos);\n";
} else {
- code += " vec4 detail_mask_tex = texture(texture_detail_mask,base_uv);\n";
+ code += " vec4 detail_mask_tex = texture(texture_detail_mask, base_uv);\n";
}
switch (detail_blend_mode) {
case BLEND_MODE_MIX: {
- code += " vec3 detail = mix(ALBEDO.rgb,detail_tex.rgb,detail_tex.a);\n";
+ code += R"(
+ // Detail Blend Mode: Mix
+ vec3 detail = mix(ALBEDO.rgb, detail_tex.rgb, detail_tex.a);
+)";
} break;
case BLEND_MODE_ADD: {
- code += " vec3 detail = mix(ALBEDO.rgb,ALBEDO.rgb+detail_tex.rgb,detail_tex.a);\n";
+ code += R"(
+ // Detail Blend Mode: Add
+ vec3 detail = mix(ALBEDO.rgb, ALBEDO.rgb + detail_tex.rgb, detail_tex.a);
+)";
} break;
case BLEND_MODE_SUB: {
- code += " vec3 detail = mix(ALBEDO.rgb,ALBEDO.rgb-detail_tex.rgb,detail_tex.a);\n";
+ code += R"(
+ // Detail Blend Mode: Subtract
+ vec3 detail = mix(ALBEDO.rgb, ALBEDO.rgb - detail_tex.rgb, detail_tex.a);
+)";
} break;
case BLEND_MODE_MUL: {
- code += " vec3 detail = mix(ALBEDO.rgb,ALBEDO.rgb*detail_tex.rgb,detail_tex.a);\n";
+ code += R"(
+ // Detail Blend Mode: Multiply
+ vec3 detail = mix(ALBEDO.rgb, ALBEDO.rgb * detail_tex.rgb, detail_tex.a);
+)";
} break;
case BLEND_MODE_MAX:
break; // Internal value, skip.
}
- code += " vec3 detail_norm = mix(NORMAL_MAP,detail_norm_tex.rgb,detail_tex.a);\n";
- code += " NORMAL_MAP = mix(NORMAL_MAP,detail_norm,detail_mask_tex.r);\n";
- code += " ALBEDO.rgb = mix(ALBEDO.rgb,detail,detail_mask_tex.r);\n";
+ code += R"( vec3 detail_norm = mix(NORMAL_MAP, detail_norm_tex.rgb, detail_tex.a);
+ NORMAL_MAP = mix(NORMAL_MAP, detail_norm, detail_mask_tex.r);
+ ALBEDO.rgb = mix(ALBEDO.rgb, detail, detail_mask_tex.r);
+)";
}
code += "}\n";
diff --git a/scene/resources/navigation_mesh_source_geometry_data_2d.cpp b/scene/resources/navigation_mesh_source_geometry_data_2d.cpp
index 7c33aa9e38..dfa3b598c8 100644
--- a/scene/resources/navigation_mesh_source_geometry_data_2d.cpp
+++ b/scene/resources/navigation_mesh_source_geometry_data_2d.cpp
@@ -35,6 +35,12 @@
void NavigationMeshSourceGeometryData2D::clear() {
traversable_outlines.clear();
obstruction_outlines.clear();
+ clear_projected_obstructions();
+}
+
+void NavigationMeshSourceGeometryData2D::clear_projected_obstructions() {
+ RWLockWrite write_lock(geometry_rwlock);
+ _projected_obstructions.clear();
}
void NavigationMeshSourceGeometryData2D::_set_traversable_outlines(const Vector<Vector<Vector2>> &p_traversable_outlines) {
@@ -117,6 +123,109 @@ void NavigationMeshSourceGeometryData2D::merge(const Ref<NavigationMeshSourceGeo
// No need to worry about `root_node_transform` here as the data is already xformed.
traversable_outlines.append_array(p_other_geometry->traversable_outlines);
obstruction_outlines.append_array(p_other_geometry->obstruction_outlines);
+
+ if (p_other_geometry->_projected_obstructions.size() > 0) {
+ RWLockWrite write_lock(geometry_rwlock);
+
+ for (const ProjectedObstruction &other_projected_obstruction : p_other_geometry->_projected_obstructions) {
+ ProjectedObstruction projected_obstruction;
+ projected_obstruction.vertices.resize(other_projected_obstruction.vertices.size());
+
+ const float *other_obstruction_vertices_ptr = other_projected_obstruction.vertices.ptr();
+ float *obstruction_vertices_ptrw = projected_obstruction.vertices.ptrw();
+
+ for (int j = 0; j < other_projected_obstruction.vertices.size(); j++) {
+ obstruction_vertices_ptrw[j] = other_obstruction_vertices_ptr[j];
+ }
+
+ projected_obstruction.carve = other_projected_obstruction.carve;
+
+ _projected_obstructions.push_back(projected_obstruction);
+ }
+ }
+}
+
+void NavigationMeshSourceGeometryData2D::add_projected_obstruction(const Vector<Vector2> &p_vertices, bool p_carve) {
+ ERR_FAIL_COND(p_vertices.size() < 2);
+
+ ProjectedObstruction projected_obstruction;
+ projected_obstruction.vertices.resize(p_vertices.size() * 2);
+ projected_obstruction.carve = p_carve;
+
+ float *obstruction_vertices_ptrw = projected_obstruction.vertices.ptrw();
+
+ int vertex_index = 0;
+ for (const Vector2 &vertex : p_vertices) {
+ obstruction_vertices_ptrw[vertex_index++] = vertex.x;
+ obstruction_vertices_ptrw[vertex_index++] = vertex.y;
+ }
+
+ RWLockWrite write_lock(geometry_rwlock);
+ _projected_obstructions.push_back(projected_obstruction);
+}
+
+void NavigationMeshSourceGeometryData2D::set_projected_obstructions(const Array &p_array) {
+ clear_projected_obstructions();
+
+ for (int i = 0; i < p_array.size(); i++) {
+ Dictionary data = p_array[i];
+ ERR_FAIL_COND(!data.has("version"));
+
+ uint32_t po_version = data["version"];
+
+ if (po_version == 1) {
+ ERR_FAIL_COND(!data.has("vertices"));
+ ERR_FAIL_COND(!data.has("carve"));
+ }
+
+ ProjectedObstruction projected_obstruction;
+ projected_obstruction.vertices = Vector<float>(data["vertices"]);
+ projected_obstruction.carve = data["carve"];
+
+ RWLockWrite write_lock(geometry_rwlock);
+ _projected_obstructions.push_back(projected_obstruction);
+ }
+}
+
+Vector<NavigationMeshSourceGeometryData2D::ProjectedObstruction> NavigationMeshSourceGeometryData2D::_get_projected_obstructions() const {
+ RWLockRead read_lock(geometry_rwlock);
+ return _projected_obstructions;
+}
+
+Array NavigationMeshSourceGeometryData2D::get_projected_obstructions() const {
+ RWLockRead read_lock(geometry_rwlock);
+
+ Array ret;
+ ret.resize(_projected_obstructions.size());
+
+ for (int i = 0; i < _projected_obstructions.size(); i++) {
+ const ProjectedObstruction &projected_obstruction = _projected_obstructions[i];
+
+ Dictionary data;
+ data["version"] = (int)ProjectedObstruction::VERSION;
+ data["vertices"] = projected_obstruction.vertices;
+ data["carve"] = projected_obstruction.carve;
+
+ ret[i] = data;
+ }
+
+ return ret;
+}
+
+bool NavigationMeshSourceGeometryData2D::_set(const StringName &p_name, const Variant &p_value) {
+ if (p_name == "projected_obstructions") {
+ set_projected_obstructions(p_value);
+ return true;
+ }
+ return false;
+}
+
+bool NavigationMeshSourceGeometryData2D::_get(const StringName &p_name, Variant &r_ret) const {
+ if (p_name == "projected_obstructions") {
+ r_ret = get_projected_obstructions();
+ return true;
+ }
+ return false;
}
void NavigationMeshSourceGeometryData2D::_bind_methods() {
@@ -134,6 +243,12 @@ void NavigationMeshSourceGeometryData2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("merge", "other_geometry"), &NavigationMeshSourceGeometryData2D::merge);
+ ClassDB::bind_method(D_METHOD("add_projected_obstruction", "vertices", "carve"), &NavigationMeshSourceGeometryData2D::add_projected_obstruction);
+ ClassDB::bind_method(D_METHOD("clear_projected_obstructions"), &NavigationMeshSourceGeometryData2D::clear_projected_obstructions);
+ ClassDB::bind_method(D_METHOD("set_projected_obstructions", "projected_obstructions"), &NavigationMeshSourceGeometryData2D::set_projected_obstructions);
+ ClassDB::bind_method(D_METHOD("get_projected_obstructions"), &NavigationMeshSourceGeometryData2D::get_projected_obstructions);
+
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "traversable_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_traversable_outlines", "get_traversable_outlines");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "obstruction_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_obstruction_outlines", "get_obstruction_outlines");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "projected_obstructions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_projected_obstructions", "get_projected_obstructions");
}
diff --git a/scene/resources/navigation_mesh_source_geometry_data_2d.h b/scene/resources/navigation_mesh_source_geometry_data_2d.h
index 4accdbc1f4..0e321fbeb9 100644
--- a/scene/resources/navigation_mesh_source_geometry_data_2d.h
+++ b/scene/resources/navigation_mesh_source_geometry_data_2d.h
@@ -31,19 +31,36 @@
#ifndef NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_2D_H
#define NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_2D_H
+#include "core/os/rw_lock.h"
#include "scene/2d/node_2d.h"
#include "scene/resources/navigation_polygon.h"
class NavigationMeshSourceGeometryData2D : public Resource {
GDCLASS(NavigationMeshSourceGeometryData2D, Resource);
+ RWLock geometry_rwlock;
Vector<Vector<Vector2>> traversable_outlines;
Vector<Vector<Vector2>> obstruction_outlines;
+public:
+ struct ProjectedObstruction;
+
+private:
+ Vector<ProjectedObstruction> _projected_obstructions;
+
protected:
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
static void _bind_methods();
public:
+ struct ProjectedObstruction {
+ static inline uint32_t VERSION = 1; // Increase when format changes so we can detect outdated formats and provide compatibility.
+
+ Vector<float> vertices;
+ bool carve = false;
+ };
+
void _set_traversable_outlines(const Vector<Vector<Vector2>> &p_traversable_outlines);
const Vector<Vector<Vector2>> &_get_traversable_outlines() const { return traversable_outlines; }
@@ -70,6 +87,13 @@ public:
bool has_data() { return traversable_outlines.size(); };
void clear();
+ void clear_projected_obstructions();
+
+ void add_projected_obstruction(const Vector<Vector2> &p_vertices, bool p_carve);
+ Vector<ProjectedObstruction> _get_projected_obstructions() const;
+
+ void set_projected_obstructions(const Array &p_array);
+ Array get_projected_obstructions() const;
void merge(const Ref<NavigationMeshSourceGeometryData2D> &p_other_geometry);
diff --git a/scene/resources/navigation_mesh_source_geometry_data_3d.cpp b/scene/resources/navigation_mesh_source_geometry_data_3d.cpp
index 43fb592bba..1bd98fe1ac 100644
--- a/scene/resources/navigation_mesh_source_geometry_data_3d.cpp
+++ b/scene/resources/navigation_mesh_source_geometry_data_3d.cpp
@@ -41,6 +41,12 @@ void NavigationMeshSourceGeometryData3D::set_indices(const Vector<int> &p_indice
void NavigationMeshSourceGeometryData3D::clear() {
vertices.clear();
indices.clear();
+ clear_projected_obstructions();
+}
+
+void NavigationMeshSourceGeometryData3D::clear_projected_obstructions() {
+ RWLockWrite write_lock(geometry_rwlock);
+ _projected_obstructions.clear();
}
void NavigationMeshSourceGeometryData3D::_add_vertex(const Vector3 &p_vec3) {
@@ -174,6 +180,121 @@ void NavigationMeshSourceGeometryData3D::merge(const Ref<NavigationMeshSourceGeo
for (int64_t i = number_of_indices_before_merge; i < indices.size(); i++) {
indices.set(i, indices[i] + number_of_vertices_before_merge / 3);
}
+
+ if (p_other_geometry->_projected_obstructions.size() > 0) {
+ RWLockWrite write_lock(geometry_rwlock);
+
+ for (const ProjectedObstruction &other_projected_obstruction : p_other_geometry->_projected_obstructions) {
+ ProjectedObstruction projected_obstruction;
+ projected_obstruction.vertices.resize(other_projected_obstruction.vertices.size());
+
+ const float *other_obstruction_vertices_ptr = other_projected_obstruction.vertices.ptr();
+ float *obstruction_vertices_ptrw = projected_obstruction.vertices.ptrw();
+
+ for (int j = 0; j < other_projected_obstruction.vertices.size(); j++) {
+ obstruction_vertices_ptrw[j] = other_obstruction_vertices_ptr[j];
+ }
+
+ projected_obstruction.elevation = other_projected_obstruction.elevation;
+ projected_obstruction.height = other_projected_obstruction.height;
+ projected_obstruction.carve = other_projected_obstruction.carve;
+
+ _projected_obstructions.push_back(projected_obstruction);
+ }
+ }
+}
+
+void NavigationMeshSourceGeometryData3D::add_projected_obstruction(const Vector<Vector3> &p_vertices, float p_elevation, float p_height, bool p_carve) {
+ ERR_FAIL_COND(p_vertices.size() < 3);
+ ERR_FAIL_COND(p_height < 0.0);
+
+ ProjectedObstruction projected_obstruction;
+ projected_obstruction.vertices.resize(p_vertices.size() * 3);
+ projected_obstruction.elevation = p_elevation;
+ projected_obstruction.height = p_height;
+ projected_obstruction.carve = p_carve;
+
+ float *obstruction_vertices_ptrw = projected_obstruction.vertices.ptrw();
+
+ int vertex_index = 0;
+ for (const Vector3 &vertex : p_vertices) {
+ obstruction_vertices_ptrw[vertex_index++] = vertex.x;
+ obstruction_vertices_ptrw[vertex_index++] = vertex.y;
+ obstruction_vertices_ptrw[vertex_index++] = vertex.z;
+ }
+
+ RWLockWrite write_lock(geometry_rwlock);
+ _projected_obstructions.push_back(projected_obstruction);
+}
+
+void NavigationMeshSourceGeometryData3D::set_projected_obstructions(const Array &p_array) {
+ clear_projected_obstructions();
+
+ for (int i = 0; i < p_array.size(); i++) {
+ Dictionary data = p_array[i];
+ ERR_FAIL_COND(!data.has("version"));
+
+ uint32_t po_version = data["version"];
+
+ if (po_version == 1) {
+ ERR_FAIL_COND(!data.has("vertices"));
+ ERR_FAIL_COND(!data.has("elevation"));
+ ERR_FAIL_COND(!data.has("height"));
+ ERR_FAIL_COND(!data.has("carve"));
+ }
+
+ ProjectedObstruction projected_obstruction;
+ projected_obstruction.vertices = Vector<float>(data["vertices"]);
+ projected_obstruction.elevation = data["elevation"];
+ projected_obstruction.height = data["height"];
+ projected_obstruction.carve = data["carve"];
+
+ RWLockWrite write_lock(geometry_rwlock);
+ _projected_obstructions.push_back(projected_obstruction);
+ }
+}
+
+Vector<NavigationMeshSourceGeometryData3D::ProjectedObstruction> NavigationMeshSourceGeometryData3D::_get_projected_obstructions() const {
+ RWLockRead read_lock(geometry_rwlock);
+ return _projected_obstructions;
+}
+
+Array NavigationMeshSourceGeometryData3D::get_projected_obstructions() const {
+ RWLockRead read_lock(geometry_rwlock);
+
+ Array ret;
+ ret.resize(_projected_obstructions.size());
+
+ for (int i = 0; i < _projected_obstructions.size(); i++) {
+ const ProjectedObstruction &projected_obstruction = _projected_obstructions[i];
+
+ Dictionary data;
+ data["version"] = (int)ProjectedObstruction::VERSION;
+ data["vertices"] = projected_obstruction.vertices;
+ data["elevation"] = projected_obstruction.elevation;
+ data["height"] = projected_obstruction.height;
+ data["carve"] = projected_obstruction.carve;
+
+ ret[i] = data;
+ }
+
+ return ret;
+}
+
+bool NavigationMeshSourceGeometryData3D::_set(const StringName &p_name, const Variant &p_value) {
+ if (p_name == "projected_obstructions") {
+ set_projected_obstructions(p_value);
+ return true;
+ }
+ return false;
+}
+
+bool NavigationMeshSourceGeometryData3D::_get(const StringName &p_name, Variant &r_ret) const {
+ if (p_name == "projected_obstructions") {
+ r_ret = get_projected_obstructions();
+ return true;
+ }
+ return false;
}
void NavigationMeshSourceGeometryData3D::_bind_methods() {
@@ -191,6 +312,12 @@ void NavigationMeshSourceGeometryData3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_faces", "faces", "xform"), &NavigationMeshSourceGeometryData3D::add_faces);
ClassDB::bind_method(D_METHOD("merge", "other_geometry"), &NavigationMeshSourceGeometryData3D::merge);
+ ClassDB::bind_method(D_METHOD("add_projected_obstruction", "vertices", "elevation", "height", "carve"), &NavigationMeshSourceGeometryData3D::add_projected_obstruction);
+ ClassDB::bind_method(D_METHOD("clear_projected_obstructions"), &NavigationMeshSourceGeometryData3D::clear_projected_obstructions);
+ ClassDB::bind_method(D_METHOD("set_projected_obstructions", "projected_obstructions"), &NavigationMeshSourceGeometryData3D::set_projected_obstructions);
+ ClassDB::bind_method(D_METHOD("get_projected_obstructions"), &NavigationMeshSourceGeometryData3D::get_projected_obstructions);
+
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "indices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_indices", "get_indices");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "projected_obstructions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_projected_obstructions", "get_projected_obstructions");
}
diff --git a/scene/resources/navigation_mesh_source_geometry_data_3d.h b/scene/resources/navigation_mesh_source_geometry_data_3d.h
index 981f20a74b..79e2f3740d 100644
--- a/scene/resources/navigation_mesh_source_geometry_data_3d.h
+++ b/scene/resources/navigation_mesh_source_geometry_data_3d.h
@@ -31,15 +31,25 @@
#ifndef NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_3D_H
#define NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_3D_H
+#include "core/os/rw_lock.h"
#include "scene/resources/mesh.h"
class NavigationMeshSourceGeometryData3D : public Resource {
GDCLASS(NavigationMeshSourceGeometryData3D, Resource);
+ RWLock geometry_rwlock;
Vector<float> vertices;
Vector<int> indices;
+public:
+ struct ProjectedObstruction;
+
+private:
+ Vector<ProjectedObstruction> _projected_obstructions;
+
protected:
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
static void _bind_methods();
private:
@@ -49,6 +59,15 @@ private:
void _add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform);
public:
+ struct ProjectedObstruction {
+ static inline uint32_t VERSION = 1; // Increase when format changes so we can detect outdated formats and provide compatibility.
+
+ Vector<float> vertices;
+ float elevation = 0.0;
+ float height = 0.0;
+ bool carve = false;
+ };
+
// kept root node transform here on the geometry data
// if we add this transform to all exposed functions we need to break comp on all functions later
// when navmesh changes from global transform to relative to navregion
@@ -63,6 +82,7 @@ public:
bool has_data() { return vertices.size() && indices.size(); };
void clear();
+ void clear_projected_obstructions();
void add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform);
void add_mesh_array(const Array &p_mesh_array, const Transform3D &p_xform);
@@ -70,6 +90,12 @@ public:
void merge(const Ref<NavigationMeshSourceGeometryData3D> &p_other_geometry);
+ void add_projected_obstruction(const Vector<Vector3> &p_vertices, float p_elevation, float p_height, bool p_carve);
+ Vector<ProjectedObstruction> _get_projected_obstructions() const;
+
+ void set_projected_obstructions(const Array &p_array);
+ Array get_projected_obstructions() const;
+
NavigationMeshSourceGeometryData3D() {}
~NavigationMeshSourceGeometryData3D() { clear(); }
};
diff --git a/scene/resources/navigation_polygon.cpp b/scene/resources/navigation_polygon.cpp
index e830153330..274b13a487 100644
--- a/scene/resources/navigation_polygon.cpp
+++ b/scene/resources/navigation_polygon.cpp
@@ -251,8 +251,7 @@ void NavigationPolygon::make_polygons_from_outlines() {
}
const Vector2 *r = ol.ptr();
for (int j = 0; j < olsize; j++) {
- outside_point.x = MAX(r[j].x, outside_point.x);
- outside_point.y = MAX(r[j].y, outside_point.y);
+ outside_point = outside_point.max(r[j]);
}
}
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index c7c2ddbb18..5cd9ec7ad0 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -4710,68 +4710,61 @@ String VisualShaderNodeExpression::get_expression() const {
return expression;
}
+bool VisualShaderNodeExpression::_is_valid_identifier_char(char32_t p_c) const {
+ return p_c == '_' || (p_c >= 'A' && p_c <= 'Z') || (p_c >= 'a' && p_c <= 'z') || (p_c >= '0' && p_c <= '9');
+}
+
+String VisualShaderNodeExpression::_replace_port_names(const Vector<Pair<String, String>> &p_pairs, const String &p_expression) const {
+ String _expression = p_expression;
+
+ for (const Pair<String, String> &pair : p_pairs) {
+ String from = pair.first;
+ String to = pair.second;
+ int search_idx = 0;
+ int len = from.length();
+
+ while (true) {
+ int index = _expression.find(from, search_idx);
+ if (index == -1) {
+ break;
+ }
+
+ int left_index = index - 1;
+ int right_index = index + len;
+ bool left_correct = left_index <= 0 || !_is_valid_identifier_char(_expression[left_index]);
+ bool right_correct = right_index >= _expression.length() || !_is_valid_identifier_char(_expression[right_index]);
+
+ if (left_correct && right_correct) {
+ _expression = _expression.erase(index, len);
+ _expression = _expression.insert(index, to);
+
+ search_idx = index + to.length();
+ } else {
+ search_idx = index + len;
+ }
+ }
+ }
+
+ return _expression;
+}
+
String VisualShaderNodeExpression::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
String _expression = expression;
_expression = _expression.insert(0, "\n");
_expression = _expression.replace("\n", "\n ");
- static Vector<String> pre_symbols;
- if (pre_symbols.is_empty()) {
- pre_symbols.push_back(" ");
- pre_symbols.push_back(",");
- pre_symbols.push_back(";");
- pre_symbols.push_back("{");
- pre_symbols.push_back("[");
- pre_symbols.push_back("]");
- pre_symbols.push_back("(");
- pre_symbols.push_back(" ");
- pre_symbols.push_back("-");
- pre_symbols.push_back("*");
- pre_symbols.push_back("/");
- pre_symbols.push_back("+");
- pre_symbols.push_back("=");
- pre_symbols.push_back("&");
- pre_symbols.push_back("|");
- pre_symbols.push_back("!");
- }
-
- static Vector<String> post_symbols;
- if (post_symbols.is_empty()) {
- post_symbols.push_back(" ");
- post_symbols.push_back("\n");
- post_symbols.push_back(",");
- post_symbols.push_back(";");
- post_symbols.push_back("}");
- post_symbols.push_back("[");
- post_symbols.push_back("]");
- post_symbols.push_back(")");
- post_symbols.push_back(" ");
- post_symbols.push_back(".");
- post_symbols.push_back("-");
- post_symbols.push_back("*");
- post_symbols.push_back("/");
- post_symbols.push_back("+");
- post_symbols.push_back("=");
- post_symbols.push_back("&");
- post_symbols.push_back("|");
- post_symbols.push_back("!");
- }
-
+ Vector<Pair<String, String>> input_port_names;
for (int i = 0; i < get_input_port_count(); i++) {
- for (int j = 0; j < pre_symbols.size(); j++) {
- for (int k = 0; k < post_symbols.size(); k++) {
- _expression = _expression.replace(pre_symbols[j] + get_input_port_name(i) + post_symbols[k], pre_symbols[j] + p_input_vars[i] + post_symbols[k]);
- }
- }
+ input_port_names.push_back(Pair<String, String>(get_input_port_name(i), p_input_vars[i]));
}
+ _expression = _replace_port_names(input_port_names, _expression);
+
+ Vector<Pair<String, String>> output_port_names;
for (int i = 0; i < get_output_port_count(); i++) {
- for (int j = 0; j < pre_symbols.size(); j++) {
- for (int k = 0; k < post_symbols.size(); k++) {
- _expression = _expression.replace(pre_symbols[j] + get_output_port_name(i) + post_symbols[k], pre_symbols[j] + p_output_vars[i] + post_symbols[k]);
- }
- }
+ output_port_names.push_back(Pair<String, String>(get_output_port_name(i), p_output_vars[i]));
}
+ _expression = _replace_port_names(output_port_names, _expression);
String output_initializer;
diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h
index 09ea9a8890..f02ada7ee8 100644
--- a/scene/resources/visual_shader.h
+++ b/scene/resources/visual_shader.h
@@ -819,6 +819,10 @@ public:
class VisualShaderNodeExpression : public VisualShaderNodeGroupBase {
GDCLASS(VisualShaderNodeExpression, VisualShaderNodeGroupBase);
+private:
+ bool _is_valid_identifier_char(char32_t p_c) const;
+ String _replace_port_names(const Vector<Pair<String, String>> &p_pairs, const String &p_expression) const;
+
protected:
String expression = "";
diff --git a/scu_builders.py b/scu_builders.py
index 8cd87a13d5..0435c0a4f5 100644
--- a/scu_builders.py
+++ b/scu_builders.py
@@ -8,7 +8,7 @@ from os.path import normpath, basename
base_folder_path = str(Path(__file__).parent) + "/"
base_folder_only = os.path.basename(os.path.normpath(base_folder_path))
-_verbose = True # Set manually for debug prints
+_verbose = False # Set manually for debug prints
_scu_folders = set()
_max_includes_per_scu = 1024
diff --git a/servers/camera_server.h b/servers/camera_server.h
index 4e0b75fbf6..e9bcd771d2 100644
--- a/servers/camera_server.h
+++ b/servers/camera_server.h
@@ -71,7 +71,7 @@ protected:
static void _bind_methods();
- template <class T>
+ template <typename T>
static CameraServer *_create_builtin() {
return memnew(T);
}
@@ -79,7 +79,7 @@ protected:
public:
static CameraServer *get_singleton();
- template <class T>
+ template <typename T>
static void make_default() {
create_func = _create_builtin<T>;
}
diff --git a/servers/native_menu.cpp b/servers/native_menu.cpp
index 4372c10707..d1894ba6c3 100644
--- a/servers/native_menu.cpp
+++ b/servers/native_menu.cpp
@@ -48,6 +48,7 @@ void NativeMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_size", "rid"), &NativeMenu::get_size);
ClassDB::bind_method(D_METHOD("popup", "rid", "position"), &NativeMenu::popup);
+ ClassDB::bind_method(D_METHOD("set_interface_direction", "rid", "is_rtl"), &NativeMenu::set_interface_direction);
ClassDB::bind_method(D_METHOD("set_popup_open_callback", "rid", "callback"), &NativeMenu::set_popup_open_callback);
ClassDB::bind_method(D_METHOD("get_popup_open_callback", "rid"), &NativeMenu::get_popup_open_callback);
ClassDB::bind_method(D_METHOD("set_popup_close_callback", "rid", "callback"), &NativeMenu::set_popup_close_callback);
@@ -111,6 +112,9 @@ void NativeMenu::_bind_methods() {
BIND_ENUM_CONSTANT(FEATURE_GLOBAL_MENU);
BIND_ENUM_CONSTANT(FEATURE_POPUP_MENU);
+ BIND_ENUM_CONSTANT(FEATURE_OPEN_CLOSE_CALLBACK);
+ BIND_ENUM_CONSTANT(FEATURE_HOVER_CALLBACK);
+ BIND_ENUM_CONSTANT(FEATURE_KEY_CALLBACK);
BIND_ENUM_CONSTANT(INVALID_MENU_ID);
BIND_ENUM_CONSTANT(MAIN_MENU_ID);
@@ -173,6 +177,10 @@ void NativeMenu::popup(const RID &p_rid, const Vector2i &p_position) {
WARN_PRINT("Global menus are not supported on this platform.");
}
+void NativeMenu::set_interface_direction(const RID &p_rid, bool p_is_rtl) {
+ WARN_PRINT("Global menus are not supported on this platform.");
+}
+
void NativeMenu::set_popup_open_callback(const RID &p_rid, const Callable &p_callback) {
WARN_PRINT("Global menus are not supported on this platform.");
}
diff --git a/servers/native_menu.h b/servers/native_menu.h
index bb61caa633..f65e193972 100644
--- a/servers/native_menu.h
+++ b/servers/native_menu.h
@@ -54,6 +54,9 @@ public:
enum Feature {
FEATURE_GLOBAL_MENU,
FEATURE_POPUP_MENU,
+ FEATURE_OPEN_CLOSE_CALLBACK,
+ FEATURE_HOVER_CALLBACK,
+ FEATURE_KEY_CALLBACK,
};
enum SystemMenus {
@@ -78,6 +81,8 @@ public:
virtual Size2 get_size(const RID &p_rid) const;
virtual void popup(const RID &p_rid, const Vector2i &p_position);
+ virtual void set_interface_direction(const RID &p_rid, bool p_is_rtl);
+
virtual void set_popup_open_callback(const RID &p_rid, const Callable &p_callback);
virtual Callable get_popup_open_callback(const RID &p_rid) const;
virtual void set_popup_close_callback(const RID &p_rid, const Callable &p_callback);
diff --git a/servers/physics_2d/godot_broad_phase_2d_bvh.h b/servers/physics_2d/godot_broad_phase_2d_bvh.h
index 36fd1223b0..6c1fae5cb2 100644
--- a/servers/physics_2d/godot_broad_phase_2d_bvh.h
+++ b/servers/physics_2d/godot_broad_phase_2d_bvh.h
@@ -38,7 +38,7 @@
#include "core/math/vector2.h"
class GodotBroadPhase2DBVH : public GodotBroadPhase2D {
- template <class T>
+ template <typename T>
class UserPairTestFunction {
public:
static bool user_pair_check(const T *p_a, const T *p_b) {
@@ -47,7 +47,7 @@ class GodotBroadPhase2DBVH : public GodotBroadPhase2D {
}
};
- template <class T>
+ template <typename T>
class UserCullTestFunction {
public:
static bool user_cull_check(const T *p_a, const T *p_b) {
diff --git a/servers/physics_2d/godot_collision_solver_2d_sat.cpp b/servers/physics_2d/godot_collision_solver_2d_sat.cpp
index c85fdc592a..daa9982b2e 100644
--- a/servers/physics_2d/godot_collision_solver_2d_sat.cpp
+++ b/servers/physics_2d/godot_collision_solver_2d_sat.cpp
@@ -171,7 +171,7 @@ static void _generate_contacts_from_supports(const Vector2 *p_points_A, int p_po
contacts_func(points_A, pointcount_A, points_B, pointcount_B, p_collector);
}
-template <class ShapeA, class ShapeB, bool castA = false, bool castB = false, bool withMargin = false>
+template <typename ShapeA, typename ShapeB, bool castA = false, bool castB = false, bool withMargin = false>
class SeparatorAxisTest2D {
const ShapeA *shape_A = nullptr;
const ShapeB *shape_B = nullptr;
diff --git a/servers/physics_3d/godot_broad_phase_3d_bvh.h b/servers/physics_3d/godot_broad_phase_3d_bvh.h
index 6279658ec4..63968dea64 100644
--- a/servers/physics_3d/godot_broad_phase_3d_bvh.h
+++ b/servers/physics_3d/godot_broad_phase_3d_bvh.h
@@ -36,7 +36,7 @@
#include "core/math/bvh.h"
class GodotBroadPhase3DBVH : public GodotBroadPhase3D {
- template <class T>
+ template <typename T>
class UserPairTestFunction {
public:
static bool user_pair_check(const T *p_a, const T *p_b) {
@@ -45,7 +45,7 @@ class GodotBroadPhase3DBVH : public GodotBroadPhase3D {
}
};
- template <class T>
+ template <typename T>
class UserCullTestFunction {
public:
static bool user_cull_check(const T *p_a, const T *p_b) {
diff --git a/servers/physics_3d/godot_collision_solver_3d_sat.cpp b/servers/physics_3d/godot_collision_solver_3d_sat.cpp
index 3aa2f256c1..beaa30eb84 100644
--- a/servers/physics_3d/godot_collision_solver_3d_sat.cpp
+++ b/servers/physics_3d/godot_collision_solver_3d_sat.cpp
@@ -612,7 +612,7 @@ static void _generate_contacts_from_supports(const Vector3 *p_points_A, int p_po
contacts_func(points_A, pointcount_A, points_B, pointcount_B, p_callback);
}
-template <class ShapeA, class ShapeB, bool withMargin = false>
+template <typename ShapeA, typename ShapeB, bool withMargin = false>
class SeparatorAxisTest {
const ShapeA *shape_A = nullptr;
const ShapeB *shape_B = nullptr;
diff --git a/servers/physics_3d/godot_shape_3d.cpp b/servers/physics_3d/godot_shape_3d.cpp
index 872d26aff6..ea389ff59c 100644
--- a/servers/physics_3d/godot_shape_3d.cpp
+++ b/servers/physics_3d/godot_shape_3d.cpp
@@ -2016,9 +2016,7 @@ void GodotHeightMapShape3D::_get_cell(const Vector3 &p_point, int &r_x, int &r_y
Vector3 pos_local = shape_aabb.position + local_origin;
Vector3 clamped_point(p_point);
- clamped_point.x = CLAMP(p_point.x, pos_local.x, pos_local.x + shape_aabb.size.x);
- clamped_point.y = CLAMP(p_point.y, pos_local.y, pos_local.y + shape_aabb.size.y);
- clamped_point.z = CLAMP(p_point.z, pos_local.z, pos_local.z + shape_aabb.size.z);
+ clamped_point = p_point.clamp(pos_local, pos_local + shape_aabb.size);
r_x = (clamped_point.x < 0.0) ? (clamped_point.x - 0.5) : (clamped_point.x + 0.5);
r_y = (clamped_point.y < 0.0) ? (clamped_point.y - 0.5) : (clamped_point.y + 0.5);
diff --git a/servers/physics_3d/godot_space_3d.cpp b/servers/physics_3d/godot_space_3d.cpp
index 46b6689533..9a6ba776b4 100644
--- a/servers/physics_3d/godot_space_3d.cpp
+++ b/servers/physics_3d/godot_space_3d.cpp
@@ -655,7 +655,7 @@ bool GodotSpace3D::test_body_motion(GodotBody3D *p_body, const PhysicsServer3D::
//this took about a week to get right..
//but is it right? who knows at this point..
- ERR_FAIL_INDEX_V(p_parameters.max_collisions, PhysicsServer3D::MotionResult::MAX_COLLISIONS, false);
+ ERR_FAIL_COND_V(p_parameters.max_collisions < 0 || p_parameters.max_collisions > PhysicsServer3D::MotionResult::MAX_COLLISIONS, false);
if (r_result) {
*r_result = PhysicsServer3D::MotionResult();
diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp
index cd38aa05f3..e32164ea98 100644
--- a/servers/rendering/renderer_canvas_cull.cpp
+++ b/servers/rendering/renderer_canvas_cull.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "core/math/geometry_2d.h"
+#include "core/math/transform_interpolator.h"
#include "renderer_viewport.h"
#include "rendering_server_default.h"
#include "rendering_server_globals.h"
@@ -81,7 +82,7 @@ void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, Transform2
if (r_items) {
r_items[r_index] = child_items[i];
child_items[i]->ysort_xform = p_transform;
- child_items[i]->ysort_pos = p_transform.xform(child_items[i]->xform.columns[2]);
+ child_items[i]->ysort_pos = p_transform.xform(child_items[i]->xform_curr.columns[2]);
child_items[i]->material_owner = child_items[i]->use_parent_material ? p_material_owner : nullptr;
child_items[i]->ysort_modulate = p_modulate;
child_items[i]->ysort_index = r_index;
@@ -98,7 +99,7 @@ void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, Transform2
r_index++;
if (child_items[i]->sort_y) {
- _collect_ysort_children(child_items[i], p_transform * child_items[i]->xform, child_items[i]->use_parent_material ? p_material_owner : child_items[i], p_modulate * child_items[i]->modulate, r_items, r_index, abs_z);
+ _collect_ysort_children(child_items[i], p_transform * child_items[i]->xform_curr, child_items[i]->use_parent_material ? p_material_owner : child_items[i], p_modulate * child_items[i]->modulate, r_items, r_index, abs_z);
}
}
}
@@ -244,7 +245,14 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
}
}
- Transform2D xform = ci->xform;
+ Transform2D final_xform;
+ if (!_interpolation_data.interpolation_enabled || !ci->interpolated) {
+ final_xform = ci->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, final_xform, f);
+ }
+
Transform2D parent_xform = p_parent_xform;
Point2 repeat_size = p_repeat_size;
@@ -258,19 +266,19 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
ci->repeat_times = repeat_times;
if (repeat_size.x || repeat_size.y) {
- rect.size += repeat_size * repeat_times / ci->xform.get_scale();
+ rect.size += repeat_size * repeat_times / final_xform.get_scale();
rect.position -= repeat_size * (repeat_times / 2);
}
}
if (snapping_2d_transforms_to_pixel) {
- xform.columns[2] = xform.columns[2].round();
+ final_xform.columns[2] = final_xform.columns[2].round();
parent_xform.columns[2] = parent_xform.columns[2].round();
}
- xform = parent_xform * xform;
+ final_xform = parent_xform * final_xform;
- Rect2 global_rect = xform.xform(rect);
+ Rect2 global_rect = final_xform.xform(rect);
global_rect.position += p_clip_rect.position;
if (ci->use_parent_material && p_material_owner) {
@@ -324,7 +332,7 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
child_item_count = ci->ysort_children_count + 1;
child_items = (Item **)alloca(child_item_count * sizeof(Item *));
- ci->ysort_xform = ci->xform.affine_inverse();
+ ci->ysort_xform = final_xform.affine_inverse();
ci->ysort_pos = Vector2();
ci->ysort_modulate = Color(1, 1, 1, 1);
ci->ysort_index = 0;
@@ -337,7 +345,7 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
sorter.sort(child_items, child_item_count);
for (i = 0; i < child_item_count; i++) {
- _cull_canvas_item(child_items[i], xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, false, p_canvas_cull_mask, repeat_size, repeat_times);
+ _cull_canvas_item(child_items[i], final_xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, false, p_canvas_cull_mask, repeat_size, repeat_times);
}
} else {
RendererCanvasRender::Item *canvas_group_from = nullptr;
@@ -347,7 +355,7 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
canvas_group_from = r_z_last_list[zidx];
}
- _attach_canvas_item_for_draw(ci, p_canvas_clip, r_z_list, r_z_last_list, xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from);
+ _attach_canvas_item_for_draw(ci, p_canvas_clip, r_z_list, r_z_last_list, final_xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from);
}
} else {
RendererCanvasRender::Item *canvas_group_from = nullptr;
@@ -361,14 +369,14 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
if (!child_items[i]->behind && !use_canvas_group) {
continue;
}
- _cull_canvas_item(child_items[i], xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times);
+ _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times);
}
- _attach_canvas_item_for_draw(ci, p_canvas_clip, r_z_list, r_z_last_list, xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from);
+ _attach_canvas_item_for_draw(ci, p_canvas_clip, r_z_list, r_z_last_list, final_xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from);
for (int i = 0; i < child_item_count; i++) {
if (child_items[i]->behind || use_canvas_group) {
continue;
}
- _cull_canvas_item(child_items[i], xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times);
+ _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times);
}
}
}
@@ -512,7 +520,16 @@ void RendererCanvasCull::canvas_item_set_transform(RID p_item, const Transform2D
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_NULL(canvas_item);
- canvas_item->xform = p_transform;
+ if (_interpolation_data.interpolation_enabled && canvas_item->interpolated) {
+ if (!canvas_item->on_interpolate_transform_list) {
+ _interpolation_data.canvas_item_transform_update_list_curr->push_back(p_item);
+ canvas_item->on_interpolate_transform_list = true;
+ } else {
+ DEV_ASSERT(_interpolation_data.canvas_item_transform_update_list_curr->size() > 0);
+ }
+ }
+
+ canvas_item->xform_curr = p_transform;
}
void RendererCanvasCull::canvas_item_set_visibility_layer(RID p_item, uint32_t p_visibility_layer) {
@@ -1622,6 +1639,26 @@ bool RendererCanvasCull::canvas_item_get_debug_redraw() const {
return debug_redraw;
}
+void RendererCanvasCull::canvas_item_set_interpolated(RID p_item, bool p_interpolated) {
+ Item *canvas_item = canvas_item_owner.get_or_null(p_item);
+ ERR_FAIL_NULL(canvas_item);
+ canvas_item->interpolated = p_interpolated;
+}
+
+void RendererCanvasCull::canvas_item_reset_physics_interpolation(RID p_item) {
+ Item *canvas_item = canvas_item_owner.get_or_null(p_item);
+ ERR_FAIL_NULL(canvas_item);
+ canvas_item->xform_prev = canvas_item->xform_curr;
+}
+
+// Useful especially for origin shifting.
+void RendererCanvasCull::canvas_item_transform_physics_interpolation(RID p_item, const Transform2D &p_transform) {
+ Item *canvas_item = canvas_item_owner.get_or_null(p_item);
+ ERR_FAIL_NULL(canvas_item);
+ canvas_item->xform_prev = p_transform * canvas_item->xform_prev;
+ canvas_item->xform_curr = p_transform * canvas_item->xform_curr;
+}
+
void RendererCanvasCull::canvas_item_set_canvas_group_mode(RID p_item, RS::CanvasGroupMode p_mode, float p_clear_margin, bool p_fit_empty, float p_fit_margin, bool p_blur_mipmaps) {
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_NULL(canvas_item);
@@ -1720,7 +1757,16 @@ void RendererCanvasCull::canvas_light_set_transform(RID p_light, const Transform
RendererCanvasRender::Light *clight = canvas_light_owner.get_or_null(p_light);
ERR_FAIL_NULL(clight);
- clight->xform = p_transform;
+ if (_interpolation_data.interpolation_enabled && clight->interpolated) {
+ if (!clight->on_interpolate_transform_list) {
+ _interpolation_data.canvas_light_transform_update_list_curr->push_back(p_light);
+ clight->on_interpolate_transform_list = true;
+ } else {
+ DEV_ASSERT(_interpolation_data.canvas_light_transform_update_list_curr->size() > 0);
+ }
+ }
+
+ clight->xform_curr = p_transform;
}
void RendererCanvasCull::canvas_light_set_texture(RID p_light, RID p_texture) {
@@ -1839,6 +1885,25 @@ void RendererCanvasCull::canvas_light_set_shadow_smooth(RID p_light, float p_smo
clight->shadow_smooth = p_smooth;
}
+void RendererCanvasCull::canvas_light_set_interpolated(RID p_light, bool p_interpolated) {
+ RendererCanvasRender::Light *clight = canvas_light_owner.get_or_null(p_light);
+ ERR_FAIL_NULL(clight);
+ clight->interpolated = p_interpolated;
+}
+
+void RendererCanvasCull::canvas_light_reset_physics_interpolation(RID p_light) {
+ RendererCanvasRender::Light *clight = canvas_light_owner.get_or_null(p_light);
+ ERR_FAIL_NULL(clight);
+ clight->xform_prev = clight->xform_curr;
+}
+
+void RendererCanvasCull::canvas_light_transform_physics_interpolation(RID p_light, const Transform2D &p_transform) {
+ RendererCanvasRender::Light *clight = canvas_light_owner.get_or_null(p_light);
+ ERR_FAIL_NULL(clight);
+ clight->xform_prev = p_transform * clight->xform_prev;
+ clight->xform_curr = p_transform * clight->xform_curr;
+}
+
RID RendererCanvasCull::canvas_light_occluder_allocate() {
return canvas_light_occluder_owner.allocate_rid();
}
@@ -1911,7 +1976,16 @@ void RendererCanvasCull::canvas_light_occluder_set_transform(RID p_occluder, con
RendererCanvasRender::LightOccluderInstance *occluder = canvas_light_occluder_owner.get_or_null(p_occluder);
ERR_FAIL_NULL(occluder);
- occluder->xform = p_xform;
+ if (_interpolation_data.interpolation_enabled && occluder->interpolated) {
+ if (!occluder->on_interpolate_transform_list) {
+ _interpolation_data.canvas_light_occluder_transform_update_list_curr->push_back(p_occluder);
+ occluder->on_interpolate_transform_list = true;
+ } else {
+ DEV_ASSERT(_interpolation_data.canvas_light_occluder_transform_update_list_curr->size() > 0);
+ }
+ }
+
+ occluder->xform_curr = p_xform;
}
void RendererCanvasCull::canvas_light_occluder_set_light_mask(RID p_occluder, int p_mask) {
@@ -1921,6 +1995,25 @@ void RendererCanvasCull::canvas_light_occluder_set_light_mask(RID p_occluder, in
occluder->light_mask = p_mask;
}
+void RendererCanvasCull::canvas_light_occluder_set_interpolated(RID p_occluder, bool p_interpolated) {
+ RendererCanvasRender::LightOccluderInstance *occluder = canvas_light_occluder_owner.get_or_null(p_occluder);
+ ERR_FAIL_NULL(occluder);
+ occluder->interpolated = p_interpolated;
+}
+
+void RendererCanvasCull::canvas_light_occluder_reset_physics_interpolation(RID p_occluder) {
+ RendererCanvasRender::LightOccluderInstance *occluder = canvas_light_occluder_owner.get_or_null(p_occluder);
+ ERR_FAIL_NULL(occluder);
+ occluder->xform_prev = occluder->xform_curr;
+}
+
+void RendererCanvasCull::canvas_light_occluder_transform_physics_interpolation(RID p_occluder, const Transform2D &p_transform) {
+ RendererCanvasRender::LightOccluderInstance *occluder = canvas_light_occluder_owner.get_or_null(p_occluder);
+ ERR_FAIL_NULL(occluder);
+ occluder->xform_prev = p_transform * occluder->xform_prev;
+ occluder->xform_curr = p_transform * occluder->xform_curr;
+}
+
RID RendererCanvasCull::canvas_occluder_polygon_allocate() {
return canvas_light_occluder_polygon_owner.allocate_rid();
}
@@ -2075,6 +2168,7 @@ bool RendererCanvasCull::free(RID p_rid) {
} else if (canvas_item_owner.owns(p_rid)) {
Item *canvas_item = canvas_item_owner.get_or_null(p_rid);
ERR_FAIL_NULL_V(canvas_item, true);
+ _interpolation_data.notify_free_canvas_item(p_rid, *canvas_item);
if (canvas_item->parent.is_valid()) {
if (canvas_owner.owns(canvas_item->parent)) {
@@ -2114,6 +2208,7 @@ bool RendererCanvasCull::free(RID p_rid) {
} else if (canvas_light_owner.owns(p_rid)) {
RendererCanvasRender::Light *canvas_light = canvas_light_owner.get_or_null(p_rid);
ERR_FAIL_NULL_V(canvas_light, true);
+ _interpolation_data.notify_free_canvas_light(p_rid, *canvas_light);
if (canvas_light->canvas.is_valid()) {
Canvas *canvas = canvas_owner.get_or_null(canvas_light->canvas);
@@ -2129,6 +2224,7 @@ bool RendererCanvasCull::free(RID p_rid) {
} else if (canvas_light_occluder_owner.owns(p_rid)) {
RendererCanvasRender::LightOccluderInstance *occluder = canvas_light_occluder_owner.get_or_null(p_rid);
ERR_FAIL_NULL_V(occluder, true);
+ _interpolation_data.notify_free_canvas_light_occluder(p_rid, *occluder);
if (occluder->polygon.is_valid()) {
LightOccluderPolygon *occluder_poly = canvas_light_occluder_polygon_owner.get_or_null(occluder->polygon);
@@ -2162,7 +2258,7 @@ bool RendererCanvasCull::free(RID p_rid) {
return true;
}
-template <class T>
+template <typename T>
void RendererCanvasCull::_free_rids(T &p_owner, const char *p_type) {
List<RID> owned;
p_owner.get_owned_list(&owned);
@@ -2186,6 +2282,81 @@ void RendererCanvasCull::finalize() {
_free_rids(canvas_light_occluder_polygon_owner, "CanvasLightOccluderPolygon");
}
+void RendererCanvasCull::tick() {
+ if (_interpolation_data.interpolation_enabled) {
+ update_interpolation_tick(true);
+ }
+}
+
+void RendererCanvasCull::update_interpolation_tick(bool p_process) {
+#define GODOT_UPDATE_INTERPOLATION_TICK(m_list_prev, m_list_curr, m_type, m_owner_list) \
+ /* Detect any that were on the previous transform list that are no longer active. */ \
+ for (unsigned int n = 0; n < _interpolation_data.m_list_prev->size(); n++) { \
+ const RID &rid = (*_interpolation_data.m_list_prev)[n]; \
+ m_type *item = m_owner_list.get_or_null(rid); \
+ /* no longer active? (either the instance deleted or no longer being transformed) */ \
+ if (item && !item->on_interpolate_transform_list) { \
+ item->xform_prev = item->xform_curr; \
+ } \
+ } \
+ /* and now for any in the transform list (being actively interpolated), */ \
+ /* keep the previous transform value up to date and ready for next tick */ \
+ if (p_process) { \
+ for (unsigned int n = 0; n < _interpolation_data.m_list_curr->size(); n++) { \
+ const RID &rid = (*_interpolation_data.m_list_curr)[n]; \
+ m_type *item = m_owner_list.get_or_null(rid); \
+ if (item) { \
+ item->xform_prev = item->xform_curr; \
+ item->on_interpolate_transform_list = false; \
+ } \
+ } \
+ } \
+ SWAP(_interpolation_data.m_list_curr, _interpolation_data.m_list_prev); \
+ _interpolation_data.m_list_curr->clear();
+
+ GODOT_UPDATE_INTERPOLATION_TICK(canvas_item_transform_update_list_prev, canvas_item_transform_update_list_curr, Item, canvas_item_owner);
+ GODOT_UPDATE_INTERPOLATION_TICK(canvas_light_transform_update_list_prev, canvas_light_transform_update_list_curr, RendererCanvasRender::Light, canvas_light_owner);
+ GODOT_UPDATE_INTERPOLATION_TICK(canvas_light_occluder_transform_update_list_prev, canvas_light_occluder_transform_update_list_curr, RendererCanvasRender::LightOccluderInstance, canvas_light_occluder_owner);
+
+#undef GODOT_UPDATE_INTERPOLATION_TICK
+}
+
+void RendererCanvasCull::InterpolationData::notify_free_canvas_item(RID p_rid, RendererCanvasCull::Item &r_canvas_item) {
+ r_canvas_item.on_interpolate_transform_list = false;
+
+ if (!interpolation_enabled) {
+ return;
+ }
+
+ // If the instance was on any of the lists, remove.
+ canvas_item_transform_update_list_curr->erase_multiple_unordered(p_rid);
+ canvas_item_transform_update_list_prev->erase_multiple_unordered(p_rid);
+}
+
+void RendererCanvasCull::InterpolationData::notify_free_canvas_light(RID p_rid, RendererCanvasRender::Light &r_canvas_light) {
+ r_canvas_light.on_interpolate_transform_list = false;
+
+ if (!interpolation_enabled) {
+ return;
+ }
+
+ // If the instance was on any of the lists, remove.
+ canvas_light_transform_update_list_curr->erase_multiple_unordered(p_rid);
+ canvas_light_transform_update_list_prev->erase_multiple_unordered(p_rid);
+}
+
+void RendererCanvasCull::InterpolationData::notify_free_canvas_light_occluder(RID p_rid, RendererCanvasRender::LightOccluderInstance &r_canvas_light_occluder) {
+ r_canvas_light_occluder.on_interpolate_transform_list = false;
+
+ if (!interpolation_enabled) {
+ return;
+ }
+
+ // If the instance was on any of the lists, remove.
+ canvas_light_occluder_transform_update_list_curr->erase_multiple_unordered(p_rid);
+ canvas_light_occluder_transform_update_list_prev->erase_multiple_unordered(p_rid);
+}
+
RendererCanvasCull::RendererCanvasCull() {
z_list = (RendererCanvasRender::Item **)memalloc(z_range * sizeof(RendererCanvasRender::Item *));
z_last_list = (RendererCanvasRender::Item **)memalloc(z_range * sizeof(RendererCanvasRender::Item *));
diff --git a/servers/rendering/renderer_canvas_cull.h b/servers/rendering/renderer_canvas_cull.h
index 8d40c2fbaa..961506ca28 100644
--- a/servers/rendering/renderer_canvas_cull.h
+++ b/servers/rendering/renderer_canvas_cull.h
@@ -170,7 +170,7 @@ public:
RID_Owner<Item, true> canvas_item_owner;
RID_Owner<RendererCanvasRender::Light, true> canvas_light_owner;
- template <class T>
+ template <typename T>
void _free_rids(T &p_owner, const char *p_type);
bool disable_scale;
@@ -271,6 +271,10 @@ public:
void canvas_item_set_debug_redraw(bool p_enabled);
bool canvas_item_get_debug_redraw() const;
+ void canvas_item_set_interpolated(RID p_item, bool p_interpolated);
+ void canvas_item_reset_physics_interpolation(RID p_item);
+ void canvas_item_transform_physics_interpolation(RID p_item, const Transform2D &p_transform);
+
RID canvas_light_allocate();
void canvas_light_initialize(RID p_rid);
@@ -297,6 +301,10 @@ public:
void canvas_light_set_shadow_color(RID p_light, const Color &p_color);
void canvas_light_set_shadow_smooth(RID p_light, float p_smooth);
+ void canvas_light_set_interpolated(RID p_light, bool p_interpolated);
+ void canvas_light_reset_physics_interpolation(RID p_light);
+ void canvas_light_transform_physics_interpolation(RID p_light, const Transform2D &p_transform);
+
RID canvas_light_occluder_allocate();
void canvas_light_occluder_initialize(RID p_rid);
@@ -307,6 +315,10 @@ public:
void canvas_light_occluder_set_transform(RID p_occluder, const Transform2D &p_xform);
void canvas_light_occluder_set_light_mask(RID p_occluder, int p_mask);
+ void canvas_light_occluder_set_interpolated(RID p_occluder, bool p_interpolated);
+ void canvas_light_occluder_reset_physics_interpolation(RID p_occluder);
+ void canvas_light_occluder_transform_physics_interpolation(RID p_occluder, const Transform2D &p_transform);
+
RID canvas_occluder_polygon_allocate();
void canvas_occluder_polygon_initialize(RID p_rid);
@@ -336,6 +348,32 @@ public:
void finalize();
+ /* INTERPOLATION */
+
+ void tick();
+ void update_interpolation_tick(bool p_process = true);
+ void set_physics_interpolation_enabled(bool p_enabled) { _interpolation_data.interpolation_enabled = p_enabled; }
+
+ struct InterpolationData {
+ void notify_free_canvas_item(RID p_rid, RendererCanvasCull::Item &r_canvas_item);
+ void notify_free_canvas_light(RID p_rid, RendererCanvasRender::Light &r_canvas_light);
+ void notify_free_canvas_light_occluder(RID p_rid, RendererCanvasRender::LightOccluderInstance &r_canvas_light_occluder);
+
+ LocalVector<RID> canvas_item_transform_update_lists[2];
+ LocalVector<RID> *canvas_item_transform_update_list_curr = &canvas_item_transform_update_lists[0];
+ LocalVector<RID> *canvas_item_transform_update_list_prev = &canvas_item_transform_update_lists[1];
+
+ LocalVector<RID> canvas_light_transform_update_lists[2];
+ LocalVector<RID> *canvas_light_transform_update_list_curr = &canvas_light_transform_update_lists[0];
+ LocalVector<RID> *canvas_light_transform_update_list_prev = &canvas_light_transform_update_lists[1];
+
+ LocalVector<RID> canvas_light_occluder_transform_update_lists[2];
+ LocalVector<RID> *canvas_light_occluder_transform_update_list_curr = &canvas_light_occluder_transform_update_lists[0];
+ LocalVector<RID> *canvas_light_occluder_transform_update_list_prev = &canvas_light_occluder_transform_update_lists[1];
+
+ bool interpolation_enabled = false;
+ } _interpolation_data;
+
RendererCanvasCull();
~RendererCanvasCull();
};
diff --git a/servers/rendering/renderer_canvas_render.h b/servers/rendering/renderer_canvas_render.h
index fd31a40a3e..4a56548932 100644
--- a/servers/rendering/renderer_canvas_render.h
+++ b/servers/rendering/renderer_canvas_render.h
@@ -51,9 +51,12 @@ public:
};
struct Light {
- bool enabled;
+ bool enabled : 1;
+ bool on_interpolate_transform_list : 1;
+ bool interpolated : 1;
Color color;
- Transform2D xform;
+ Transform2D xform_curr;
+ Transform2D xform_prev;
float height;
float energy;
float scale;
@@ -97,6 +100,8 @@ public:
Light() {
version = 0;
enabled = true;
+ on_interpolate_transform_list = false;
+ interpolated = true;
color = Color(1, 1, 1);
shadow_color = Color(0, 0, 0, 0);
height = 0;
@@ -307,11 +312,17 @@ public:
Rect2 rect;
};
- Transform2D xform;
- bool clip;
- bool visible;
- bool behind;
- bool update_when_visible;
+ // For interpolation we store the current local xform,
+ // and the previous xform from the previous tick.
+ Transform2D xform_curr;
+ Transform2D xform_prev;
+
+ bool clip : 1;
+ bool visible : 1;
+ bool behind : 1;
+ bool update_when_visible : 1;
+ bool on_interpolate_transform_list : 1;
+ bool interpolated : 1;
struct CanvasGroup {
RS::CanvasGroupMode mode;
@@ -366,7 +377,7 @@ public:
mutable double debug_redraw_time = 0;
#endif
- template <class T>
+ template <typename T>
T *alloc_command() {
T *command = nullptr;
if (commands == nullptr) {
@@ -472,6 +483,8 @@ public:
texture_filter = RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT;
texture_repeat = RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT;
repeat_source = false;
+ on_interpolate_transform_list = false;
+ interpolated = true;
}
virtual ~Item() {
clear();
@@ -487,12 +500,15 @@ public:
virtual void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used, RenderingMethod::RenderInfo *r_render_info = nullptr) = 0;
struct LightOccluderInstance {
- bool enabled;
+ bool enabled : 1;
+ bool on_interpolate_transform_list : 1;
+ bool interpolated : 1;
RID canvas;
RID polygon;
RID occluder;
Rect2 aabb_cache;
- Transform2D xform;
+ Transform2D xform_curr;
+ Transform2D xform_prev;
Transform2D xform_cache;
int light_mask;
bool sdf_collision;
@@ -502,6 +518,8 @@ public:
LightOccluderInstance() {
enabled = true;
+ on_interpolate_transform_list = false;
+ interpolated = false;
sdf_collision = false;
next = nullptr;
light_mask = 1;
diff --git a/servers/rendering/renderer_rd/cluster_builder_rd.cpp b/servers/rendering/renderer_rd/cluster_builder_rd.cpp
index e661fd9217..41df6107a8 100644
--- a/servers/rendering/renderer_rd/cluster_builder_rd.cpp
+++ b/servers/rendering/renderer_rd/cluster_builder_rd.cpp
@@ -62,15 +62,17 @@ ClusterBuilderSharedDataRD::ClusterBuilderSharedDataRD() {
defines = "\n#define USE_ATTACHMENT\n";
}
+ RD::PipelineRasterizationState rasterization_state;
+ rasterization_state.enable_depth_clamp = true;
Vector<String> versions;
versions.push_back("");
cluster_render.cluster_render_shader.initialize(versions, defines);
cluster_render.shader_version = cluster_render.cluster_render_shader.version_create();
cluster_render.shader = cluster_render.cluster_render_shader.version_get_shader(cluster_render.shader_version, 0);
- cluster_render.shader_pipelines[ClusterRender::PIPELINE_NORMAL] = RD::get_singleton()->render_pipeline_create(cluster_render.shader, fb_format, vertex_format, RD::RENDER_PRIMITIVE_TRIANGLES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), RD::PipelineDepthStencilState(), blend_state, 0);
+ cluster_render.shader_pipelines[ClusterRender::PIPELINE_NORMAL] = RD::get_singleton()->render_pipeline_create(cluster_render.shader, fb_format, vertex_format, RD::RENDER_PRIMITIVE_TRIANGLES, rasterization_state, RD::PipelineMultisampleState(), RD::PipelineDepthStencilState(), blend_state, 0);
RD::PipelineMultisampleState ms;
ms.sample_count = RD::TEXTURE_SAMPLES_4;
- cluster_render.shader_pipelines[ClusterRender::PIPELINE_MSAA] = RD::get_singleton()->render_pipeline_create(cluster_render.shader, fb_format, vertex_format, RD::RENDER_PRIMITIVE_TRIANGLES, RD::PipelineRasterizationState(), ms, RD::PipelineDepthStencilState(), blend_state, 0);
+ cluster_render.shader_pipelines[ClusterRender::PIPELINE_MSAA] = RD::get_singleton()->render_pipeline_create(cluster_render.shader, fb_format, vertex_format, RD::RENDER_PRIMITIVE_TRIANGLES, rasterization_state, ms, RD::PipelineDepthStencilState(), blend_state, 0);
}
{
Vector<String> versions;
diff --git a/servers/rendering/renderer_rd/environment/fog.cpp b/servers/rendering/renderer_rd/environment/fog.cpp
index 1d144bedcf..48537a97d9 100644
--- a/servers/rendering/renderer_rd/environment/fog.cpp
+++ b/servers/rendering/renderer_rd/environment/fog.cpp
@@ -509,9 +509,7 @@ Vector3i Fog::_point_get_position_in_froxel_volume(const Vector3 &p_point, float
fog_position.z = Math::pow(float(fog_position.z), float(1.0 / volumetric_fog_detail_spread));
fog_position = fog_position * fog_size - Vector3(0.5, 0.5, 0.5);
- fog_position.x = CLAMP(fog_position.x, 0.0, fog_size.x);
- fog_position.y = CLAMP(fog_position.y, 0.0, fog_size.y);
- fog_position.z = CLAMP(fog_position.z, 0.0, fog_size.z);
+ fog_position = fog_position.clamp(Vector3(), fog_size);
return Vector3i(fog_position);
}
@@ -680,8 +678,8 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
max = Vector3i(1, 1, 1);
for (int j = 0; j < 8; j++) {
- min = Vector3i(MIN(min.x, points[j].x), MIN(min.y, points[j].y), MIN(min.z, points[j].z));
- max = Vector3i(MAX(max.x, points[j].x), MAX(max.y, points[j].y), MAX(max.z, points[j].z));
+ min = min.min(points[j]);
+ max = max.max(points[j]);
}
kernel_size = max - min;
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index 673afc53e5..6f56711151 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -34,6 +34,7 @@
#include "core/math/geometry_2d.h"
#include "core/math/math_defs.h"
#include "core/math/math_funcs.h"
+#include "core/math/transform_interpolator.h"
#include "renderer_compositor_rd.h"
#include "servers/rendering/renderer_rd/storage_rd/material_storage.h"
#include "servers/rendering/renderer_rd/storage_rd/particles_storage.h"
@@ -427,7 +428,7 @@ void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_rend
Transform2D base_transform = p_canvas_transform_inverse * p_item->final_transform;
if (p_offset.x || p_offset.y) {
- base_transform *= Transform2D(0, p_offset / p_item->xform.get_scale());
+ base_transform *= Transform2D(0, p_offset / p_item->xform_curr.get_scale()); // TODO: Interpolate or explain why not needed.
}
Transform2D draw_transform;
@@ -1366,7 +1367,15 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
ERR_CONTINUE(!clight);
}
- Vector2 canvas_light_pos = p_canvas_transform.xform(l->xform.get_origin()); //convert light position to canvas coordinates, as all computation is done in canvas coords to avoid precision loss
+ Transform2D final_xform;
+ if (!RSG::canvas->_interpolation_data.interpolation_enabled || !l->interpolated) {
+ final_xform = l->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(l->xform_prev, l->xform_curr, final_xform, f);
+ }
+ // Convert light position to canvas coordinates, as all computation is done in canvas coordinates to avoid precision loss.
+ Vector2 canvas_light_pos = p_canvas_transform.xform(final_xform.get_origin());
state.light_uniforms[index].position[0] = canvas_light_pos.x;
state.light_uniforms[index].position[1] = canvas_light_pos.y;
diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
index dd94982f1a..f3ce432495 100644
--- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
@@ -3637,8 +3637,7 @@ void TextureStorage::_render_target_allocate_sdf(RenderTarget *rt) {
}
rt->process_size = size * scale / 100;
- rt->process_size.x = MAX(rt->process_size.x, 1);
- rt->process_size.y = MAX(rt->process_size.y, 1);
+ rt->process_size = rt->process_size.max(Size2i(1, 1));
tformat.format = RD::DATA_FORMAT_R16G16_SINT;
tformat.width = rt->process_size.width;
diff --git a/servers/rendering/renderer_scene_occlusion_cull.h b/servers/rendering/renderer_scene_occlusion_cull.h
index 565b393094..149d7b1cdb 100644
--- a/servers/rendering/renderer_scene_occlusion_cull.h
+++ b/servers/rendering/renderer_scene_occlusion_cull.h
@@ -65,7 +65,7 @@ public:
return false;
}
- Vector3 closest_point = Vector3(CLAMP(p_cam_position.x, p_bounds[0], p_bounds[3]), CLAMP(p_cam_position.y, p_bounds[1], p_bounds[4]), CLAMP(p_cam_position.z, p_bounds[2], p_bounds[5]));
+ Vector3 closest_point = p_cam_position.clamp(Vector3(p_bounds[0], p_bounds[1], p_bounds[2]), Vector3(p_bounds[3], p_bounds[4], p_bounds[5]));
if (closest_point == p_cam_position) {
return false;
diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp
index baa198626c..bafabf16b8 100644
--- a/servers/rendering/renderer_viewport.cpp
+++ b/servers/rendering/renderer_viewport.cpp
@@ -31,6 +31,7 @@
#include "renderer_viewport.h"
#include "core/config/project_settings.h"
+#include "core/math/transform_interpolator.h"
#include "core/object/worker_thread_pool.h"
#include "renderer_canvas_cull.h"
#include "renderer_scene_cull.h"
@@ -339,7 +340,14 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
if (!F->enabled) {
continue;
}
- F->xform_cache = xf * F->xform;
+
+ if (!RSG::canvas->_interpolation_data.interpolation_enabled || !F->interpolated) {
+ F->xform_cache = xf * F->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(F->xform_prev, F->xform_curr, F->xform_cache, f);
+ F->xform_cache = xf * F->xform_cache;
+ }
if (sdf_rect.intersects_transformed(F->xform_cache, F->aabb_cache)) {
F->next = occluders;
@@ -378,7 +386,14 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
Vector2 offset = tsize / 2.0;
cl->rect_cache = Rect2(-offset + cl->texture_offset, tsize);
- cl->xform_cache = xf * cl->xform;
+
+ if (!RSG::canvas->_interpolation_data.interpolation_enabled || !cl->interpolated) {
+ cl->xform_cache = xf * cl->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(cl->xform_prev, cl->xform_curr, cl->xform_cache, f);
+ cl->xform_cache = xf * cl->xform_cache;
+ }
if (clip_rect.intersects_transformed(cl->xform_cache, cl->rect_cache)) {
cl->filter_next_ptr = lights;
@@ -386,7 +401,7 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
Transform2D scale;
scale.scale(cl->rect_cache.size);
scale.columns[2] = cl->rect_cache.position;
- cl->light_shader_xform = xf * cl->xform * scale;
+ cl->light_shader_xform = cl->xform_cache * scale;
if (cl->use_shadow) {
cl->shadows_next_ptr = lights_with_shadow;
if (lights_with_shadow == nullptr) {
@@ -406,7 +421,13 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
if (cl->enabled) {
cl->filter_next_ptr = directional_lights;
directional_lights = cl;
- cl->xform_cache = xf * cl->xform;
+ if (!RSG::canvas->_interpolation_data.interpolation_enabled || !cl->interpolated) {
+ cl->xform_cache = xf * cl->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(cl->xform_prev, cl->xform_curr, cl->xform_cache, f);
+ cl->xform_cache = xf * cl->xform_cache;
+ }
cl->xform_cache.columns[2] = Vector2(); //translation is pointless
if (cl->use_shadow) {
cl->shadows_next_ptr = directional_lights_with_shadow;
@@ -441,7 +462,13 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
if (!F->enabled) {
continue;
}
- F->xform_cache = xf * F->xform;
+ if (!RSG::canvas->_interpolation_data.interpolation_enabled || !F->interpolated) {
+ F->xform_cache = xf * F->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(F->xform_prev, F->xform_curr, F->xform_cache, f);
+ F->xform_cache = xf * F->xform_cache;
+ }
if (shadow_rect.intersects_transformed(F->xform_cache, F->aabb_cache)) {
F->next = occluders;
occluders = F;
@@ -521,7 +548,13 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
if (!F->enabled) {
continue;
}
- F->xform_cache = xf * F->xform;
+ if (!RSG::canvas->_interpolation_data.interpolation_enabled || !F->interpolated) {
+ F->xform_cache = xf * F->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(F->xform_prev, F->xform_curr, F->xform_cache, f);
+ F->xform_cache = xf * F->xform_cache;
+ }
Transform2D localizer = F->xform_cache.affine_inverse();
for (int j = 0; j < point_count; j++) {
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index dc48ddbd56..7dfff0b76f 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -5182,7 +5182,7 @@ void RenderingDevice::_save_pipeline_cache(void *p_data) {
}
}
-template <class T>
+template <typename T>
void RenderingDevice::_free_rids(T &p_owner, const char *p_type) {
List<RID> owned;
p_owner.get_owned_list(&owned);
diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h
index 8c88c08950..9a898a2fca 100644
--- a/servers/rendering/rendering_device.h
+++ b/servers/rendering/rendering_device.h
@@ -1294,7 +1294,7 @@ private:
void _stall_for_previous_frames();
void _flush_and_stall_for_all_frames();
- template <class T>
+ template <typename T>
void _free_rids(T &p_owner, const char *p_type);
#ifdef DEV_ENABLED
diff --git a/servers/rendering/rendering_device_driver.h b/servers/rendering/rendering_device_driver.h
index ee33dc103c..09a0412941 100644
--- a/servers/rendering/rendering_device_driver.h
+++ b/servers/rendering/rendering_device_driver.h
@@ -56,7 +56,7 @@
// This may one day be used in Godot for interoperability between C arrays, Vector and LocalVector.
// (See https://github.com/godotengine/godot-proposals/issues/5144.)
-template <class T>
+template <typename T>
class VectorView {
const T *_ptr = nullptr;
const uint32_t _size = 0;
@@ -97,20 +97,20 @@ public:
#define ENUM_MEMBERS_EQUAL(m_a, m_b) ((int64_t)m_a == (int64_t)m_b)
// This helps using a single paged allocator for many resource types.
-template <class... RESOURCE_TYPES>
+template <typename... RESOURCE_TYPES>
struct VersatileResourceTemplate {
static constexpr size_t RESOURCE_SIZES[] = { sizeof(RESOURCE_TYPES)... };
static constexpr size_t MAX_RESOURCE_SIZE = std::max_element(RESOURCE_SIZES, RESOURCE_SIZES + sizeof...(RESOURCE_TYPES))[0];
uint8_t data[MAX_RESOURCE_SIZE];
- template <class T>
+ template <typename T>
static T *allocate(PagedAllocator<VersatileResourceTemplate> &p_allocator) {
T *obj = (T *)p_allocator.alloc();
memnew_placement(obj, T);
return obj;
}
- template <class T>
+ template <typename T>
static void free(PagedAllocator<VersatileResourceTemplate> &p_allocator, T *p_object) {
p_object->~T();
p_allocator.free((VersatileResourceTemplate *)p_object);
diff --git a/servers/rendering/rendering_server_default.cpp b/servers/rendering/rendering_server_default.cpp
index b4c539dcff..f614070f68 100644
--- a/servers/rendering/rendering_server_default.cpp
+++ b/servers/rendering/rendering_server_default.cpp
@@ -370,6 +370,16 @@ void RenderingServerDefault::_thread_loop() {
_finish();
}
+/* INTERPOLATION */
+
+void RenderingServerDefault::tick() {
+ RSG::canvas->tick();
+}
+
+void RenderingServerDefault::set_physics_interpolation_enabled(bool p_enabled) {
+ RSG::canvas->set_physics_interpolation_enabled(p_enabled);
+}
+
/* EVENT QUEUING */
void RenderingServerDefault::sync() {
diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h
index 99fd683e1d..139624c777 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -926,6 +926,10 @@ public:
FUNC1(canvas_item_set_debug_redraw, bool)
FUNC0RC(bool, canvas_item_get_debug_redraw)
+ FUNC2(canvas_item_set_interpolated, RID, bool)
+ FUNC1(canvas_item_reset_physics_interpolation, RID)
+ FUNC2(canvas_item_transform_physics_interpolation, RID, const Transform2D &)
+
FUNCRIDSPLIT(canvas_light)
FUNC2(canvas_light_set_mode, RID, CanvasLightMode)
@@ -952,6 +956,10 @@ public:
FUNC2(canvas_light_set_shadow_color, RID, const Color &)
FUNC2(canvas_light_set_shadow_smooth, RID, float)
+ FUNC2(canvas_light_set_interpolated, RID, bool)
+ FUNC1(canvas_light_reset_physics_interpolation, RID)
+ FUNC2(canvas_light_transform_physics_interpolation, RID, const Transform2D &)
+
FUNCRIDSPLIT(canvas_light_occluder)
FUNC2(canvas_light_occluder_attach_to_canvas, RID, RID)
FUNC2(canvas_light_occluder_set_enabled, RID, bool)
@@ -960,6 +968,10 @@ public:
FUNC2(canvas_light_occluder_set_transform, RID, const Transform2D &)
FUNC2(canvas_light_occluder_set_light_mask, RID, int)
+ FUNC2(canvas_light_occluder_set_interpolated, RID, bool)
+ FUNC1(canvas_light_occluder_reset_physics_interpolation, RID)
+ FUNC2(canvas_light_occluder_transform_physics_interpolation, RID, const Transform2D &)
+
FUNCRIDSPLIT(canvas_occluder_polygon)
FUNC3(canvas_occluder_polygon_set_shape, RID, const Vector<Vector2> &, bool)
@@ -1021,6 +1033,11 @@ public:
}
}
+ /* INTERPOLATION */
+
+ virtual void tick() override;
+ virtual void set_physics_interpolation_enabled(bool p_enabled) override;
+
/* EVENT QUEUING */
virtual void request_frame_drawn_callback(const Callable &p_callable) override;
diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h
index 737545b8ca..816a202b50 100644
--- a/servers/rendering/shader_language.h
+++ b/servers/rendering/shader_language.h
@@ -383,7 +383,7 @@ public:
virtual ~Node() {}
};
- template <class T>
+ template <typename T>
T *alloc_node() {
T *node = memnew(T);
node->next = nodes;
diff --git a/servers/rendering/storage/variant_converters.h b/servers/rendering/storage/variant_converters.h
index 6e3c07237e..7dbdb0f517 100644
--- a/servers/rendering/storage/variant_converters.h
+++ b/servers/rendering/storage/variant_converters.h
@@ -242,10 +242,10 @@ inline bool is_convertible_array(Variant::Type type) {
type == Variant::PACKED_COLOR_ARRAY;
}
-template <class, class = void>
+template <typename, typename = void>
inline constexpr bool is_vector_type_v = false;
-template <class T>
+template <typename T>
inline constexpr bool is_vector_type_v<T, std::void_t<decltype(T::AXIS_COUNT)>> = true;
template <typename T, typename P>
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 1ed0424839..994f6ad8b4 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -3219,6 +3219,9 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("canvas_item_set_modulate", "item", "color"), &RenderingServer::canvas_item_set_modulate);
ClassDB::bind_method(D_METHOD("canvas_item_set_self_modulate", "item", "color"), &RenderingServer::canvas_item_set_self_modulate);
ClassDB::bind_method(D_METHOD("canvas_item_set_draw_behind_parent", "item", "enabled"), &RenderingServer::canvas_item_set_draw_behind_parent);
+ ClassDB::bind_method(D_METHOD("canvas_item_set_interpolated", "item", "interpolated"), &RenderingServer::canvas_item_set_interpolated);
+ ClassDB::bind_method(D_METHOD("canvas_item_reset_physics_interpolation", "item"), &RenderingServer::canvas_item_reset_physics_interpolation);
+ ClassDB::bind_method(D_METHOD("canvas_item_transform_physics_interpolation", "item", "transform"), &RenderingServer::canvas_item_transform_physics_interpolation);
/* Primitives */
@@ -3302,6 +3305,9 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("canvas_light_set_shadow_color", "light", "color"), &RenderingServer::canvas_light_set_shadow_color);
ClassDB::bind_method(D_METHOD("canvas_light_set_shadow_smooth", "light", "smooth"), &RenderingServer::canvas_light_set_shadow_smooth);
ClassDB::bind_method(D_METHOD("canvas_light_set_blend_mode", "light", "mode"), &RenderingServer::canvas_light_set_blend_mode);
+ ClassDB::bind_method(D_METHOD("canvas_light_set_interpolated", "light", "interpolated"), &RenderingServer::canvas_light_set_interpolated);
+ ClassDB::bind_method(D_METHOD("canvas_light_reset_physics_interpolation", "light"), &RenderingServer::canvas_light_reset_physics_interpolation);
+ ClassDB::bind_method(D_METHOD("canvas_light_transform_physics_interpolation", "light", "transform"), &RenderingServer::canvas_light_transform_physics_interpolation);
BIND_ENUM_CONSTANT(CANVAS_LIGHT_MODE_POINT);
BIND_ENUM_CONSTANT(CANVAS_LIGHT_MODE_DIRECTIONAL);
@@ -3324,6 +3330,9 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("canvas_light_occluder_set_as_sdf_collision", "occluder", "enable"), &RenderingServer::canvas_light_occluder_set_as_sdf_collision);
ClassDB::bind_method(D_METHOD("canvas_light_occluder_set_transform", "occluder", "transform"), &RenderingServer::canvas_light_occluder_set_transform);
ClassDB::bind_method(D_METHOD("canvas_light_occluder_set_light_mask", "occluder", "mask"), &RenderingServer::canvas_light_occluder_set_light_mask);
+ ClassDB::bind_method(D_METHOD("canvas_light_occluder_set_interpolated", "occluder", "interpolated"), &RenderingServer::canvas_light_occluder_set_interpolated);
+ ClassDB::bind_method(D_METHOD("canvas_light_occluder_reset_physics_interpolation", "occluder"), &RenderingServer::canvas_light_occluder_reset_physics_interpolation);
+ ClassDB::bind_method(D_METHOD("canvas_light_occluder_transform_physics_interpolation", "occluder", "transform"), &RenderingServer::canvas_light_occluder_transform_physics_interpolation);
/* CANVAS LIGHT OCCLUDER POLYGON */
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index a67ae0a66c..a3a77bc57b 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -1476,6 +1476,10 @@ public:
virtual void canvas_item_set_debug_redraw(bool p_enabled) = 0;
virtual bool canvas_item_get_debug_redraw() const = 0;
+ virtual void canvas_item_set_interpolated(RID p_item, bool p_interpolated) = 0;
+ virtual void canvas_item_reset_physics_interpolation(RID p_item) = 0;
+ virtual void canvas_item_transform_physics_interpolation(RID p_item, const Transform2D &p_transform) = 0;
+
/* CANVAS LIGHT */
virtual RID canvas_light_create() = 0;
@@ -1523,6 +1527,10 @@ public:
virtual void canvas_light_set_shadow_color(RID p_light, const Color &p_color) = 0;
virtual void canvas_light_set_shadow_smooth(RID p_light, float p_smooth) = 0;
+ virtual void canvas_light_set_interpolated(RID p_light, bool p_interpolated) = 0;
+ virtual void canvas_light_reset_physics_interpolation(RID p_light) = 0;
+ virtual void canvas_light_transform_physics_interpolation(RID p_light, const Transform2D &p_transform) = 0;
+
/* CANVAS LIGHT OCCLUDER */
virtual RID canvas_light_occluder_create() = 0;
@@ -1533,6 +1541,10 @@ public:
virtual void canvas_light_occluder_set_transform(RID p_occluder, const Transform2D &p_xform) = 0;
virtual void canvas_light_occluder_set_light_mask(RID p_occluder, int p_mask) = 0;
+ virtual void canvas_light_occluder_set_interpolated(RID p_occluder, bool p_interpolated) = 0;
+ virtual void canvas_light_occluder_reset_physics_interpolation(RID p_occluder) = 0;
+ virtual void canvas_light_occluder_transform_physics_interpolation(RID p_occluder, const Transform2D &p_transform) = 0;
+
/* CANVAS LIGHT OCCLUDER POLYGON */
virtual RID canvas_occluder_polygon_create() = 0;
@@ -1604,6 +1616,11 @@ public:
virtual void free(RID p_rid) = 0; // Free RIDs associated with the rendering server.
+ /* INTERPOLATION */
+
+ virtual void tick() = 0;
+ virtual void set_physics_interpolation_enabled(bool p_enabled) = 0;
+
/* EVENT QUEUING */
virtual void request_frame_drawn_callback(const Callable &p_callable) = 0;
diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h
index da742d0183..027dcac92d 100644
--- a/tests/core/string/test_string.h
+++ b/tests/core/string/test_string.h
@@ -336,6 +336,14 @@ TEST_CASE("[String] Natural compare function test") {
CHECK(a.naturalnocasecmp_to("img10.png") < 0);
}
+TEST_CASE("[String] File compare function test") {
+ String a = "_img2.png";
+
+ CHECK(a.nocasecmp_to("img10.png") > 0);
+ CHECK_MESSAGE(a.filenocasecmp_to("img10.png") < 0, "Should sort before letters.");
+ CHECK_MESSAGE(a.filenocasecmp_to(".img10.png") > 0, "Should sort after period.");
+}
+
TEST_CASE("[String] hex_encode_buffer") {
static const uint8_t u8str[] = { 0x45, 0xE3, 0x81, 0x8A, 0x8F, 0xE3 };
String s = String::hex_encode_buffer(u8str, 6);
diff --git a/thirdparty/README.md b/thirdparty/README.md
index 2c9bc36545..d86c14cd14 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -769,9 +769,6 @@ Files extracted from upstream source:
- `src/sljit/`
- `AUTHORS` and `LICENCE`
-A sljit patch from upstream was backported to fix macOS < 11.0 compilation
-in 10.40, it can be found in the `patches` folder.
-
## recastnavigation
diff --git a/thirdparty/pcre2/patches/sljit-macos11-conditional.patch b/thirdparty/pcre2/patches/sljit-macos11-conditional.patch
deleted file mode 100644
index b92c8f9e09..0000000000
--- a/thirdparty/pcre2/patches/sljit-macos11-conditional.patch
+++ /dev/null
@@ -1,31 +0,0 @@
-From de8fc816bc6698ab97316ed954e133e7e5098262 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Carlo=20Marcelo=20Arenas=20Bel=C3=B3n?= <carenas@gmail.com>
-Date: Thu, 21 Apr 2022 21:01:12 -0700
-Subject: [PATCH] macos: somehow allow building with a target below 11.0
-
-While building for macOS older than 11 in Apple Silicon makes no
-sense, some build systems lack the flexibility to set a target per
-architecture while aiming to support multi architecture binaries.
-
-Allow an option in those cases by using the slower runtime checks
-if the toolchain allows it.
-
-Fixes: PCRE2Project/pcre2#109
----
- thirdparty/pcre2/src/sljit/sljitExecAllocator.c | 3 +++
- 1 file changed, 3 insertions(+)
-
-diff --git a/thirdparty/pcre2/src/sljit/sljitExecAllocator.c b/thirdparty/pcre2/src/sljit/sljitExecAllocator.c
-index 92d940ddc2..6359848cd5 100644
---- a/thirdparty/pcre2/src/sljit/sljitExecAllocator.c
-+++ b/thirdparty/pcre2/src/sljit/sljitExecAllocator.c
-@@ -152,6 +152,9 @@ static SLJIT_INLINE void apple_update_wx_flags(sljit_s32 enable_exec)
- {
- #if MAC_OS_X_VERSION_MIN_REQUIRED >= 110000
- pthread_jit_write_protect_np(enable_exec);
-+#elif defined(__clang__)
-+ if (__builtin_available(macOS 11.0, *))
-+ pthread_jit_write_protect_np(enable_exec);
- #else
- #error "Must target Big Sur or newer"
- #endif /* BigSur */
diff --git a/thirdparty/pcre2/src/sljit/sljitExecAllocator.c b/thirdparty/pcre2/src/sljit/sljitExecAllocator.c
deleted file mode 100644
index 6359848cd5..0000000000
--- a/thirdparty/pcre2/src/sljit/sljitExecAllocator.c
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Stack-less Just-In-Time compiler
- *
- * Copyright Zoltan Herczeg (hzmester@freemail.hu). All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification, are
- * permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this list of
- * conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice, this list
- * of conditions and the following disclaimer in the documentation and/or other materials
- * provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND CONTRIBUTORS ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
- * SHALL THE COPYRIGHT HOLDER(S) OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
- * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
- * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/*
- This file contains a simple executable memory allocator
-
- It is assumed, that executable code blocks are usually medium (or sometimes
- large) memory blocks, and the allocator is not too frequently called (less
- optimized than other allocators). Thus, using it as a generic allocator is
- not suggested.
-
- How does it work:
- Memory is allocated in continuous memory areas called chunks by alloc_chunk()
- Chunk format:
- [ block ][ block ] ... [ block ][ block terminator ]
-
- All blocks and the block terminator is started with block_header. The block
- header contains the size of the previous and the next block. These sizes
- can also contain special values.
- Block size:
- 0 - The block is a free_block, with a different size member.
- 1 - The block is a block terminator.
- n - The block is used at the moment, and the value contains its size.
- Previous block size:
- 0 - This is the first block of the memory chunk.
- n - The size of the previous block.
-
- Using these size values we can go forward or backward on the block chain.
- The unused blocks are stored in a chain list pointed by free_blocks. This
- list is useful if we need to find a suitable memory area when the allocator
- is called.
-
- When a block is freed, the new free block is connected to its adjacent free
- blocks if possible.
-
- [ free block ][ used block ][ free block ]
- and "used block" is freed, the three blocks are connected together:
- [ one big free block ]
-*/
-
-/* --------------------------------------------------------------------- */
-/* System (OS) functions */
-/* --------------------------------------------------------------------- */
-
-/* 64 KByte. */
-#define CHUNK_SIZE (sljit_uw)0x10000u
-
-/*
- alloc_chunk / free_chunk :
- * allocate executable system memory chunks
- * the size is always divisible by CHUNK_SIZE
- SLJIT_ALLOCATOR_LOCK / SLJIT_ALLOCATOR_UNLOCK :
- * provided as part of sljitUtils
- * only the allocator requires this lock, sljit is fully thread safe
- as it only uses local variables
-*/
-
-#ifdef _WIN32
-#define SLJIT_UPDATE_WX_FLAGS(from, to, enable_exec)
-
-static SLJIT_INLINE void* alloc_chunk(sljit_uw size)
-{
- return VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
-}
-
-static SLJIT_INLINE void free_chunk(void *chunk, sljit_uw size)
-{
- SLJIT_UNUSED_ARG(size);
- VirtualFree(chunk, 0, MEM_RELEASE);
-}
-
-#else /* POSIX */
-
-#if defined(__APPLE__) && defined(MAP_JIT)
-/*
- On macOS systems, returns MAP_JIT if it is defined _and_ we're running on a
- version where it's OK to have more than one JIT block or where MAP_JIT is
- required.
- On non-macOS systems, returns MAP_JIT if it is defined.
-*/
-#include <TargetConditionals.h>
-#if TARGET_OS_OSX
-#if defined SLJIT_CONFIG_X86 && SLJIT_CONFIG_X86
-#ifdef MAP_ANON
-#include <sys/utsname.h>
-#include <stdlib.h>
-
-#define SLJIT_MAP_JIT (get_map_jit_flag())
-
-static SLJIT_INLINE int get_map_jit_flag()
-{
- size_t page_size;
- void *ptr;
- struct utsname name;
- static int map_jit_flag = -1;
-
- if (map_jit_flag < 0) {
- map_jit_flag = 0;
- uname(&name);
-
- /* Kernel version for 10.14.0 (Mojave) or later */
- if (atoi(name.release) >= 18) {
- page_size = get_page_alignment() + 1;
- /* Only use MAP_JIT if a hardened runtime is used */
- ptr = mmap(NULL, page_size, PROT_WRITE | PROT_EXEC,
- MAP_PRIVATE | MAP_ANON, -1, 0);
-
- if (ptr != MAP_FAILED)
- munmap(ptr, page_size);
- else
- map_jit_flag = MAP_JIT;
- }
- }
- return map_jit_flag;
-}
-#endif /* MAP_ANON */
-#else /* !SLJIT_CONFIG_X86 */
-#if !(defined SLJIT_CONFIG_ARM && SLJIT_CONFIG_ARM)
-#error "Unsupported architecture"
-#endif /* SLJIT_CONFIG_ARM */
-#include <AvailabilityMacros.h>
-#include <pthread.h>
-
-#define SLJIT_MAP_JIT (MAP_JIT)
-#define SLJIT_UPDATE_WX_FLAGS(from, to, enable_exec) \
- apple_update_wx_flags(enable_exec)
-
-static SLJIT_INLINE void apple_update_wx_flags(sljit_s32 enable_exec)
-{
-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 110000
- pthread_jit_write_protect_np(enable_exec);
-#elif defined(__clang__)
- if (__builtin_available(macOS 11.0, *))
- pthread_jit_write_protect_np(enable_exec);
-#else
-#error "Must target Big Sur or newer"
-#endif /* BigSur */
-}
-#endif /* SLJIT_CONFIG_X86 */
-#else /* !TARGET_OS_OSX */
-#define SLJIT_MAP_JIT (MAP_JIT)
-#endif /* TARGET_OS_OSX */
-#endif /* __APPLE__ && MAP_JIT */
-#ifndef SLJIT_UPDATE_WX_FLAGS
-#define SLJIT_UPDATE_WX_FLAGS(from, to, enable_exec)
-#endif /* !SLJIT_UPDATE_WX_FLAGS */
-#ifndef SLJIT_MAP_JIT
-#define SLJIT_MAP_JIT (0)
-#endif /* !SLJIT_MAP_JIT */
-
-static SLJIT_INLINE void* alloc_chunk(sljit_uw size)
-{
- void *retval;
- int prot = PROT_READ | PROT_WRITE | PROT_EXEC;
- int flags = MAP_PRIVATE;
- int fd = -1;
-
-#ifdef PROT_MAX
- prot |= PROT_MAX(prot);
-#endif
-
-#ifdef MAP_ANON
- flags |= MAP_ANON | SLJIT_MAP_JIT;
-#else /* !MAP_ANON */
- if (SLJIT_UNLIKELY((dev_zero < 0) && open_dev_zero()))
- return NULL;
-
- fd = dev_zero;
-#endif /* MAP_ANON */
-
- retval = mmap(NULL, size, prot, flags, fd, 0);
- if (retval == MAP_FAILED)
- return NULL;
-
-#ifdef __FreeBSD__
- /* HardenedBSD's mmap lies, so check permissions again */
- if (mprotect(retval, size, PROT_READ | PROT_WRITE | PROT_EXEC) < 0) {
- munmap(retval, size);
- return NULL;
- }
-#endif /* FreeBSD */
-
- SLJIT_UPDATE_WX_FLAGS(retval, (uint8_t *)retval + size, 0);
-
- return retval;
-}
-
-static SLJIT_INLINE void free_chunk(void *chunk, sljit_uw size)
-{
- munmap(chunk, size);
-}
-
-#endif /* windows */
-
-/* --------------------------------------------------------------------- */
-/* Common functions */
-/* --------------------------------------------------------------------- */
-
-#define CHUNK_MASK (~(CHUNK_SIZE - 1))
-
-struct block_header {
- sljit_uw size;
- sljit_uw prev_size;
-};
-
-struct free_block {
- struct block_header header;
- struct free_block *next;
- struct free_block *prev;
- sljit_uw size;
-};
-
-#define AS_BLOCK_HEADER(base, offset) \
- ((struct block_header*)(((sljit_u8*)base) + offset))
-#define AS_FREE_BLOCK(base, offset) \
- ((struct free_block*)(((sljit_u8*)base) + offset))
-#define MEM_START(base) ((void*)(((sljit_u8*)base) + sizeof(struct block_header)))
-#define ALIGN_SIZE(size) (((size) + sizeof(struct block_header) + 7u) & ~(sljit_uw)7)
-
-static struct free_block* free_blocks;
-static sljit_uw allocated_size;
-static sljit_uw total_size;
-
-static SLJIT_INLINE void sljit_insert_free_block(struct free_block *free_block, sljit_uw size)
-{
- free_block->header.size = 0;
- free_block->size = size;
-
- free_block->next = free_blocks;
- free_block->prev = NULL;
- if (free_blocks)
- free_blocks->prev = free_block;
- free_blocks = free_block;
-}
-
-static SLJIT_INLINE void sljit_remove_free_block(struct free_block *free_block)
-{
- if (free_block->next)
- free_block->next->prev = free_block->prev;
-
- if (free_block->prev)
- free_block->prev->next = free_block->next;
- else {
- SLJIT_ASSERT(free_blocks == free_block);
- free_blocks = free_block->next;
- }
-}
-
-SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size)
-{
- struct block_header *header;
- struct block_header *next_header;
- struct free_block *free_block;
- sljit_uw chunk_size;
-
- SLJIT_ALLOCATOR_LOCK();
- if (size < (64 - sizeof(struct block_header)))
- size = (64 - sizeof(struct block_header));
- size = ALIGN_SIZE(size);
-
- free_block = free_blocks;
- while (free_block) {
- if (free_block->size >= size) {
- chunk_size = free_block->size;
- SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 0);
- if (chunk_size > size + 64) {
- /* We just cut a block from the end of the free block. */
- chunk_size -= size;
- free_block->size = chunk_size;
- header = AS_BLOCK_HEADER(free_block, chunk_size);
- header->prev_size = chunk_size;
- AS_BLOCK_HEADER(header, size)->prev_size = size;
- }
- else {
- sljit_remove_free_block(free_block);
- header = (struct block_header*)free_block;
- size = chunk_size;
- }
- allocated_size += size;
- header->size = size;
- SLJIT_ALLOCATOR_UNLOCK();
- return MEM_START(header);
- }
- free_block = free_block->next;
- }
-
- chunk_size = (size + sizeof(struct block_header) + CHUNK_SIZE - 1) & CHUNK_MASK;
- header = (struct block_header*)alloc_chunk(chunk_size);
- if (!header) {
- SLJIT_ALLOCATOR_UNLOCK();
- return NULL;
- }
-
- chunk_size -= sizeof(struct block_header);
- total_size += chunk_size;
-
- header->prev_size = 0;
- if (chunk_size > size + 64) {
- /* Cut the allocated space into a free and a used block. */
- allocated_size += size;
- header->size = size;
- chunk_size -= size;
-
- free_block = AS_FREE_BLOCK(header, size);
- free_block->header.prev_size = size;
- sljit_insert_free_block(free_block, chunk_size);
- next_header = AS_BLOCK_HEADER(free_block, chunk_size);
- }
- else {
- /* All space belongs to this allocation. */
- allocated_size += chunk_size;
- header->size = chunk_size;
- next_header = AS_BLOCK_HEADER(header, chunk_size);
- }
- next_header->size = 1;
- next_header->prev_size = chunk_size;
- SLJIT_ALLOCATOR_UNLOCK();
- return MEM_START(header);
-}
-
-SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr)
-{
- struct block_header *header;
- struct free_block* free_block;
-
- SLJIT_ALLOCATOR_LOCK();
- header = AS_BLOCK_HEADER(ptr, -(sljit_sw)sizeof(struct block_header));
- allocated_size -= header->size;
-
- /* Connecting free blocks together if possible. */
- SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 0);
-
- /* If header->prev_size == 0, free_block will equal to header.
- In this case, free_block->header.size will be > 0. */
- free_block = AS_FREE_BLOCK(header, -(sljit_sw)header->prev_size);
- if (SLJIT_UNLIKELY(!free_block->header.size)) {
- free_block->size += header->size;
- header = AS_BLOCK_HEADER(free_block, free_block->size);
- header->prev_size = free_block->size;
- }
- else {
- free_block = (struct free_block*)header;
- sljit_insert_free_block(free_block, header->size);
- }
-
- header = AS_BLOCK_HEADER(free_block, free_block->size);
- if (SLJIT_UNLIKELY(!header->size)) {
- free_block->size += ((struct free_block*)header)->size;
- sljit_remove_free_block((struct free_block*)header);
- header = AS_BLOCK_HEADER(free_block, free_block->size);
- header->prev_size = free_block->size;
- }
-
- /* The whole chunk is free. */
- if (SLJIT_UNLIKELY(!free_block->header.prev_size && header->size == 1)) {
- /* If this block is freed, we still have (allocated_size / 2) free space. */
- if (total_size - free_block->size > (allocated_size * 3 / 2)) {
- total_size -= free_block->size;
- sljit_remove_free_block(free_block);
- free_chunk(free_block, free_block->size + sizeof(struct block_header));
- }
- }
-
- SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 1);
- SLJIT_ALLOCATOR_UNLOCK();
-}
-
-SLJIT_API_FUNC_ATTRIBUTE void sljit_free_unused_memory_exec(void)
-{
- struct free_block* free_block;
- struct free_block* next_free_block;
-
- SLJIT_ALLOCATOR_LOCK();
- SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 0);
-
- free_block = free_blocks;
- while (free_block) {
- next_free_block = free_block->next;
- if (!free_block->header.prev_size &&
- AS_BLOCK_HEADER(free_block, free_block->size)->size == 1) {
- total_size -= free_block->size;
- sljit_remove_free_block(free_block);
- free_chunk(free_block, free_block->size + sizeof(struct block_header));
- }
- free_block = next_free_block;
- }
-
- SLJIT_ASSERT((total_size && free_blocks) || (!total_size && !free_blocks));
- SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 1);
- SLJIT_ALLOCATOR_UNLOCK();
-}
diff --git a/thirdparty/pcre2/src/sljit/sljitProtExecAllocator.c b/thirdparty/pcre2/src/sljit/sljitProtExecAllocator.c
deleted file mode 100644
index 915411fbed..0000000000
--- a/thirdparty/pcre2/src/sljit/sljitProtExecAllocator.c
+++ /dev/null
@@ -1,474 +0,0 @@
-/*
- * Stack-less Just-In-Time compiler
- *
- * Copyright Zoltan Herczeg (hzmester@freemail.hu). All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification, are
- * permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this list of
- * conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice, this list
- * of conditions and the following disclaimer in the documentation and/or other materials
- * provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND CONTRIBUTORS ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
- * SHALL THE COPYRIGHT HOLDER(S) OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
- * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
- * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/*
- This file contains a simple executable memory allocator
-
- It is assumed, that executable code blocks are usually medium (or sometimes
- large) memory blocks, and the allocator is not too frequently called (less
- optimized than other allocators). Thus, using it as a generic allocator is
- not suggested.
-
- How does it work:
- Memory is allocated in continuous memory areas called chunks by alloc_chunk()
- Chunk format:
- [ block ][ block ] ... [ block ][ block terminator ]
-
- All blocks and the block terminator is started with block_header. The block
- header contains the size of the previous and the next block. These sizes
- can also contain special values.
- Block size:
- 0 - The block is a free_block, with a different size member.
- 1 - The block is a block terminator.
- n - The block is used at the moment, and the value contains its size.
- Previous block size:
- 0 - This is the first block of the memory chunk.
- n - The size of the previous block.
-
- Using these size values we can go forward or backward on the block chain.
- The unused blocks are stored in a chain list pointed by free_blocks. This
- list is useful if we need to find a suitable memory area when the allocator
- is called.
-
- When a block is freed, the new free block is connected to its adjacent free
- blocks if possible.
-
- [ free block ][ used block ][ free block ]
- and "used block" is freed, the three blocks are connected together:
- [ one big free block ]
-*/
-
-/* --------------------------------------------------------------------- */
-/* System (OS) functions */
-/* --------------------------------------------------------------------- */
-
-/* 64 KByte. */
-#define CHUNK_SIZE (sljit_uw)0x10000
-
-struct chunk_header {
- void *executable;
-};
-
-/*
- alloc_chunk / free_chunk :
- * allocate executable system memory chunks
- * the size is always divisible by CHUNK_SIZE
- SLJIT_ALLOCATOR_LOCK / SLJIT_ALLOCATOR_UNLOCK :
- * provided as part of sljitUtils
- * only the allocator requires this lock, sljit is fully thread safe
- as it only uses local variables
-*/
-
-#ifndef __NetBSD__
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <string.h>
-
-#ifndef O_NOATIME
-#define O_NOATIME 0
-#endif
-
-/* this is a linux extension available since kernel 3.11 */
-#ifndef O_TMPFILE
-#define O_TMPFILE 020200000
-#endif
-
-#ifndef _GNU_SOURCE
-char *secure_getenv(const char *name);
-int mkostemp(char *template, int flags);
-#endif
-
-static SLJIT_INLINE int create_tempfile(void)
-{
- int fd;
- char tmp_name[256];
- size_t tmp_name_len = 0;
- char *dir;
- struct stat st;
-#if defined(SLJIT_SINGLE_THREADED) && SLJIT_SINGLE_THREADED
- mode_t mode;
-#endif
-
-#ifdef HAVE_MEMFD_CREATE
- /* this is a GNU extension, make sure to use -D_GNU_SOURCE */
- fd = memfd_create("sljit", MFD_CLOEXEC);
- if (fd != -1) {
- fchmod(fd, 0);
- return fd;
- }
-#endif
-
- dir = secure_getenv("TMPDIR");
-
- if (dir) {
- tmp_name_len = strlen(dir);
- if (tmp_name_len > 0 && tmp_name_len < sizeof(tmp_name)) {
- if ((stat(dir, &st) == 0) && S_ISDIR(st.st_mode))
- strcpy(tmp_name, dir);
- }
- }
-
-#ifdef P_tmpdir
- if (!tmp_name_len) {
- tmp_name_len = strlen(P_tmpdir);
- if (tmp_name_len > 0 && tmp_name_len < sizeof(tmp_name))
- strcpy(tmp_name, P_tmpdir);
- }
-#endif
- if (!tmp_name_len) {
- strcpy(tmp_name, "/tmp");
- tmp_name_len = 4;
- }
-
- SLJIT_ASSERT(tmp_name_len > 0 && tmp_name_len < sizeof(tmp_name));
-
- if (tmp_name[tmp_name_len - 1] == '/')
- tmp_name[--tmp_name_len] = '\0';
-
-#ifdef __linux__
- /*
- * the previous trimming might had left an empty string if TMPDIR="/"
- * so work around the problem below
- */
- fd = open(tmp_name_len ? tmp_name : "/",
- O_TMPFILE | O_EXCL | O_RDWR | O_NOATIME | O_CLOEXEC, 0);
- if (fd != -1)
- return fd;
-#endif
-
- if (tmp_name_len + 7 >= sizeof(tmp_name))
- return -1;
-
- strcpy(tmp_name + tmp_name_len, "/XXXXXX");
-#if defined(SLJIT_SINGLE_THREADED) && SLJIT_SINGLE_THREADED
- mode = umask(0777);
-#endif
- fd = mkostemp(tmp_name, O_CLOEXEC | O_NOATIME);
-#if defined(SLJIT_SINGLE_THREADED) && SLJIT_SINGLE_THREADED
- umask(mode);
-#else
- fchmod(fd, 0);
-#endif
-
- if (fd == -1)
- return -1;
-
- if (unlink(tmp_name)) {
- close(fd);
- return -1;
- }
-
- return fd;
-}
-
-static SLJIT_INLINE struct chunk_header* alloc_chunk(sljit_uw size)
-{
- struct chunk_header *retval;
- int fd;
-
- fd = create_tempfile();
- if (fd == -1)
- return NULL;
-
- if (ftruncate(fd, (off_t)size)) {
- close(fd);
- return NULL;
- }
-
- retval = (struct chunk_header *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
-
- if (retval == MAP_FAILED) {
- close(fd);
- return NULL;
- }
-
- retval->executable = mmap(NULL, size, PROT_READ | PROT_EXEC, MAP_SHARED, fd, 0);
-
- if (retval->executable == MAP_FAILED) {
- munmap((void *)retval, size);
- close(fd);
- return NULL;
- }
-
- close(fd);
- return retval;
-}
-#else
-/*
- * MAP_REMAPDUP is a NetBSD extension available sinde 8.0, make sure to
- * adjust your feature macros (ex: -D_NETBSD_SOURCE) as needed
- */
-static SLJIT_INLINE struct chunk_header* alloc_chunk(sljit_uw size)
-{
- struct chunk_header *retval;
-
- retval = (struct chunk_header *)mmap(NULL, size,
- PROT_READ | PROT_WRITE | PROT_MPROTECT(PROT_EXEC),
- MAP_ANON | MAP_SHARED, -1, 0);
-
- if (retval == MAP_FAILED)
- return NULL;
-
- retval->executable = mremap(retval, size, NULL, size, MAP_REMAPDUP);
- if (retval->executable == MAP_FAILED) {
- munmap((void *)retval, size);
- return NULL;
- }
-
- if (mprotect(retval->executable, size, PROT_READ | PROT_EXEC) == -1) {
- munmap(retval->executable, size);
- munmap((void *)retval, size);
- return NULL;
- }
-
- return retval;
-}
-#endif /* NetBSD */
-
-static SLJIT_INLINE void free_chunk(void *chunk, sljit_uw size)
-{
- struct chunk_header *header = ((struct chunk_header *)chunk) - 1;
-
- munmap(header->executable, size);
- munmap((void *)header, size);
-}
-
-/* --------------------------------------------------------------------- */
-/* Common functions */
-/* --------------------------------------------------------------------- */
-
-#define CHUNK_MASK (~(CHUNK_SIZE - 1))
-
-struct block_header {
- sljit_uw size;
- sljit_uw prev_size;
- sljit_sw executable_offset;
-};
-
-struct free_block {
- struct block_header header;
- struct free_block *next;
- struct free_block *prev;
- sljit_uw size;
-};
-
-#define AS_BLOCK_HEADER(base, offset) \
- ((struct block_header*)(((sljit_u8*)base) + offset))
-#define AS_FREE_BLOCK(base, offset) \
- ((struct free_block*)(((sljit_u8*)base) + offset))
-#define MEM_START(base) ((void*)((base) + 1))
-#define ALIGN_SIZE(size) (((size) + sizeof(struct block_header) + 7u) & ~(sljit_uw)7)
-
-static struct free_block* free_blocks;
-static sljit_uw allocated_size;
-static sljit_uw total_size;
-
-static SLJIT_INLINE void sljit_insert_free_block(struct free_block *free_block, sljit_uw size)
-{
- free_block->header.size = 0;
- free_block->size = size;
-
- free_block->next = free_blocks;
- free_block->prev = NULL;
- if (free_blocks)
- free_blocks->prev = free_block;
- free_blocks = free_block;
-}
-
-static SLJIT_INLINE void sljit_remove_free_block(struct free_block *free_block)
-{
- if (free_block->next)
- free_block->next->prev = free_block->prev;
-
- if (free_block->prev)
- free_block->prev->next = free_block->next;
- else {
- SLJIT_ASSERT(free_blocks == free_block);
- free_blocks = free_block->next;
- }
-}
-
-SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size)
-{
- struct chunk_header *chunk_header;
- struct block_header *header;
- struct block_header *next_header;
- struct free_block *free_block;
- sljit_uw chunk_size;
- sljit_sw executable_offset;
-
- SLJIT_ALLOCATOR_LOCK();
- if (size < (64 - sizeof(struct block_header)))
- size = (64 - sizeof(struct block_header));
- size = ALIGN_SIZE(size);
-
- free_block = free_blocks;
- while (free_block) {
- if (free_block->size >= size) {
- chunk_size = free_block->size;
- if (chunk_size > size + 64) {
- /* We just cut a block from the end of the free block. */
- chunk_size -= size;
- free_block->size = chunk_size;
- header = AS_BLOCK_HEADER(free_block, chunk_size);
- header->prev_size = chunk_size;
- header->executable_offset = free_block->header.executable_offset;
- AS_BLOCK_HEADER(header, size)->prev_size = size;
- }
- else {
- sljit_remove_free_block(free_block);
- header = (struct block_header*)free_block;
- size = chunk_size;
- }
- allocated_size += size;
- header->size = size;
- SLJIT_ALLOCATOR_UNLOCK();
- return MEM_START(header);
- }
- free_block = free_block->next;
- }
-
- chunk_size = sizeof(struct chunk_header) + sizeof(struct block_header);
- chunk_size = (chunk_size + size + CHUNK_SIZE - 1) & CHUNK_MASK;
-
- chunk_header = alloc_chunk(chunk_size);
- if (!chunk_header) {
- SLJIT_ALLOCATOR_UNLOCK();
- return NULL;
- }
-
- executable_offset = (sljit_sw)((sljit_u8*)chunk_header->executable - (sljit_u8*)chunk_header);
-
- chunk_size -= sizeof(struct chunk_header) + sizeof(struct block_header);
- total_size += chunk_size;
-
- header = (struct block_header *)(chunk_header + 1);
-
- header->prev_size = 0;
- header->executable_offset = executable_offset;
- if (chunk_size > size + 64) {
- /* Cut the allocated space into a free and a used block. */
- allocated_size += size;
- header->size = size;
- chunk_size -= size;
-
- free_block = AS_FREE_BLOCK(header, size);
- free_block->header.prev_size = size;
- free_block->header.executable_offset = executable_offset;
- sljit_insert_free_block(free_block, chunk_size);
- next_header = AS_BLOCK_HEADER(free_block, chunk_size);
- }
- else {
- /* All space belongs to this allocation. */
- allocated_size += chunk_size;
- header->size = chunk_size;
- next_header = AS_BLOCK_HEADER(header, chunk_size);
- }
- next_header->size = 1;
- next_header->prev_size = chunk_size;
- next_header->executable_offset = executable_offset;
- SLJIT_ALLOCATOR_UNLOCK();
- return MEM_START(header);
-}
-
-SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr)
-{
- struct block_header *header;
- struct free_block* free_block;
-
- SLJIT_ALLOCATOR_LOCK();
- header = AS_BLOCK_HEADER(ptr, -(sljit_sw)sizeof(struct block_header));
- header = AS_BLOCK_HEADER(header, -header->executable_offset);
- allocated_size -= header->size;
-
- /* Connecting free blocks together if possible. */
-
- /* If header->prev_size == 0, free_block will equal to header.
- In this case, free_block->header.size will be > 0. */
- free_block = AS_FREE_BLOCK(header, -(sljit_sw)header->prev_size);
- if (SLJIT_UNLIKELY(!free_block->header.size)) {
- free_block->size += header->size;
- header = AS_BLOCK_HEADER(free_block, free_block->size);
- header->prev_size = free_block->size;
- }
- else {
- free_block = (struct free_block*)header;
- sljit_insert_free_block(free_block, header->size);
- }
-
- header = AS_BLOCK_HEADER(free_block, free_block->size);
- if (SLJIT_UNLIKELY(!header->size)) {
- free_block->size += ((struct free_block*)header)->size;
- sljit_remove_free_block((struct free_block*)header);
- header = AS_BLOCK_HEADER(free_block, free_block->size);
- header->prev_size = free_block->size;
- }
-
- /* The whole chunk is free. */
- if (SLJIT_UNLIKELY(!free_block->header.prev_size && header->size == 1)) {
- /* If this block is freed, we still have (allocated_size / 2) free space. */
- if (total_size - free_block->size > (allocated_size * 3 / 2)) {
- total_size -= free_block->size;
- sljit_remove_free_block(free_block);
- free_chunk(free_block, free_block->size +
- sizeof(struct chunk_header) +
- sizeof(struct block_header));
- }
- }
-
- SLJIT_ALLOCATOR_UNLOCK();
-}
-
-SLJIT_API_FUNC_ATTRIBUTE void sljit_free_unused_memory_exec(void)
-{
- struct free_block* free_block;
- struct free_block* next_free_block;
-
- SLJIT_ALLOCATOR_LOCK();
-
- free_block = free_blocks;
- while (free_block) {
- next_free_block = free_block->next;
- if (!free_block->header.prev_size &&
- AS_BLOCK_HEADER(free_block, free_block->size)->size == 1) {
- total_size -= free_block->size;
- sljit_remove_free_block(free_block);
- free_chunk(free_block, free_block->size +
- sizeof(struct chunk_header) +
- sizeof(struct block_header));
- }
- free_block = next_free_block;
- }
-
- SLJIT_ASSERT((total_size && free_blocks) || (!total_size && !free_blocks));
- SLJIT_ALLOCATOR_UNLOCK();
-}
-
-SLJIT_API_FUNC_ATTRIBUTE sljit_sw sljit_exec_offset(void* ptr)
-{
- return ((struct block_header *)(ptr))[-1].executable_offset;
-}
diff --git a/thirdparty/pcre2/src/sljit/sljitWXExecAllocator.c b/thirdparty/pcre2/src/sljit/sljitWXExecAllocator.c
deleted file mode 100644
index 6893813155..0000000000
--- a/thirdparty/pcre2/src/sljit/sljitWXExecAllocator.c
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Stack-less Just-In-Time compiler
- *
- * Copyright Zoltan Herczeg (hzmester@freemail.hu). All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification, are
- * permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this list of
- * conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice, this list
- * of conditions and the following disclaimer in the documentation and/or other materials
- * provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND CONTRIBUTORS ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
- * SHALL THE COPYRIGHT HOLDER(S) OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
- * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
- * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/*
- This file contains a simple W^X executable memory allocator for POSIX
- like systems and Windows
-
- In *NIX, MAP_ANON is required (that is considered a feature) so make
- sure to set the right availability macros for your system or the code
- will fail to build.
-
- If your system doesn't support mapping of anonymous pages (ex: IRIX) it
- is also likely that it doesn't need this allocator and should be using
- the standard one instead.
-
- It allocates a separate map for each code block and may waste a lot of
- memory, because whatever was requested, will be rounded up to the page
- size (minimum 4KB, but could be even bigger).
-
- It changes the page permissions (RW <-> RX) as needed and therefore, if you
- will be updating the code after it has been generated, need to make sure to
- block any concurrent execution, or could result in a SIGBUS, that could
- even manifest itself at a different address than the one that was being
- modified.
-
- Only use if you are unable to use the regular allocator because of security
- restrictions and adding exceptions to your application or the system are
- not possible.
-*/
-
-#define SLJIT_UPDATE_WX_FLAGS(from, to, enable_exec) \
- sljit_update_wx_flags((from), (to), (enable_exec))
-
-#ifndef _WIN32
-#include <sys/types.h>
-#include <sys/mman.h>
-
-#ifdef __NetBSD__
-#define SLJIT_PROT_WX PROT_MPROTECT(PROT_EXEC)
-#define check_se_protected(ptr, size) (0)
-#else /* POSIX */
-#if !(defined SLJIT_SINGLE_THREADED && SLJIT_SINGLE_THREADED)
-#include <pthread.h>
-#define SLJIT_SE_LOCK() pthread_mutex_lock(&se_lock)
-#define SLJIT_SE_UNLOCK() pthread_mutex_unlock(&se_lock)
-#endif /* !SLJIT_SINGLE_THREADED */
-
-#define check_se_protected(ptr, size) generic_se_protected(ptr, size)
-
-static SLJIT_INLINE int generic_se_protected(void *ptr, sljit_uw size)
-{
- if (SLJIT_LIKELY(!mprotect(ptr, size, PROT_EXEC)))
- return mprotect(ptr, size, PROT_READ | PROT_WRITE);
-
- return -1;
-}
-#endif /* NetBSD */
-
-#ifndef SLJIT_SE_LOCK
-#define SLJIT_SE_LOCK()
-#endif
-#ifndef SLJIT_SE_UNLOCK
-#define SLJIT_SE_UNLOCK()
-#endif
-#ifndef SLJIT_PROT_WX
-#define SLJIT_PROT_WX 0
-#endif
-
-SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size)
-{
-#if !(defined SLJIT_SINGLE_THREADED && SLJIT_SINGLE_THREADED) \
- && !defined(__NetBSD__)
- static pthread_mutex_t se_lock = PTHREAD_MUTEX_INITIALIZER;
-#endif
- static int se_protected = !SLJIT_PROT_WX;
- int prot = PROT_READ | PROT_WRITE | SLJIT_PROT_WX;
- sljit_uw* ptr;
-
- if (SLJIT_UNLIKELY(se_protected < 0))
- return NULL;
-
-#ifdef PROT_MAX
- prot |= PROT_MAX(PROT_READ | PROT_WRITE | PROT_EXEC);
-#endif
-
- size += sizeof(sljit_uw);
- ptr = (sljit_uw*)mmap(NULL, size, prot, MAP_PRIVATE | MAP_ANON, -1, 0);
-
- if (ptr == MAP_FAILED)
- return NULL;
-
- if (SLJIT_UNLIKELY(se_protected > 0)) {
- SLJIT_SE_LOCK();
- se_protected = check_se_protected(ptr, size);
- SLJIT_SE_UNLOCK();
- if (SLJIT_UNLIKELY(se_protected < 0)) {
- munmap((void *)ptr, size);
- return NULL;
- }
- }
-
- *ptr++ = size;
- return ptr;
-}
-
-#undef SLJIT_PROT_WX
-#undef SLJIT_SE_UNLOCK
-#undef SLJIT_SE_LOCK
-
-SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr)
-{
- sljit_uw *start_ptr = ((sljit_uw*)ptr) - 1;
- munmap((void*)start_ptr, *start_ptr);
-}
-
-static void sljit_update_wx_flags(void *from, void *to, sljit_s32 enable_exec)
-{
- sljit_uw page_mask = (sljit_uw)get_page_alignment();
- sljit_uw start = (sljit_uw)from;
- sljit_uw end = (sljit_uw)to;
- int prot = PROT_READ | (enable_exec ? PROT_EXEC : PROT_WRITE);
-
- SLJIT_ASSERT(start < end);
-
- start &= ~page_mask;
- end = (end + page_mask) & ~page_mask;
-
- mprotect((void*)start, end - start, prot);
-}
-
-#else /* windows */
-
-SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size)
-{
- sljit_uw *ptr;
-
- size += sizeof(sljit_uw);
- ptr = (sljit_uw*)VirtualAlloc(NULL, size,
- MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
-
- if (!ptr)
- return NULL;
-
- *ptr++ = size;
-
- return ptr;
-}
-
-SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr)
-{
- sljit_uw start = (sljit_uw)ptr - sizeof(sljit_uw);
-#if defined(SLJIT_DEBUG) && SLJIT_DEBUG
- sljit_uw page_mask = (sljit_uw)get_page_alignment();
-
- SLJIT_ASSERT(!(start & page_mask));
-#endif
- VirtualFree((void*)start, 0, MEM_RELEASE);
-}
-
-static void sljit_update_wx_flags(void *from, void *to, sljit_s32 enable_exec)
-{
- DWORD oldprot;
- sljit_uw page_mask = (sljit_uw)get_page_alignment();
- sljit_uw start = (sljit_uw)from;
- sljit_uw end = (sljit_uw)to;
- DWORD prot = enable_exec ? PAGE_EXECUTE : PAGE_READWRITE;
-
- SLJIT_ASSERT(start < end);
-
- start &= ~page_mask;
- end = (end + page_mask) & ~page_mask;
-
- VirtualProtect((void*)start, end - start, prot, &oldprot);
-}
-
-#endif /* !windows */
-
-SLJIT_API_FUNC_ATTRIBUTE void sljit_free_unused_memory_exec(void)
-{
- /* This allocator does not keep unused memory for future allocations. */
-}