summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.yml2
-rw-r--r--.lgtm.yml7
-rw-r--r--core/object/undo_redo.cpp20
-rw-r--r--core/object/undo_redo.h4
-rw-r--r--doc/classes/@GlobalScope.xml2
-rw-r--r--doc/classes/EditorPlugin.xml10
-rw-r--r--doc/classes/GraphEdit.xml16
-rw-r--r--doc/classes/PackedByteArray.xml2
-rw-r--r--doc/classes/PopupMenu.xml41
-rw-r--r--doc/classes/ProjectSettings.xml6
-rw-r--r--doc/classes/RDPipelineDepthStencilState.xml20
-rw-r--r--doc/classes/RayCast2D.xml2
-rw-r--r--doc/classes/RayCast3D.xml2
-rw-r--r--doc/classes/ReflectionProbe.xml6
-rw-r--r--doc/classes/RenderingServer.xml10
-rw-r--r--doc/classes/Skeleton3D.xml6
-rw-r--r--doc/classes/TreeItem.xml19
-rw-r--r--doc/classes/UndoRedo.xml5
-rw-r--r--doc/classes/XRInterface.xml2
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.cpp56
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.h5
-rw-r--r--drivers/gles3/shaders/canvas.glsl3
-rw-r--r--drivers/gles3/storage/light_storage.cpp7
-rw-r--r--drivers/gles3/storage/light_storage.h2
-rw-r--r--drivers/gles3/storage/mesh_storage.cpp14
-rw-r--r--drivers/gles3/storage/mesh_storage.h7
-rw-r--r--editor/connections_dialog.cpp88
-rw-r--r--editor/editor_data.cpp6
-rw-r--r--editor/editor_data.h1
-rw-r--r--editor/editor_help_search.cpp3
-rw-r--r--editor/editor_node.cpp1
-rw-r--r--editor/editor_plugin.cpp5
-rw-r--r--editor/editor_plugin.h1
-rw-r--r--editor/export/export_template_manager.cpp7
-rw-r--r--editor/find_in_files.cpp3
-rw-r--r--editor/groups_editor.cpp3
-rw-r--r--editor/gui/scene_tree_editor.cpp5
-rw-r--r--editor/inspector_dock.cpp22
-rw-r--r--editor/inspector_dock.h2
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp2
-rw-r--r--editor/plugins/tiles/tiles_editor_plugin.cpp9
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp227
-rw-r--r--editor/plugins/visual_shader_editor_plugin.h14
-rw-r--r--editor/scene_tree_dock.cpp15
-rw-r--r--editor/scene_tree_dock.h1
-rw-r--r--editor/themes/editor_theme_manager.cpp2
-rw-r--r--main/main.cpp36
-rw-r--r--misc/dist/shell/_godot.zsh-completion3
-rw-r--r--misc/dist/shell/godot.bash-completion1
-rw-r--r--misc/dist/shell/godot.fish3
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp20
-rw-r--r--modules/gdscript/gdscript_analyzer.h1
-rw-r--r--modules/gdscript/gdscript_editor.cpp40
-rw-r--r--modules/gltf/doc_classes/GLTFPhysicsBody.xml19
-rw-r--r--modules/gltf/doc_classes/GLTFPhysicsShape.xml7
-rw-r--r--modules/gltf/extensions/physics/gltf_document_extension_physics.cpp316
-rw-r--r--modules/gltf/extensions/physics/gltf_physics_body.cpp300
-rw-r--r--modules/gltf/extensions/physics/gltf_physics_body.h34
-rw-r--r--modules/gltf/extensions/physics/gltf_physics_shape.cpp86
-rw-r--r--modules/gltf/extensions/physics/gltf_physics_shape.h5
-rw-r--r--modules/openxr/SCsub1
-rw-r--r--modules/openxr/extensions/openxr_local_floor_extension.cpp59
-rw-r--r--modules/openxr/extensions/openxr_local_floor_extension.h53
-rw-r--r--modules/openxr/openxr_api.cpp123
-rw-r--r--modules/openxr/openxr_api.h10
-rw-r--r--modules/openxr/openxr_interface.cpp37
-rw-r--r--modules/openxr/register_types.cpp2
-rw-r--r--platform/android/export/export_plugin.cpp2
-rw-r--r--platform/android/java/app/AndroidManifest.xml1
-rw-r--r--platform/android/java/app/assetPacks/installTime/build.gradle4
-rw-r--r--platform/android/java/app/build.gradle34
-rw-r--r--platform/android/java/app/config.gradle21
-rw-r--r--platform/android/java/app/settings.gradle4
-rw-r--r--platform/android/java/build.gradle19
-rw-r--r--platform/android/java/editor/build.gradle17
-rw-r--r--platform/android/java/editor/src/main/AndroidManifest.xml1
-rw-r--r--platform/android/java/gradle/wrapper/gradle-wrapper.properties3
-rw-r--r--platform/android/java/lib/AndroidManifest.xml1
-rw-r--r--platform/android/java/lib/build.gradle8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt20
-rw-r--r--platform/android/java/nativeSrcsConfigs/AndroidManifest.xml2
-rw-r--r--platform/android/java/nativeSrcsConfigs/build.gradle2
-rw-r--r--platform/android/java/settings.gradle5
-rw-r--r--scene/3d/reflection_probe.cpp13
-rw-r--r--scene/3d/reflection_probe.h4
-rw-r--r--scene/3d/skeleton_3d.cpp13
-rw-r--r--scene/animation/animation_player.cpp23
-rw-r--r--scene/animation/animation_player.h2
-rw-r--r--scene/gui/button.cpp15
-rw-r--r--scene/gui/popup_menu.cpp24
-rw-r--r--scene/gui/popup_menu.h1
-rw-r--r--scene/gui/tree.cpp55
-rw-r--r--scene/gui/tree.h7
-rw-r--r--scene/main/node.cpp5
-rw-r--r--scene/main/node.h1
-rw-r--r--scene/main/viewport.cpp10
-rw-r--r--scene/resources/mesh.cpp2
-rw-r--r--scene/resources/visual_shader.cpp14
-rw-r--r--scene/resources/visual_shader.h1
-rw-r--r--scene/theme/theme_db.cpp4
-rw-r--r--servers/debugger/servers_debugger.cpp10
-rw-r--r--servers/rendering/dummy/storage/light_storage.h2
-rw-r--r--servers/rendering/dummy/storage/material_storage.cpp136
-rw-r--r--servers/rendering/dummy/storage/material_storage.h28
-rw-r--r--servers/rendering/dummy/storage/mesh_storage.h9
-rw-r--r--servers/rendering/dummy/storage/texture_storage.h4
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp4
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp4
-rw-r--r--servers/rendering/renderer_rd/shaders/canvas.glsl3
-rw-r--r--servers/rendering/renderer_rd/storage_rd/light_storage.cpp19
-rw-r--r--servers/rendering/renderer_rd/storage_rd/light_storage.h3
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp14
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.h5
-rw-r--r--servers/rendering/renderer_rd/storage_rd/texture_storage.cpp8
-rw-r--r--servers/rendering/renderer_scene_cull.cpp2
-rw-r--r--servers/rendering/rendering_device.cpp34
-rw-r--r--servers/rendering/rendering_server_default.h4
-rw-r--r--servers/rendering/storage/light_storage.h2
-rw-r--r--servers/rendering/storage/mesh_storage.h4
-rw-r--r--servers/rendering_server.cpp1
-rw-r--r--servers/rendering_server.h4
-rw-r--r--servers/xr/xr_interface.h2
-rw-r--r--servers/xr_server.cpp6
123 files changed, 2001 insertions, 494 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 9169bdd456..d1fafec50d 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -9,7 +9,7 @@ body:
- Write a descriptive issue title above.
- The golden rule is to **always open *one* issue for *one* bug**. If you notice several bugs and want to report them, make sure to create one new issue for each of them.
- Search [open](https://github.com/godotengine/godot/issues) and [closed](https://github.com/godotengine/godot/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported. If you don't find a relevant match or if you're unsure, don't hesitate to **open a new issue**. The bugsquad will handle it from there if it's a duplicate.
- - Verify that you are using a [supported Godot version](https://docs.godotengine.org/en/stable/about/release_policy.html). Please always check if your issue is reproducible in the latest version – it may already have been fixed!
+ - Verify that you are using a [supported Godot version](https://docs.godotengine.org/en/latest/about/release_policy.html). Please always check if your issue is reproducible in the latest version – it may already have been fixed!
- If you use a custom build, please test if your issue is reproducible in official builds too. Likewise if you use any C++ modules, GDExtensions, or editor plugins, you should check if the bug is reproducible in a project without these.
- type: textarea
diff --git a/.lgtm.yml b/.lgtm.yml
deleted file mode 100644
index e4841eaff4..0000000000
--- a/.lgtm.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-extraction:
- cpp:
- after_prepare:
- - pip3 install scons
- - PATH="/opt/work/.local/bin:$PATH"
- index:
- build_command: scons -j2
diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp
index 3c1d4ef95e..569e6ae19f 100644
--- a/core/object/undo_redo.cpp
+++ b/core/object/undo_redo.cpp
@@ -316,6 +316,14 @@ void UndoRedo::commit_action(bool p_execute) {
_redo(p_execute); // perform action
committing--;
+ if (max_steps > 0) {
+ // Clear early steps.
+
+ while (actions.size() > max_steps) {
+ _pop_history_tail();
+ }
+ }
+
if (add_message && callback && actions.size() > 0) {
callback(callback_ud, actions[actions.size() - 1].name);
}
@@ -473,6 +481,14 @@ uint64_t UndoRedo::get_version() const {
return version;
}
+void UndoRedo::set_max_steps(int p_max_steps) {
+ max_steps = p_max_steps;
+}
+
+int UndoRedo::get_max_steps() const {
+ return max_steps;
+}
+
void UndoRedo::set_commit_notify_callback(CommitNotifyCallback p_callback, void *p_ud) {
callback = p_callback;
callback_ud = p_ud;
@@ -517,9 +533,13 @@ void UndoRedo::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_undo"), &UndoRedo::has_undo);
ClassDB::bind_method(D_METHOD("has_redo"), &UndoRedo::has_redo);
ClassDB::bind_method(D_METHOD("get_version"), &UndoRedo::get_version);
+ ClassDB::bind_method(D_METHOD("set_max_steps", "max_steps"), &UndoRedo::set_max_steps);
+ ClassDB::bind_method(D_METHOD("get_max_steps"), &UndoRedo::get_max_steps);
ClassDB::bind_method(D_METHOD("redo"), &UndoRedo::redo);
ClassDB::bind_method(D_METHOD("undo"), &UndoRedo::undo);
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "max_steps", PROPERTY_HINT_RANGE, "0,50,1,or_greater"), "set_max_steps", "get_max_steps");
+
ADD_SIGNAL(MethodInfo("version_changed"));
BIND_ENUM_CONSTANT(MERGE_DISABLE);
diff --git a/core/object/undo_redo.h b/core/object/undo_redo.h
index b3a3322e4b..62aebff877 100644
--- a/core/object/undo_redo.h
+++ b/core/object/undo_redo.h
@@ -80,6 +80,7 @@ private:
int current_action = -1;
bool force_keep_in_merge_ends = false;
int action_level = 0;
+ int max_steps = 0;
MergeMode merge_mode = MERGE_DISABLE;
bool merging = false;
uint64_t version = 1;
@@ -135,6 +136,9 @@ public:
uint64_t get_version() const;
+ void set_max_steps(int p_max_steps);
+ int get_max_steps() const;
+
void set_commit_notify_callback(CommitNotifyCallback p_callback, void *p_ud);
void set_method_notify_callback(MethodNotifyCallback p_method_callback, void *p_ud);
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index d72fc61394..b74ca4b035 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -1429,6 +1429,7 @@
<description>
Encodes a [Variant] value to a byte array, without encoding objects. Deserialization can be done with [method bytes_to_var].
[b]Note:[/b] If you need object serialization, see [method var_to_bytes_with_objects].
+ [b]Note:[/b] Encoding [Callable] is not supported and will result in an empty value, regardless of the data.
</description>
</method>
<method name="var_to_bytes_with_objects">
@@ -1436,6 +1437,7 @@
<param index="0" name="variable" type="Variant" />
<description>
Encodes a [Variant] value to a byte array. Encoding objects is allowed (and can potentially include executable code). Deserialization can be done with [method bytes_to_var_with_objects].
+ [b]Note:[/b] Encoding [Callable] is not supported and will result in an empty value, regardless of the data.
</description>
</method>
<method name="var_to_str">
diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml
index 148a6541a2..407d64e9a2 100644
--- a/doc/classes/EditorPlugin.xml
+++ b/doc/classes/EditorPlugin.xml
@@ -759,7 +759,7 @@
<signal name="resource_saved">
<param index="0" name="resource" type="Resource" />
<description>
- Emitted when the given [param resource] was saved on disc.
+ Emitted when the given [param resource] was saved on disc. See also [signal scene_saved].
</description>
</signal>
<signal name="scene_changed">
@@ -771,7 +771,13 @@
<signal name="scene_closed">
<param index="0" name="filepath" type="String" />
<description>
- Emitted when user closes a scene. The argument is file path to a closed scene.
+ Emitted when user closes a scene. The argument is a file path to the closed scene.
+ </description>
+ </signal>
+ <signal name="scene_saved">
+ <param index="0" name="filepath" type="String" />
+ <description>
+ Emitted when a scene was saved on disc. The argument is a file path to the saved scene. See also [signal resource_saved].
</description>
</signal>
</signals>
diff --git a/doc/classes/GraphEdit.xml b/doc/classes/GraphEdit.xml
index e5952d9f71..709d26668c 100644
--- a/doc/classes/GraphEdit.xml
+++ b/doc/classes/GraphEdit.xml
@@ -323,7 +323,7 @@
<signals>
<signal name="begin_node_move">
<description>
- Emitted at the beginning of a GraphNode movement.
+ Emitted at the beginning of a [GraphElement]'s movement.
</description>
</signal>
<signal name="connection_drag_ended">
@@ -366,13 +366,14 @@
</signal>
<signal name="copy_nodes_request">
<description>
- Emitted when the user presses [kbd]Ctrl + C[/kbd].
+ Emitted when this [GraphEdit] captures a [code]ui_copy[/code] action ([kbd]Ctrl + C[/kbd] by default). In general, this signal indicates that the selected [GraphElement]s should be copied.
</description>
</signal>
<signal name="delete_nodes_request">
<param index="0" name="nodes" type="StringName[]" />
<description>
- Emitted when attempting to remove a GraphNode from the GraphEdit. Provides a list of node names to be removed (all selected nodes, excluding nodes without closing button).
+ Emitted when this [GraphEdit] captures a [code]ui_graph_delete[/code] action ([kbd]Delete[/kbd] by default).
+ [param nodes] is an array of node names that should be removed. These usually include all selected nodes.
</description>
</signal>
<signal name="disconnection_request">
@@ -386,28 +387,29 @@
</signal>
<signal name="duplicate_nodes_request">
<description>
- Emitted when a GraphNode is attempted to be duplicated in the GraphEdit.
+ Emitted when this [GraphEdit] captures a [code]ui_graph_duplicate[/code] action ([kbd]Ctrl + D[/kbd] by default). In general, this signal indicates that the selected [GraphElement]s should be duplicated.
</description>
</signal>
<signal name="end_node_move">
<description>
- Emitted at the end of a GraphNode movement.
+ Emitted at the end of a [GraphElement]'s movement.
</description>
</signal>
<signal name="node_deselected">
<param index="0" name="node" type="Node" />
<description>
+ Emitted when the given [GraphElement] node is deselected.
</description>
</signal>
<signal name="node_selected">
<param index="0" name="node" type="Node" />
<description>
- Emitted when a GraphNode is selected.
+ Emitted when the given [GraphElement] node is selected.
</description>
</signal>
<signal name="paste_nodes_request">
<description>
- Emitted when the user presses [kbd]Ctrl + V[/kbd].
+ Emitted when this [GraphEdit] captures a [code]ui_paste[/code] action ([kbd]Ctrl + V[/kbd] by default). In general, this signal indicates that previously copied [GraphElement]s should be pasted.
</description>
</signal>
<signal name="popup_request">
diff --git a/doc/classes/PackedByteArray.xml b/doc/classes/PackedByteArray.xml
index a934d32a6d..c5f522ba10 100644
--- a/doc/classes/PackedByteArray.xml
+++ b/doc/classes/PackedByteArray.xml
@@ -174,6 +174,7 @@
<param index="1" name="compression_mode" type="int" default="0" />
<description>
Returns a new [PackedByteArray] with the data decompressed. Set [param buffer_size] to the size of the uncompressed data. Set the compression mode using one of [enum FileAccess.CompressionMode]'s constants.
+ [b]Note:[/b] Decompression is not guaranteed to work with data not compressed by Godot, for example if data compressed with the deflate compression mode lacks a checksum or header.
</description>
</method>
<method name="decompress_dynamic" qualifiers="const">
@@ -184,6 +185,7 @@
Returns a new [PackedByteArray] with the data decompressed. Set the compression mode using one of [enum FileAccess.CompressionMode]'s constants. [b]This method only accepts brotli, gzip, and deflate compression modes.[/b]
This method is potentially slower than [method decompress], as it may have to re-allocate its output buffer multiple times while decompressing, whereas [method decompress] knows it's output buffer size from the beginning.
GZIP has a maximal compression ratio of 1032:1, meaning it's very possible for a small compressed payload to decompress to a potentially very large output. To guard against this, you may provide a maximum size this function is allowed to allocate in bytes via [param max_output_size]. Passing -1 will allow for unbounded output. If any positive value is passed, and the decompression exceeds that amount in bytes, then an error will be returned.
+ [b]Note:[/b] Decompression is not guaranteed to work with data not compressed by Godot, for example if data compressed with the deflate compression mode lacks a checksum or header.
</description>
</method>
<method name="duplicate">
diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml
index 40d5a9f4a2..8f6507eabf 100644
--- a/doc/classes/PopupMenu.xml
+++ b/doc/classes/PopupMenu.xml
@@ -133,8 +133,25 @@
<param index="4" name="accel" type="int" enum="Key" default="0" />
<description>
Adds a new multistate item with text [param label].
- Contrarily to normal binary items, multistate items can have more than two states, as defined by [param max_states]. Each press or activate of the item will increase the state by one. The default value is defined by [param default_state].
+ Contrarily to normal binary items, multistate items can have more than two states, as defined by [param max_states]. The default value is defined by [param default_state].
An [param id] can optionally be provided, as well as an accelerator ([param accel]). If no [param id] is provided, one will be created from the index. If no [param accel] is provided, then the default value of 0 (corresponding to [constant @GlobalScope.KEY_NONE]) will be assigned to the item (which means it won't have any accelerator). See [method get_item_accelerator] for more info on accelerators.
+ [b]Note:[/b] Multistate items don't update their state automatically and must be done manually. See [method toggle_item_multistate], [method set_item_multistate] and [method get_item_multistate] for more info on how to control it.
+ Example usage:
+ [codeblock]
+ func _ready():
+ add_multistate_item("Item", 3, 0)
+
+ index_pressed.connect(func(index: int):
+ toggle_item_multistate(index)
+ match get_item_multistate(index):
+ 0:
+ print("First state")
+ 1:
+ print("Second state")
+ 2:
+ print("Third state")
+ )
+ [/codeblock]
</description>
</method>
<method name="add_radio_check_item">
@@ -266,6 +283,20 @@
Returns the metadata of the specified item, which might be of any type. You can set it with [method set_item_metadata], which provides a simple way of assigning context data to items.
</description>
</method>
+ <method name="get_item_multistate" qualifiers="const">
+ <return type="int" />
+ <param index="0" name="index" type="int" />
+ <description>
+ Returns the state of the item at the given [param index].
+ </description>
+ </method>
+ <method name="get_item_multistate_max" qualifiers="const">
+ <return type="int" />
+ <param index="0" name="index" type="int" />
+ <description>
+ Returns the max states of the item at the given [param index].
+ </description>
+ </method>
<method name="get_item_shortcut" qualifiers="const">
<return type="Shortcut" />
<param index="0" name="index" type="int" />
@@ -489,6 +520,14 @@
Sets the state of a multistate item. See [method add_multistate_item] for details.
</description>
</method>
+ <method name="set_item_multistate_max">
+ <return type="void" />
+ <param index="0" name="index" type="int" />
+ <param index="1" name="max_states" type="int" />
+ <description>
+ Sets the max states of a multistate item. See [method add_multistate_item] for details.
+ </description>
+ </method>
<method name="set_item_shortcut">
<return type="void" />
<param index="0" name="index" type="int" />
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index e0d41ab90a..cbd797273c 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -433,16 +433,18 @@
If canvas item redraw debugging is active, this will be the time the flash will last each time they redraw.
</member>
<member name="debug/file_logging/enable_file_logging" type="bool" setter="" getter="" default="false">
- If [code]true[/code], logs all output to files.
+ If [code]true[/code], logs all output and error messages to files. See also [member debug/file_logging/log_path], [member debug/file_logging/max_log_files], and [member application/run/flush_stdout_on_print].
</member>
<member name="debug/file_logging/enable_file_logging.pc" type="bool" setter="" getter="" default="true">
Desktop override for [member debug/file_logging/enable_file_logging], as log files are not readily accessible on mobile/Web platforms.
</member>
<member name="debug/file_logging/log_path" type="String" setter="" getter="" default="&quot;user://logs/godot.log&quot;">
Path at which to store log files for the project. Using a path under [code]user://[/code] is recommended.
+ This can be specified manually on the command line using the [code]--log-file &lt;file&gt;[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url]. If this command line argument is specified, log rotation is automatically disabled (see [member debug/file_logging/max_log_files]).
</member>
<member name="debug/file_logging/max_log_files" type="int" setter="" getter="" default="5">
- Specifies the maximum number of log files allowed (used for rotation).
+ Specifies the maximum number of log files allowed (used for rotation). Set to [code]1[/code] to disable log file rotation.
+ If the [code]--log-file &lt;file&gt;[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url] is used, log rotation is always disabled.
</member>
<member name="debug/gdscript/warnings/assert_always_false" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an [code]assert[/code] call always evaluates to false.
diff --git a/doc/classes/RDPipelineDepthStencilState.xml b/doc/classes/RDPipelineDepthStencilState.xml
index fa2a4e17c9..b8245f97af 100644
--- a/doc/classes/RDPipelineDepthStencilState.xml
+++ b/doc/classes/RDPipelineDepthStencilState.xml
@@ -10,47 +10,67 @@
</tutorials>
<members>
<member name="back_op_compare" type="int" setter="set_back_op_compare" getter="get_back_op_compare" enum="RenderingDevice.CompareOperator" default="7">
+ The method used for comparing the previous back stencil value and [member back_op_reference].
</member>
<member name="back_op_compare_mask" type="int" setter="set_back_op_compare_mask" getter="get_back_op_compare_mask" default="0">
+ Selects which bits from the back stencil value will be compared.
</member>
<member name="back_op_depth_fail" type="int" setter="set_back_op_depth_fail" getter="get_back_op_depth_fail" enum="RenderingDevice.StencilOperation" default="1">
+ The operation to perform on the stencil buffer for back pixels that pass the stencil test but fail the depth test.
</member>
<member name="back_op_fail" type="int" setter="set_back_op_fail" getter="get_back_op_fail" enum="RenderingDevice.StencilOperation" default="1">
+ The operation to perform on the stencil buffer for back pixels that fail the stencil test
</member>
<member name="back_op_pass" type="int" setter="set_back_op_pass" getter="get_back_op_pass" enum="RenderingDevice.StencilOperation" default="1">
+ The operation to perform on the stencil buffer for back pixels that pass the stencil test.
</member>
<member name="back_op_reference" type="int" setter="set_back_op_reference" getter="get_back_op_reference" default="0">
+ The value the previous back stencil value will be compared to.
</member>
<member name="back_op_write_mask" type="int" setter="set_back_op_write_mask" getter="get_back_op_write_mask" default="0">
+ Selects which bits from the back stencil value will be changed.
</member>
<member name="depth_compare_operator" type="int" setter="set_depth_compare_operator" getter="get_depth_compare_operator" enum="RenderingDevice.CompareOperator" default="7">
+ The method used for comparing the previous and current depth values.
</member>
<member name="depth_range_max" type="float" setter="set_depth_range_max" getter="get_depth_range_max" default="0.0">
+ The maximum depth that returns true for [member enable_depth_range].
</member>
<member name="depth_range_min" type="float" setter="set_depth_range_min" getter="get_depth_range_min" default="0.0">
+ The minimum depth that returns true for [member enable_depth_range].
</member>
<member name="enable_depth_range" type="bool" setter="set_enable_depth_range" getter="get_enable_depth_range" default="false">
+ If [code]true[/code], each depth value will be tested to see if it is between [member depth_range_min] and [member depth_range_max]. If it is outside of these values, it is discarded.
</member>
<member name="enable_depth_test" type="bool" setter="set_enable_depth_test" getter="get_enable_depth_test" default="false">
If [code]true[/code], enables depth testing which allows objects to be automatically occluded by other objects based on their depth. This also allows objects to be partially occluded by other objects. If [code]false[/code], objects will appear in the order they were drawn (like in Godot's 2D renderer).
</member>
<member name="enable_depth_write" type="bool" setter="set_enable_depth_write" getter="get_enable_depth_write" default="false">
+ If [code]true[/code], writes to the depth buffer whenever the depth test returns true. Only works when enable_depth_test is also true.
</member>
<member name="enable_stencil" type="bool" setter="set_enable_stencil" getter="get_enable_stencil" default="false">
+ If [code]true[/code], enables stencil testing. There are separate stencil buffers for front-facing triangles and back-facing triangles. See properties that begin with "front_op" and properties with "back_op" for each.
</member>
<member name="front_op_compare" type="int" setter="set_front_op_compare" getter="get_front_op_compare" enum="RenderingDevice.CompareOperator" default="7">
+ The method used for comparing the previous front stencil value and [member front_op_reference].
</member>
<member name="front_op_compare_mask" type="int" setter="set_front_op_compare_mask" getter="get_front_op_compare_mask" default="0">
+ Selects which bits from the front stencil value will be compared.
</member>
<member name="front_op_depth_fail" type="int" setter="set_front_op_depth_fail" getter="get_front_op_depth_fail" enum="RenderingDevice.StencilOperation" default="1">
+ The operation to perform on the stencil buffer for front pixels that pass the stencil test but fail the depth test.
</member>
<member name="front_op_fail" type="int" setter="set_front_op_fail" getter="get_front_op_fail" enum="RenderingDevice.StencilOperation" default="1">
+ The operation to perform on the stencil buffer for front pixels that fail the stencil test.
</member>
<member name="front_op_pass" type="int" setter="set_front_op_pass" getter="get_front_op_pass" enum="RenderingDevice.StencilOperation" default="1">
+ The operation to perform on the stencil buffer for front pixels that pass the stencil test.
</member>
<member name="front_op_reference" type="int" setter="set_front_op_reference" getter="get_front_op_reference" default="0">
+ The value the previous front stencil value will be compared to.
</member>
<member name="front_op_write_mask" type="int" setter="set_front_op_write_mask" getter="get_front_op_write_mask" default="0">
+ Selects which bits from the front stencil value will be changed.
</member>
</members>
</class>
diff --git a/doc/classes/RayCast2D.xml b/doc/classes/RayCast2D.xml
index 6144fd8f0b..22e3ae07ba 100644
--- a/doc/classes/RayCast2D.xml
+++ b/doc/classes/RayCast2D.xml
@@ -74,7 +74,7 @@
<method name="get_collision_point" qualifiers="const">
<return type="Vector2" />
<description>
- Returns the collision point at which the ray intersects the closest object.
+ 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.
</description>
</method>
diff --git a/doc/classes/RayCast3D.xml b/doc/classes/RayCast3D.xml
index 7157ec9b5f..406fed107f 100644
--- a/doc/classes/RayCast3D.xml
+++ b/doc/classes/RayCast3D.xml
@@ -81,7 +81,7 @@
<method name="get_collision_point" qualifiers="const">
<return type="Vector3" />
<description>
- Returns the collision point at which the ray intersects the closest object.
+ 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.
</description>
</method>
diff --git a/doc/classes/ReflectionProbe.xml b/doc/classes/ReflectionProbe.xml
index f53ddfc3ac..c7d067b94d 100644
--- a/doc/classes/ReflectionProbe.xml
+++ b/doc/classes/ReflectionProbe.xml
@@ -28,7 +28,8 @@
[b]Note:[/b] To better fit rectangle-shaped rooms that are not aligned to the grid, you can rotate the [ReflectionProbe] node.
</member>
<member name="cull_mask" type="int" setter="set_cull_mask" getter="get_cull_mask" default="1048575">
- Sets the cull mask which determines what objects are drawn by this probe. Every [VisualInstance3D] with a layer included in this cull mask will be rendered by the probe. To improve performance, it is best to only include large objects which are likely to take up a lot of space in the reflection.
+ Sets the cull mask which determines what objects are drawn by this probe. Every [VisualInstance3D] with a layer included in this cull mask will be rendered by the probe. It is best to only include large objects which are likely to take up a lot of space in the reflection in order to save on rendering cost.
+ This can also be used to prevent an object from reflecting upon itself (for instance, a [ReflectionProbe] centered on a vehicle).
</member>
<member name="enable_shadows" type="bool" setter="set_enable_shadows" getter="are_shadows_enabled" default="false">
If [code]true[/code], computes shadows in the reflection probe. This makes the reflection probe slower to render; you may want to disable this if using the [constant UPDATE_ALWAYS] [member update_mode].
@@ -50,6 +51,9 @@
<member name="origin_offset" type="Vector3" setter="set_origin_offset" getter="get_origin_offset" default="Vector3(0, 0, 0)">
Sets the origin offset to be used when this [ReflectionProbe] is in [member box_projection] mode. This can be set to a non-zero value to ensure a reflection fits a rectangle-shaped room, while reducing the number of objects that "get in the way" of the reflection.
</member>
+ <member name="reflection_mask" type="int" setter="set_reflection_mask" getter="get_reflection_mask" default="1048575">
+ Sets the reflection mask which determines what objects have reflections applied from this probe. Every [VisualInstance3D] with a layer included in this reflection mask will have reflections applied from this probe. See also [member cull_mask], which can be used to exclude objects from appearing in the reflection while still making them affected by the [ReflectionProbe].
+ </member>
<member name="size" type="Vector3" setter="set_size" getter="get_size" default="Vector3(20, 20, 20)">
The size of the reflection probe. The larger the size, the more space covered by the probe, which will lower the perceived resolution. It is best to keep the size only as large as you need it.
[b]Note:[/b] To better fit areas that are not aligned to the grid, you can rotate the [ReflectionProbe] node.
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index 42a164bbeb..4ab511b5a9 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -2894,7 +2894,7 @@
<param index="0" name="probe" type="RID" />
<param index="1" name="layers" type="int" />
<description>
- Sets the render cull mask for this reflection probe. Only instances with a matching cull mask will be rendered by this probe. Equivalent to [member ReflectionProbe.cull_mask].
+ Sets the render cull mask for this reflection probe. Only instances with a matching layer will be reflected by this probe. Equivalent to [member ReflectionProbe.cull_mask].
</description>
</method>
<method name="reflection_probe_set_enable_box_projection">
@@ -2945,6 +2945,14 @@
Sets the origin offset to be used when this reflection probe is in box project mode. Equivalent to [member ReflectionProbe.origin_offset].
</description>
</method>
+ <method name="reflection_probe_set_reflection_mask">
+ <return type="void" />
+ <param index="0" name="probe" type="RID" />
+ <param index="1" name="layers" type="int" />
+ <description>
+ Sets the render reflection mask for this reflection probe. Only instances with a matching layer will have reflections applied from this probe. Equivalent to [member ReflectionProbe.reflection_mask].
+ </description>
+ </method>
<method name="reflection_probe_set_resolution">
<return type="void" />
<param index="0" name="probe" type="RID" />
diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml
index 6cb1c948ea..2a629abc0d 100644
--- a/doc/classes/Skeleton3D.xml
+++ b/doc/classes/Skeleton3D.xml
@@ -310,12 +310,14 @@
[b]Note:[/b] Unless this value is [code]1.0[/code], the key value in animation will not match the actual position value.
</member>
<member name="show_rest_only" type="bool" setter="set_show_rest_only" getter="is_show_rest_only" default="false">
+ If [code]true[/code], forces the bones in their default rest pose, regardless of their values. In the editor, this also prevents the bones from being edited.
</member>
</members>
<signals>
<signal name="bone_enabled_changed">
<param index="0" name="bone_idx" type="int" />
<description>
+ Emitted when the bone at [param bone_idx] is toggled with [method set_bone_enabled]. Use [method is_bone_enabled] to check the new value.
</description>
</signal>
<signal name="bone_pose_changed">
@@ -326,15 +328,19 @@
</signal>
<signal name="pose_updated">
<description>
+ Emitted when the pose is updated, after [constant NOTIFICATION_UPDATE_SKELETON] is received.
</description>
</signal>
<signal name="show_rest_only_changed">
<description>
+ Emitted when the value of [member show_rest_only] changes.
</description>
</signal>
</signals>
<constants>
<constant name="NOTIFICATION_UPDATE_SKELETON" value="50">
+ Notification received when this skeleton's pose needs to be updated.
+ This notification is received [i]before[/i] the related [signal pose_updated] signal.
</constant>
</constants>
</class>
diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml
index 597a363514..3b8a4c8872 100644
--- a/doc/classes/TreeItem.xml
+++ b/doc/classes/TreeItem.xml
@@ -160,6 +160,13 @@
Returns the custom color of column [param column].
</description>
</method>
+ <method name="get_custom_draw_callback" qualifiers="const">
+ <return type="Callable" />
+ <param index="0" name="column" type="int" />
+ <description>
+ Returns the custom callback of column [param column].
+ </description>
+ </method>
<method name="get_custom_font" qualifiers="const">
<return type="Font" />
<param index="0" name="column" type="int" />
@@ -553,7 +560,7 @@
Sets the given column's custom color.
</description>
</method>
- <method name="set_custom_draw">
+ <method name="set_custom_draw" is_deprecated="true">
<return type="void" />
<param index="0" name="column" type="int" />
<param index="1" name="object" type="Object" />
@@ -561,6 +568,16 @@
<description>
Sets the given column's custom draw callback to [param callback] method on [param object].
The [param callback] should accept two arguments: the [TreeItem] that is drawn and its position and size as a [Rect2].
+ [i]Deprecated.[/i] Use [method TreeItem.set_custom_draw_callback] instead.
+ </description>
+ </method>
+ <method name="set_custom_draw_callback">
+ <return type="void" />
+ <param index="0" name="column" type="int" />
+ <param index="1" name="callback" type="Callable" />
+ <description>
+ Sets the given column's custom draw callback. Use an empty [Callable] ([code skip-lint]Callable()[/code]) to clear the custom callback.
+ The [param callback] should accept two arguments: the [TreeItem] that is drawn and its position and size as a [Rect2].
</description>
</method>
<method name="set_custom_font">
diff --git a/doc/classes/UndoRedo.xml b/doc/classes/UndoRedo.xml
index 50414d2580..3d36eafb08 100644
--- a/doc/classes/UndoRedo.xml
+++ b/doc/classes/UndoRedo.xml
@@ -255,6 +255,11 @@
</description>
</method>
</methods>
+ <members>
+ <member name="max_steps" type="int" setter="set_max_steps" getter="get_max_steps" default="0">
+ The maximum number of steps that can be stored in the undo/redo history. If the number of stored steps exceeds this limit, older steps are removed from history and can no longer be reached by calling [method undo]. A value of [code]0[/code] or lower means no limit.
+ </member>
+ </members>
<signals>
<signal name="version_changed">
<description>
diff --git a/doc/classes/XRInterface.xml b/doc/classes/XRInterface.xml
index a7878378dd..29a5b75672 100644
--- a/doc/classes/XRInterface.xml
+++ b/doc/classes/XRInterface.xml
@@ -256,7 +256,7 @@
Player is free to move around, full positional tracking.
</constant>
<constant name="XR_PLAY_AREA_STAGE" value="4" enum="PlayAreaMode">
- Same as [constant XR_PLAY_AREA_ROOMSCALE] but origin point is fixed to the center of the physical space, [method XRServer.center_on_hmd] disabled.
+ Same as [constant XR_PLAY_AREA_ROOMSCALE] but origin point is fixed to the center of the physical space. In this mode, system-level recentering may be disabled, requiring the use of [method XRServer.center_on_hmd].
</constant>
<constant name="XR_ENV_BLEND_MODE_OPAQUE" value="0" enum="EnvironmentBlendMode">
Opaque blend mode. This is typically used for VR devices.
diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp
index a646b1ec7d..bc0d23acc4 100644
--- a/drivers/gles3/rasterizer_scene_gles3.cpp
+++ b/drivers/gles3/rasterizer_scene_gles3.cpp
@@ -280,7 +280,9 @@ void RasterizerSceneGLES3::_geometry_instance_add_surface_with_material(Geometry
GLES3::Mesh::Surface *s = reinterpret_cast<GLES3::Mesh::Surface *>(sdcache->surface);
if (p_material->shader_data->uses_tangent && !(s->format & RS::ARRAY_FORMAT_TANGENT)) {
- WARN_PRINT_ED("Attempting to use a shader that requires tangents with a mesh that doesn't contain tangents. Ensure that meshes are imported with the 'ensure_tangents' option. If creating your own meshes, add an `ARRAY_TANGENT` array (when using ArrayMesh) or call `generate_tangents()` (when using SurfaceTool).");
+ String shader_path = p_material->shader_data->path.is_empty() ? "" : "(" + p_material->shader_data->path + ")";
+ String mesh_path = mesh_storage->mesh_get_path(p_mesh).is_empty() ? "" : "(" + mesh_storage->mesh_get_path(p_mesh) + ")";
+ WARN_PRINT_ED(vformat("Attempting to use a shader %s that requires tangents with a mesh %s that doesn't contain tangents. Ensure that meshes are imported with the 'ensure_tangents' option. If creating your own meshes, add an `ARRAY_TANGENT` array (when using ArrayMesh) or call `generate_tangents()` (when using SurfaceTool).", shader_path, mesh_path));
}
}
@@ -1485,7 +1487,15 @@ void RasterizerSceneGLES3::_setup_environment(const RenderDataGLES3 *p_render_da
//time global variables
scene_state.ubo.time = time;
- if (is_environment(p_render_data->environment)) {
+ if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_UNSHADED) {
+ scene_state.ubo.use_ambient_light = true;
+ scene_state.ubo.ambient_light_color_energy[0] = 1;
+ scene_state.ubo.ambient_light_color_energy[1] = 1;
+ scene_state.ubo.ambient_light_color_energy[2] = 1;
+ scene_state.ubo.ambient_light_color_energy[3] = 1.0;
+ scene_state.ubo.use_ambient_cubemap = false;
+ scene_state.ubo.use_reflection_cubemap = false;
+ } else if (is_environment(p_render_data->environment)) {
RS::EnvironmentBG env_bg = environment_get_background(p_render_data->environment);
RS::EnvironmentAmbientSource ambient_src = environment_get_ambient_source(p_render_data->environment);
@@ -2324,7 +2334,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
bool keep_color = false;
float sky_energy_multiplier = 1.0;
- if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_OVERDRAW) {
+ if (unlikely(get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_OVERDRAW)) {
clear_color = Color(0, 0, 0, 1); //in overdraw mode, BG should always be black
} else if (render_data.environment.is_valid()) {
RS::EnvironmentBG bg_mode = environment_get_background(render_data.environment);
@@ -2693,6 +2703,7 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
}
glBindTexture(GL_TEXTURE_CUBE_MAP, texture_to_bind);
}
+
} else if constexpr (p_pass_mode == PASS_MODE_DEPTH || p_pass_mode == PASS_MODE_SHADOW) {
shader_variant = SceneShaderGLES3::MODE_DEPTH;
}
@@ -2730,8 +2741,16 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
material_data = surf->material_shadow;
mesh_surface = surf->surface_shadow;
} else {
- shader = surf->shader;
- material_data = surf->material;
+ if (unlikely(get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_OVERDRAW)) {
+ material_data = overdraw_material_data_ptr;
+ shader = material_data->shader_data;
+ } else if (unlikely(get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_LIGHTING)) {
+ material_data = default_material_data_ptr;
+ shader = material_data->shader_data;
+ } else {
+ shader = surf->shader;
+ material_data = surf->material;
+ }
mesh_surface = surf->surface;
}
@@ -3638,6 +3657,29 @@ void fragment() {
scene_globals.default_material = material_storage->material_allocate();
material_storage->material_initialize(scene_globals.default_material);
material_storage->material_set_shader(scene_globals.default_material, scene_globals.default_shader);
+ default_material_data_ptr = static_cast<GLES3::SceneMaterialData *>(GLES3::MaterialStorage::get_singleton()->material_get_data(scene_globals.default_material, RS::SHADER_SPATIAL));
+ }
+
+ {
+ // Overdraw material and shader.
+ scene_globals.overdraw_shader = material_storage->shader_allocate();
+ material_storage->shader_initialize(scene_globals.overdraw_shader);
+ material_storage->shader_set_code(scene_globals.overdraw_shader, R"(
+// 3D editor Overdraw debug draw mode shader.
+
+shader_type spatial;
+
+render_mode blend_add, unshaded;
+
+void fragment() {
+ ALBEDO = vec3(0.4, 0.8, 0.8);
+ ALPHA = 0.2;
+}
+)");
+ scene_globals.overdraw_material = material_storage->material_allocate();
+ material_storage->material_initialize(scene_globals.overdraw_material);
+ material_storage->material_set_shader(scene_globals.overdraw_material, scene_globals.overdraw_shader);
+ overdraw_material_data_ptr = static_cast<GLES3::SceneMaterialData *>(GLES3::MaterialStorage::get_singleton()->material_get_data(scene_globals.overdraw_material, RS::SHADER_SPATIAL));
}
{
@@ -3752,6 +3794,10 @@ RasterizerSceneGLES3::~RasterizerSceneGLES3() {
RSG::material_storage->material_free(scene_globals.default_material);
RSG::material_storage->shader_free(scene_globals.default_shader);
+ // Overdraw Shader
+ RSG::material_storage->material_free(scene_globals.overdraw_material);
+ RSG::material_storage->shader_free(scene_globals.overdraw_shader);
+
// Sky Shader
GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_free(sky_globals.shader_default_version);
RSG::material_storage->material_free(sky_globals.default_material);
diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h
index 045511321a..8bb4a30e2d 100644
--- a/drivers/gles3/rasterizer_scene_gles3.h
+++ b/drivers/gles3/rasterizer_scene_gles3.h
@@ -152,8 +152,13 @@ private:
RID default_material;
RID default_shader;
RID cubemap_filter_shader_version;
+ RID overdraw_material;
+ RID overdraw_shader;
} scene_globals;
+ GLES3::SceneMaterialData *default_material_data_ptr = nullptr;
+ GLES3::SceneMaterialData *overdraw_material_data_ptr = nullptr;
+
/* LIGHT INSTANCE */
struct LightData {
diff --git a/drivers/gles3/shaders/canvas.glsl b/drivers/gles3/shaders/canvas.glsl
index ce8fe25625..c517fcb8ae 100644
--- a/drivers/gles3/shaders/canvas.glsl
+++ b/drivers/gles3/shaders/canvas.glsl
@@ -587,6 +587,9 @@ void main() {
if (normal_used || (using_light && bool(read_draw_data_flags & FLAGS_DEFAULT_NORMAL_MAP_USED))) {
normal.xy = texture(normal_texture, uv).xy * vec2(2.0, -2.0) - vec2(1.0, -1.0);
+ if (bool(read_draw_data_flags & FLAGS_TRANSPOSE_RECT)) {
+ normal.xy = normal.yx;
+ }
if (bool(read_draw_data_flags & FLAGS_FLIP_H)) {
normal.x = -normal.x;
}
diff --git a/drivers/gles3/storage/light_storage.cpp b/drivers/gles3/storage/light_storage.cpp
index 2607a133d6..5421f57646 100644
--- a/drivers/gles3/storage/light_storage.cpp
+++ b/drivers/gles3/storage/light_storage.cpp
@@ -468,6 +468,9 @@ void LightStorage::reflection_probe_set_enable_shadows(RID p_probe, bool p_enabl
void LightStorage::reflection_probe_set_cull_mask(RID p_probe, uint32_t p_layers) {
}
+void LightStorage::reflection_probe_set_reflection_mask(RID p_probe, uint32_t p_layers) {
+}
+
void LightStorage::reflection_probe_set_resolution(RID p_probe, int p_resolution) {
}
@@ -483,6 +486,10 @@ uint32_t LightStorage::reflection_probe_get_cull_mask(RID p_probe) const {
return 0;
}
+uint32_t LightStorage::reflection_probe_get_reflection_mask(RID p_probe) const {
+ return 0;
+}
+
Vector3 LightStorage::reflection_probe_get_size(RID p_probe) const {
return Vector3();
}
diff --git a/drivers/gles3/storage/light_storage.h b/drivers/gles3/storage/light_storage.h
index 7ab0286098..96e6200219 100644
--- a/drivers/gles3/storage/light_storage.h
+++ b/drivers/gles3/storage/light_storage.h
@@ -575,6 +575,7 @@ public:
virtual void reflection_probe_set_enable_box_projection(RID p_probe, bool p_enable) override;
virtual void reflection_probe_set_enable_shadows(RID p_probe, bool p_enable) override;
virtual void reflection_probe_set_cull_mask(RID p_probe, uint32_t p_layers) override;
+ virtual void reflection_probe_set_reflection_mask(RID p_probe, uint32_t p_layers) override;
virtual void reflection_probe_set_resolution(RID p_probe, int p_resolution) override;
virtual void reflection_probe_set_mesh_lod_threshold(RID p_probe, float p_ratio) override;
virtual float reflection_probe_get_mesh_lod_threshold(RID p_probe) const override;
@@ -582,6 +583,7 @@ public:
virtual AABB reflection_probe_get_aabb(RID p_probe) const override;
virtual RS::ReflectionProbeUpdateMode reflection_probe_get_update_mode(RID p_probe) const override;
virtual uint32_t reflection_probe_get_cull_mask(RID p_probe) const override;
+ virtual uint32_t reflection_probe_get_reflection_mask(RID p_probe) const override;
virtual Vector3 reflection_probe_get_size(RID p_probe) const override;
virtual Vector3 reflection_probe_get_origin_offset(RID p_probe) const override;
virtual float reflection_probe_get_origin_max_distance(RID p_probe) const override;
diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp
index b4e266d976..475f3c33b8 100644
--- a/drivers/gles3/storage/mesh_storage.cpp
+++ b/drivers/gles3/storage/mesh_storage.cpp
@@ -727,6 +727,20 @@ AABB MeshStorage::mesh_get_aabb(RID p_mesh, RID p_skeleton) {
return aabb;
}
+void MeshStorage::mesh_set_path(RID p_mesh, const String &p_path) {
+ Mesh *mesh = mesh_owner.get_or_null(p_mesh);
+ ERR_FAIL_NULL(mesh);
+
+ mesh->path = p_path;
+}
+
+String MeshStorage::mesh_get_path(RID p_mesh) const {
+ Mesh *mesh = mesh_owner.get_or_null(p_mesh);
+ ERR_FAIL_NULL_V(mesh, String());
+
+ return mesh->path;
+}
+
void MeshStorage::mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) {
Mesh *mesh = mesh_owner.get_or_null(p_mesh);
ERR_FAIL_NULL(mesh);
diff --git a/drivers/gles3/storage/mesh_storage.h b/drivers/gles3/storage/mesh_storage.h
index 217c4dabf0..cea81baa0b 100644
--- a/drivers/gles3/storage/mesh_storage.h
+++ b/drivers/gles3/storage/mesh_storage.h
@@ -142,6 +142,8 @@ struct Mesh {
RID shadow_mesh;
HashSet<Mesh *> shadow_owners;
+ String path;
+
Dependency dependency;
};
@@ -304,8 +306,11 @@ public:
virtual void mesh_set_custom_aabb(RID p_mesh, const AABB &p_aabb) override;
virtual AABB mesh_get_custom_aabb(RID p_mesh) const override;
-
virtual AABB mesh_get_aabb(RID p_mesh, RID p_skeleton = RID()) override;
+
+ virtual void mesh_set_path(RID p_mesh, const String &p_path) override;
+ virtual String mesh_get_path(RID p_mesh) const override;
+
virtual void mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) override;
virtual void mesh_clear(RID p_mesh) override;
diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp
index 0285692ab7..e022294277 100644
--- a/editor/connections_dialog.cpp
+++ b/editor/connections_dialog.cpp
@@ -1203,15 +1203,24 @@ void ConnectionsDock::_slot_menu_about_to_popup() {
}
void ConnectionsDock::_tree_gui_input(const Ref<InputEvent> &p_event) {
- // Handle Delete press.
- if (ED_IS_SHORTCUT("connections_editor/disconnect", p_event)) {
- TreeItem *item = tree->get_selected();
- if (item && _get_item_type(*item) == TREE_ITEM_TYPE_CONNECTION) {
- Connection connection = item->get_metadata(0);
- _disconnect(connection);
- update_tree();
+ const Ref<InputEventKey> &key = p_event;
+
+ if (key.is_valid() && key->is_pressed() && !key->is_echo()) {
+ if (ED_IS_SHORTCUT("connections_editor/disconnect", p_event)) {
+ TreeItem *item = tree->get_selected();
+ if (item && _get_item_type(*item) == TREE_ITEM_TYPE_CONNECTION) {
+ Connection connection = item->get_metadata(0);
+ _disconnect(connection);
+ update_tree();
+
+ // Stop the Delete input from propagating elsewhere.
+ accept_event();
+ return;
+ }
+ } else if (ED_IS_SHORTCUT("editor/open_search", p_event)) {
+ search_box->grab_focus();
+ search_box->select_all();
- // Stop the Delete input from propagating elsewhere.
accept_event();
return;
}
@@ -1219,42 +1228,41 @@ void ConnectionsDock::_tree_gui_input(const Ref<InputEvent> &p_event) {
// Handle RMB press.
const Ref<InputEventMouseButton> &mb_event = p_event;
- if (mb_event.is_null() || !mb_event->is_pressed() || mb_event->get_button_index() != MouseButton::RIGHT) {
- return;
- }
- TreeItem *item = tree->get_item_at_position(mb_event->get_position());
- if (!item) {
- return;
- }
+ if (mb_event.is_valid() && mb_event->is_pressed() && mb_event->get_button_index() == MouseButton::RIGHT) {
+ TreeItem *item = tree->get_item_at_position(mb_event->get_position());
+ if (!item) {
+ return;
+ }
- if (item->is_selectable(0)) {
- // Update selection now, before `about_to_popup` signal. Needed for SIGNAL and CONNECTION context menus.
- tree->set_selected(item);
- }
+ if (item->is_selectable(0)) {
+ // Update selection now, before `about_to_popup` signal. Needed for SIGNAL and CONNECTION context menus.
+ tree->set_selected(item);
+ }
- Vector2 screen_position = tree->get_screen_position() + mb_event->get_position();
+ Vector2 screen_position = tree->get_screen_position() + mb_event->get_position();
- switch (_get_item_type(*item)) {
- case TREE_ITEM_TYPE_ROOT:
- break;
- case TREE_ITEM_TYPE_CLASS:
- class_menu_doc_class_name = item->get_metadata(0);
- class_menu->set_position(screen_position);
- class_menu->reset_size();
- class_menu->popup();
- accept_event(); // Don't collapse item.
- break;
- case TREE_ITEM_TYPE_SIGNAL:
- signal_menu->set_position(screen_position);
- signal_menu->reset_size();
- signal_menu->popup();
- break;
- case TREE_ITEM_TYPE_CONNECTION:
- slot_menu->set_position(screen_position);
- slot_menu->reset_size();
- slot_menu->popup();
- break;
+ switch (_get_item_type(*item)) {
+ case TREE_ITEM_TYPE_ROOT:
+ break;
+ case TREE_ITEM_TYPE_CLASS:
+ class_menu_doc_class_name = item->get_metadata(0);
+ class_menu->set_position(screen_position);
+ class_menu->reset_size();
+ class_menu->popup();
+ accept_event(); // Don't collapse item.
+ break;
+ case TREE_ITEM_TYPE_SIGNAL:
+ signal_menu->set_position(screen_position);
+ signal_menu->reset_size();
+ signal_menu->popup();
+ break;
+ case TREE_ITEM_TYPE_CONNECTION:
+ slot_menu->set_position(screen_position);
+ slot_menu->reset_size();
+ slot_menu->popup();
+ break;
+ }
}
}
diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp
index 794d1b3e10..786e841c21 100644
--- a/editor/editor_data.cpp
+++ b/editor/editor_data.cpp
@@ -361,6 +361,12 @@ void EditorData::notify_resource_saved(const Ref<Resource> &p_resource) {
}
}
+void EditorData::notify_scene_saved(const String &p_path) {
+ for (int i = 0; i < editor_plugins.size(); i++) {
+ editor_plugins[i]->notify_scene_saved(p_path);
+ }
+}
+
void EditorData::clear_editor_states() {
for (int i = 0; i < editor_plugins.size(); i++) {
editor_plugins[i]->clear();
diff --git a/editor/editor_data.h b/editor/editor_data.h
index 01e9dc4b07..d1af400e87 100644
--- a/editor/editor_data.h
+++ b/editor/editor_data.h
@@ -236,6 +236,7 @@ public:
Dictionary restore_edited_scene_state(EditorSelection *p_selection, EditorSelectionHistory *p_history);
void notify_edited_scene_changed();
void notify_resource_saved(const Ref<Resource> &p_resource);
+ void notify_scene_saved(const String &p_path);
bool script_class_is_parent(const String &p_class, const String &p_inherits);
StringName script_class_get_base(const String &p_class) const;
diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp
index 229eb79e11..104b0a73c4 100644
--- a/editor/editor_help_search.cpp
+++ b/editor/editor_help_search.cpp
@@ -445,6 +445,9 @@ bool EditorHelpSearch::Runner::_phase_match_classes() {
}
void EditorHelpSearch::Runner::_populate_cache() {
+ // Deselect to prevent re-selection issues.
+ results_tree->deselect_all();
+
root_item = results_tree->get_root();
if (root_item) {
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 8cffc2b4b0..24bfba3844 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -1805,6 +1805,7 @@ void EditorNode::_save_scene(String p_file, int idx) {
// This needs to be emitted before saving external resources.
emit_signal(SNAME("scene_saved"), p_file);
+ editor_data.notify_scene_saved(p_file);
_save_external_resources();
saving_scene = p_file; // Some editors may save scenes of built-in resources as external data, so avoid saving this scene again.
diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp
index f0044edff2..866d7ae233 100644
--- a/editor/editor_plugin.cpp
+++ b/editor/editor_plugin.cpp
@@ -249,6 +249,10 @@ void EditorPlugin::notify_resource_saved(const Ref<Resource> &p_resource) {
emit_signal(SNAME("resource_saved"), p_resource);
}
+void EditorPlugin::notify_scene_saved(const String &p_scene_filepath) {
+ emit_signal(SNAME("scene_saved"), p_scene_filepath);
+}
+
bool EditorPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
bool success = false;
GDVIRTUAL_CALL(_forward_canvas_gui_input, p_event, success);
@@ -632,6 +636,7 @@ void EditorPlugin::_bind_methods() {
ADD_SIGNAL(MethodInfo("scene_closed", PropertyInfo(Variant::STRING, "filepath")));
ADD_SIGNAL(MethodInfo("main_screen_changed", PropertyInfo(Variant::STRING, "screen_name")));
ADD_SIGNAL(MethodInfo("resource_saved", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource")));
+ ADD_SIGNAL(MethodInfo("scene_saved", PropertyInfo(Variant::STRING, "filepath")));
ADD_SIGNAL(MethodInfo("project_settings_changed"));
BIND_ENUM_CONSTANT(CONTAINER_TOOLBAR);
diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h
index 8870ef425e..a93955046a 100644
--- a/editor/editor_plugin.h
+++ b/editor/editor_plugin.h
@@ -160,6 +160,7 @@ public:
void notify_scene_changed(const Node *scn_root);
void notify_scene_closed(const String &scene_filepath);
void notify_resource_saved(const Ref<Resource> &p_resource);
+ void notify_scene_saved(const String &p_scene_filepath);
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event);
virtual void forward_canvas_draw_over_viewport(Control *p_overlay);
diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp
index 69ad076f8a..1491adee52 100644
--- a/editor/export/export_template_manager.cpp
+++ b/editor/export/export_template_manager.cpp
@@ -466,6 +466,13 @@ bool ExportTemplateManager::_install_file_selected(const String &p_file, bool p_
break;
}
+ if (String::utf8(fname).ends_with("/")) {
+ // File is a directory, ignore it.
+ // Directories will be created when extracting each file.
+ ret = unzGoToNextFile(pkg);
+ continue;
+ }
+
String file_path(String::utf8(fname).simplify_path());
String file = file_path.get_file();
diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp
index c708e77719..c6087f5b13 100644
--- a/editor/find_in_files.cpp
+++ b/editor/find_in_files.cpp
@@ -747,7 +747,7 @@ void FindInFilesPanel::_on_result_found(String fpath, int line_number, int begin
String start = vformat("%3s: ", line_number);
item->set_text(text_index, start + text);
- item->set_custom_draw(text_index, this, "_draw_result_text");
+ item->set_custom_draw_callback(text_index, callable_mp(this, &FindInFilesPanel::draw_result_text));
Result r;
r.line_number = line_number;
@@ -988,7 +988,6 @@ void FindInFilesPanel::set_progress_visible(bool p_visible) {
void FindInFilesPanel::_bind_methods() {
ClassDB::bind_method("_on_result_found", &FindInFilesPanel::_on_result_found);
ClassDB::bind_method("_on_finished", &FindInFilesPanel::_on_finished);
- ClassDB::bind_method("_draw_result_text", &FindInFilesPanel::draw_result_text);
ADD_SIGNAL(MethodInfo(SIGNAL_RESULT_SELECTED,
PropertyInfo(Variant::STRING, "path"),
diff --git a/editor/groups_editor.cpp b/editor/groups_editor.cpp
index 723a7c8901..ba7b627207 100644
--- a/editor/groups_editor.cpp
+++ b/editor/groups_editor.cpp
@@ -783,6 +783,9 @@ void GroupsEditor::_groups_gui_input(Ref<InputEvent> p_event) {
_menu_id_pressed(DELETE_GROUP);
} else if (ED_IS_SHORTCUT("groups_editor/rename", p_event)) {
_menu_id_pressed(RENAME_GROUP);
+ } else if (ED_IS_SHORTCUT("editor/open_search", p_event)) {
+ filter->grab_focus();
+ filter->select_all();
} else {
return;
}
diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp
index 7a9df26fa7..8e90ec0194 100644
--- a/editor/gui/scene_tree_editor.cpp
+++ b/editor/gui/scene_tree_editor.cpp
@@ -1051,10 +1051,9 @@ void SceneTreeEditor::_rename_node(Node *p_node, const String &p_name) {
}
}
+ new_name = p_node->get_parent()->prevalidate_child_name(p_node, new_name);
if (new_name == p_node->get_name()) {
- if (item->get_text(0).is_empty()) {
- item->set_text(0, new_name);
- }
+ item->set_text(0, new_name);
return;
}
diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp
index e1640af47b..d5be2bd5a9 100644
--- a/editor/inspector_dock.cpp
+++ b/editor/inspector_dock.cpp
@@ -614,6 +614,26 @@ void InspectorDock::apply_script_properties(Object *p_object) {
stored_properties.clear();
}
+void InspectorDock::shortcut_input(const Ref<InputEvent> &p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
+ Ref<InputEventKey> key = p_event;
+
+ if (key.is_null() || !key->is_pressed() || key->is_echo()) {
+ return;
+ }
+
+ if (!is_visible() || !inspector->get_rect().has_point(inspector->get_local_mouse_position())) {
+ return;
+ }
+
+ if (ED_IS_SHORTCUT("editor/open_search", p_event)) {
+ search->grab_focus();
+ search->select_all();
+ accept_event();
+ }
+}
+
InspectorDock::InspectorDock(EditorData &p_editor_data) {
singleton = this;
set_name("Inspector");
@@ -770,6 +790,8 @@ InspectorDock::InspectorDock(EditorData &p_editor_data) {
inspector->set_use_filter(true); // TODO: check me
inspector->connect("resource_selected", callable_mp(this, &InspectorDock::_resource_selected));
+
+ set_process_shortcut_input(true);
}
InspectorDock::~InspectorDock() {
diff --git a/editor/inspector_dock.h b/editor/inspector_dock.h
index 622a2521b3..1ad4b52b7d 100644
--- a/editor/inspector_dock.h
+++ b/editor/inspector_dock.h
@@ -133,6 +133,8 @@ class InspectorDock : public VBoxContainer {
void _select_history(int p_idx);
void _prepare_history();
+ virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
+
private:
static InspectorDock *singleton;
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 3722d6beba..30e0dc95c7 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -3933,7 +3933,7 @@ void CanvasItemEditor::_notification(int p_what) {
} else {
rect = Rect2();
}
- Transform2D xform = ci->get_transform();
+ Transform2D xform = ci->get_global_transform();
if (rect != se->prev_rect || xform != se->prev_xform) {
viewport->queue_redraw();
diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp
index 057e6443d6..65f52bc92c 100644
--- a/editor/plugins/tiles/tiles_editor_plugin.cpp
+++ b/editor/plugins/tiles/tiles_editor_plugin.cpp
@@ -68,6 +68,9 @@ void TilesEditorUtils::_thread_func(void *ud) {
}
void TilesEditorUtils::_thread() {
+ CallQueue queue;
+ MessageQueue::set_thread_singleton_override(&queue);
+
pattern_thread_exited.clear();
while (!pattern_thread_exit.is_set()) {
pattern_preview_sem.wait();
@@ -127,6 +130,8 @@ void TilesEditorUtils::_thread() {
// Add the viewport at the last moment to avoid rendering too early.
callable_mp((Node *)EditorNode::get_singleton(), &Node::add_child).call_deferred(viewport, false, Node::INTERNAL_MODE_DISABLED);
+ MessageQueue::get_singleton()->flush();
+
RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<TilesEditorUtils *>(this), &TilesEditorUtils::_preview_frame_started), Object::CONNECT_ONE_SHOT);
pattern_preview_done.wait();
@@ -139,7 +144,11 @@ void TilesEditorUtils::_thread() {
viewport->queue_free();
}
}
+
+ MessageQueue::get_singleton()->flush();
}
+
+ MessageQueue::get_singleton()->flush();
pattern_thread_exited.set();
}
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index 6de37172b3..89fff008ea 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -3191,6 +3191,15 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, Stri
bool created_expression_port = false;
+ // A node is inserted in an already present connection.
+ if (from_node != -1 && from_slot != -1 && to_node != -1 && to_slot != -1) {
+ undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_slot, to_node, to_slot);
+ undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, from_node, from_slot, to_node, to_slot);
+ undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_slot, to_node, to_slot);
+ undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_slot, to_node, to_slot);
+ }
+
+ // Create a connection from the new node to an input port of an existing one.
if (to_node != -1 && to_slot != -1) {
VisualShaderNode::PortType input_port_type = visual_shader->get_node(type, to_node)->get_input_port_type(to_slot);
@@ -3260,7 +3269,10 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, Stri
}
}
}
- } else if (from_node != -1 && from_slot != -1) {
+ }
+
+ // Create a connection from the output port of an existing node to the new one.
+ if (from_node != -1 && from_slot != -1) {
VisualShaderNode::PortType output_port_type = visual_shader->get_node(type, from_node)->get_output_port_type(from_slot);
if (expr && expr->is_editable()) {
@@ -3483,8 +3495,11 @@ void VisualShaderEditor::_nodes_dragged() {
undo_redo->add_undo_method(graph_plugin.ptr(), "set_node_position", E.type, E.node, E.from);
}
- drag_buffer.clear();
undo_redo->commit_action();
+
+ _handle_node_drop_on_connection();
+
+ drag_buffer.clear();
}
void VisualShaderEditor::_connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) {
@@ -3564,6 +3579,132 @@ void VisualShaderEditor::_connection_from_empty(const String &p_to, int p_to_slo
_show_members_dialog(true, input_port_type, output_port_type);
}
+bool VisualShaderEditor::_check_node_drop_on_connection(const Vector2 &p_position, Ref<GraphEdit::Connection> *r_closest_connection, int *r_from_port, int *r_to_port) {
+ VisualShader::Type shader_type = get_current_shader_type();
+
+ // Get selected graph node.
+ Ref<VisualShaderNode> selected_vsnode;
+ int selected_node_id = -1;
+ int selected_node_count = 0;
+ Rect2 selected_node_rect;
+
+ for (int i = 0; i < graph->get_child_count(); i++) {
+ GraphNode *graph_node = Object::cast_to<GraphNode>(graph->get_child(i));
+ if (graph_node && graph_node->is_selected()) {
+ selected_node_id = String(graph_node->get_name()).to_int();
+ Ref<VisualShaderNode> vsnode = visual_shader->get_node(shader_type, selected_node_id);
+ if (!vsnode->is_closable()) {
+ continue;
+ }
+
+ selected_node_count += 1;
+
+ Ref<VisualShaderNode> node = visual_shader->get_node(shader_type, selected_node_id);
+ selected_vsnode = node;
+ selected_node_rect = graph_node->get_rect();
+ }
+ }
+
+ // Only a single node - which has both input and output ports but is not connected yet - can be inserted.
+ if (selected_node_count != 1 || !selected_vsnode.is_valid()) {
+ return false;
+ }
+
+ // Check whether the dragged node was dropped over a connection.
+ List<Ref<GraphEdit::Connection>> intersecting_connections = graph->get_connections_intersecting_with_rect(selected_node_rect);
+
+ if (intersecting_connections.is_empty()) {
+ return false;
+ }
+
+ Ref<GraphEdit::Connection> intersecting_connection = intersecting_connections.front()->get();
+
+ if (selected_vsnode->is_any_port_connected() || selected_vsnode->get_input_port_count() == 0 || selected_vsnode->get_output_port_count() == 0) {
+ return false;
+ }
+
+ VisualShaderNode::PortType original_port_type_from = visual_shader->get_node(shader_type, String(intersecting_connection->from_node).to_int())->get_output_port_type(intersecting_connection->from_port);
+ VisualShaderNode::PortType original_port_type_to = visual_shader->get_node(shader_type, String(intersecting_connection->to_node).to_int())->get_input_port_type(intersecting_connection->to_port);
+
+ // Searching for the default port or the first compatible input port of the node to insert.
+ int _to_port = -1;
+ for (int i = 0; i < selected_vsnode->get_input_port_count(); i++) {
+ if (visual_shader->is_port_types_compatible(original_port_type_from, selected_vsnode->get_input_port_type(i))) {
+ if (i == selected_vsnode->get_default_input_port(original_port_type_from)) {
+ _to_port = i;
+ break;
+ } else if (_to_port == -1) {
+ _to_port = i;
+ }
+ }
+ }
+
+ // Searching for the first compatible output port of the node to insert.
+ int _from_port = -1;
+ for (int i = 0; i < selected_vsnode->get_output_port_count(); i++) {
+ if (visual_shader->is_port_types_compatible(selected_vsnode->get_output_port_type(i), original_port_type_to)) {
+ _from_port = i;
+ break;
+ }
+ }
+
+ if (_to_port == -1 || _from_port == -1) {
+ return false;
+ }
+
+ if (r_closest_connection != nullptr) {
+ *r_closest_connection = intersecting_connection;
+ }
+ if (r_from_port != nullptr) {
+ *r_from_port = _from_port;
+ }
+ if (r_to_port != nullptr) {
+ *r_to_port = _to_port;
+ }
+
+ return true;
+}
+
+void VisualShaderEditor::_handle_node_drop_on_connection() {
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Insert node"));
+
+ // Check whether the dragged node was dropped over a connection.
+ Ref<GraphEdit::Connection> closest_connection;
+ int _from_port = -1;
+ int _to_port = -1;
+
+ if (!_check_node_drop_on_connection(graph->get_local_mouse_position(), &closest_connection, &_from_port, &_to_port)) {
+ return;
+ }
+
+ int selected_node_id = drag_buffer[0].node;
+ VisualShader::Type shader_type = get_current_shader_type();
+ Ref<VisualShaderNode> selected_vsnode = visual_shader->get_node(shader_type, selected_node_id);
+
+ // Delete the old connection.
+ undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", shader_type, String(closest_connection->from_node).to_int(), closest_connection->from_port, String(closest_connection->to_node).to_int(), closest_connection->to_port);
+ undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", shader_type, String(closest_connection->from_node).to_int(), closest_connection->from_port, String(closest_connection->to_node).to_int(), closest_connection->to_port);
+ undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", shader_type, String(closest_connection->from_node).to_int(), closest_connection->from_port, String(closest_connection->to_node).to_int(), closest_connection->to_port);
+ undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", shader_type, String(closest_connection->from_node).to_int(), closest_connection->from_port, String(closest_connection->to_node).to_int(), closest_connection->to_port);
+
+ // Add the connection to the dropped node.
+ undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", shader_type, String(closest_connection->from_node).to_int(), closest_connection->from_port, selected_node_id, _to_port);
+ undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", shader_type, String(closest_connection->from_node).to_int(), closest_connection->from_port, selected_node_id, _to_port);
+ undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", shader_type, String(closest_connection->from_node).to_int(), closest_connection->from_port, selected_node_id, _to_port);
+ undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", shader_type, String(closest_connection->from_node).to_int(), closest_connection->from_port, selected_node_id, _to_port);
+
+ // Add the connection from the dropped node.
+ undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", shader_type, selected_node_id, _from_port, String(closest_connection->to_node).to_int(), closest_connection->to_port);
+ undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", shader_type, selected_node_id, _from_port, String(closest_connection->to_node).to_int(), closest_connection->to_port);
+ undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", shader_type, selected_node_id, _from_port, String(closest_connection->to_node).to_int(), closest_connection->to_port);
+ undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", shader_type, selected_node_id, _from_port, String(closest_connection->to_node).to_int(), closest_connection->to_port);
+
+ undo_redo->commit_action();
+
+ call_deferred(SNAME("_update_graph"));
+}
+
void VisualShaderEditor::_delete_nodes(int p_type, const List<int> &p_nodes) {
VisualShader::Type type = VisualShader::Type(p_type);
List<VisualShader::Connection> conns;
@@ -3923,9 +4064,19 @@ void VisualShaderEditor::_node_selected(Object *p_node) {
}
void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) {
+ Ref<InputEventMouseMotion> mm = p_event;
Ref<InputEventMouseButton> mb = p_event;
VisualShader::Type type = get_current_shader_type();
+ // Highlight valid connection on which a node can be dropped.
+ if (mm.is_valid() && mm->get_button_mask().has_flag(MouseButtonMask::LEFT)) {
+ Ref<GraphEdit::Connection> closest_connection;
+ graph->reset_all_connection_activity();
+ if (_check_node_drop_on_connection(graph->get_local_mouse_position(), &closest_connection)) {
+ graph->set_connection_activity(closest_connection->from_node, closest_connection->from_port, closest_connection->to_node, closest_connection->to_port, 1.0);
+ }
+ }
+
Ref<VisualShaderNode> selected_vsnode;
// Right click actions.
if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
@@ -3981,7 +4132,16 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) {
}
}
- if (selected_closable_graph_elements.is_empty() && copy_buffer_empty) {
+ menu_point = graph->get_local_mouse_position();
+ Point2 gpos = get_screen_position() + get_local_mouse_position();
+
+ Ref<GraphEdit::Connection> closest_connection = graph->get_closest_connection_at_point(menu_point);
+ if (closest_connection.is_valid()) {
+ clicked_connection = closest_connection;
+ connection_popup_menu->set_position(gpos);
+ connection_popup_menu->reset_size();
+ connection_popup_menu->popup();
+ } else if (selected_closable_graph_elements.is_empty() && copy_buffer_empty) {
_show_members_dialog(true);
} else {
popup_menu->set_item_disabled(NodeMenuOptions::CUT, selected_closable_graph_elements.is_empty());
@@ -4053,8 +4213,6 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) {
popup_menu->add_item(TTR("Set Comment Description"), NodeMenuOptions::SET_COMMENT_DESCRIPTION);
}
- menu_point = graph->get_local_mouse_position();
- Point2 gpos = get_screen_position() + get_local_mouse_position();
popup_menu->set_position(gpos);
popup_menu->reset_size();
popup_menu->popup();
@@ -4757,6 +4915,27 @@ void VisualShaderEditor::_member_create() {
TreeItem *item = members->get_selected();
if (item != nullptr && item->has_meta("id")) {
int idx = members->get_selected()->get_meta("id");
+ if (connection_node_insert_requested) {
+ from_node = String(clicked_connection->from_node).to_int();
+ from_slot = clicked_connection->from_port;
+ to_node = String(clicked_connection->to_node).to_int();
+ to_slot = clicked_connection->to_port;
+
+ connection_node_insert_requested = false;
+
+ saved_node_pos_dirty = true;
+
+ // Find both graph nodes and get their positions.
+ GraphNode *from_graph_element = Object::cast_to<GraphNode>(graph->get_node(itos(from_node)));
+ GraphNode *to_graph_element = Object::cast_to<GraphNode>(graph->get_node(itos(to_node)));
+
+ ERR_FAIL_NULL(from_graph_element);
+ ERR_FAIL_NULL(to_graph_element);
+
+ // Since the size of the node to add is not known yet, it's not possible to center it exactly.
+ float zoom = graph->get_zoom();
+ saved_node_pos = 0.5 * (from_graph_element->get_position() + zoom * from_graph_element->get_output_port_position(from_slot) + to_graph_element->get_position() + zoom * to_graph_element->get_input_port_position(to_slot));
+ }
_add_node(idx, add_options[idx].ops);
members_dialog->hide();
}
@@ -4767,6 +4946,7 @@ void VisualShaderEditor::_member_cancel() {
to_slot = -1;
from_node = -1;
from_slot = -1;
+ connection_node_insert_requested = false;
}
void VisualShaderEditor::_update_varying_tree() {
@@ -4938,6 +5118,37 @@ void VisualShaderEditor::_node_menu_id_pressed(int p_idx) {
}
}
+void VisualShaderEditor::_connection_menu_id_pressed(int p_idx) {
+ switch (p_idx) {
+ case ConnectionMenuOptions::DISCONNECT: {
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Disconnect"));
+ undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", get_current_shader_type(), String(clicked_connection->from_node).to_int(), clicked_connection->from_port, String(clicked_connection->to_node).to_int(), clicked_connection->to_port);
+ undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", get_current_shader_type(), String(clicked_connection->from_node).to_int(), clicked_connection->from_port, String(clicked_connection->to_node).to_int(), clicked_connection->to_port);
+ undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", get_current_shader_type(), String(clicked_connection->from_node).to_int(), clicked_connection->from_port, String(clicked_connection->to_node).to_int(), clicked_connection->to_port);
+ undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", get_current_shader_type(), String(clicked_connection->from_node).to_int(), clicked_connection->from_port, String(clicked_connection->to_node).to_int(), clicked_connection->to_port);
+ undo_redo->commit_action();
+ } break;
+ case ConnectionMenuOptions::INSERT_NEW_NODE: {
+ VisualShaderNode::PortType input_port_type = VisualShaderNode::PORT_TYPE_MAX;
+ VisualShaderNode::PortType output_port_type = VisualShaderNode::PORT_TYPE_MAX;
+ Ref<VisualShaderNode> node1 = visual_shader->get_node(get_current_shader_type(), String(clicked_connection->from_node).to_int());
+ if (node1.is_valid()) {
+ output_port_type = node1->get_output_port_type(from_slot);
+ }
+ Ref<VisualShaderNode> node2 = visual_shader->get_node(get_current_shader_type(), String(clicked_connection->to_node).to_int());
+ if (node2.is_valid()) {
+ input_port_type = node2->get_input_port_type(to_slot);
+ }
+
+ connection_node_insert_requested = true;
+ _show_members_dialog(true, input_port_type, output_port_type);
+ } break;
+ default:
+ break;
+ }
+}
+
Variant VisualShaderEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
if (p_from == members) {
TreeItem *it = members->get_item_at_position(p_point);
@@ -5417,6 +5628,12 @@ VisualShaderEditor::VisualShaderEditor() {
popup_menu->add_item(TTR("Clear Copy Buffer"), NodeMenuOptions::CLEAR_COPY_BUFFER);
popup_menu->connect("id_pressed", callable_mp(this, &VisualShaderEditor::_node_menu_id_pressed));
+ connection_popup_menu = memnew(PopupMenu);
+ add_child(connection_popup_menu);
+ connection_popup_menu->add_item(TTR("Disconnect"), ConnectionMenuOptions::DISCONNECT);
+ connection_popup_menu->add_item(TTR("Insert New Node"), ConnectionMenuOptions::INSERT_NEW_NODE);
+ connection_popup_menu->connect("id_pressed", callable_mp(this, &VisualShaderEditor::_connection_menu_id_pressed));
+
///////////////////////////////////////
// SHADER NODES TREE
///////////////////////////////////////
diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h
index 5f1fde3a52..39e721f226 100644
--- a/editor/plugins/visual_shader_editor_plugin.h
+++ b/editor/plugins/visual_shader_editor_plugin.h
@@ -34,13 +34,13 @@
#include "editor/editor_plugin.h"
#include "editor/editor_properties.h"
#include "editor/plugins/editor_resource_conversion_plugin.h"
+#include "scene/gui/graph_edit.h"
#include "scene/resources/syntax_highlighter.h"
#include "scene/resources/visual_shader.h"
class CodeEdit;
class ColorPicker;
class CurveEditor;
-class GraphEdit;
class GraphElement;
class MenuButton;
class PopupPanel;
@@ -203,6 +203,7 @@ class VisualShaderEditor : public VBoxContainer {
VisualShaderNode::PortType members_input_port_type = VisualShaderNode::PORT_TYPE_MAX;
VisualShaderNode::PortType members_output_port_type = VisualShaderNode::PORT_TYPE_MAX;
PopupMenu *popup_menu = nullptr;
+ PopupMenu *connection_popup_menu = nullptr;
PopupMenu *constants_submenu = nullptr;
MenuButton *tools = nullptr;
@@ -282,6 +283,11 @@ class VisualShaderEditor : public VBoxContainer {
SET_COMMENT_DESCRIPTION,
};
+ enum ConnectionMenuOptions {
+ INSERT_NEW_NODE,
+ DISCONNECT,
+ };
+
enum class VaryingMenuOptions {
ADD,
REMOVE,
@@ -397,6 +403,9 @@ class VisualShaderEditor : public VBoxContainer {
int from_node = -1;
int from_slot = -1;
+ Ref<GraphEdit::Connection> clicked_connection;
+ bool connection_node_insert_requested = false;
+
HashSet<int> selected_constants;
HashSet<int> selected_parameters;
int selected_comment = -1;
@@ -409,6 +418,8 @@ class VisualShaderEditor : public VBoxContainer {
void _connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position);
void _connection_from_empty(const String &p_to, int p_to_slot, const Vector2 &p_release_position);
+ bool _check_node_drop_on_connection(const Vector2 &p_position, Ref<GraphEdit::Connection> *r_closest_connection, int *r_node_id = nullptr, int *r_to_port = nullptr);
+ void _handle_node_drop_on_connection();
void _comment_title_popup_show(const Point2 &p_position, int p_node_id);
void _comment_title_popup_hide();
@@ -501,6 +512,7 @@ class VisualShaderEditor : public VBoxContainer {
Vector2 menu_point;
void _node_menu_id_pressed(int p_idx);
+ void _connection_menu_id_pressed(int p_idx);
Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 9c7ba827b7..4d998118e2 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -162,6 +162,20 @@ void SceneTreeDock::shortcut_input(const Ref<InputEvent> &p_event) {
accept_event();
}
+void SceneTreeDock::_scene_tree_gui_input(Ref<InputEvent> p_event) {
+ Ref<InputEventKey> key = p_event;
+
+ if (key.is_null() || !key->is_pressed() || key->is_echo()) {
+ return;
+ }
+
+ if (ED_IS_SHORTCUT("editor/open_search", p_event)) {
+ filter->grab_focus();
+ filter->select_all();
+ accept_event();
+ }
+}
+
void SceneTreeDock::instantiate(const String &p_file) {
Vector<String> scenes;
scenes.push_back(p_file);
@@ -4226,6 +4240,7 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec
scene_tree->connect("script_dropped", callable_mp(this, &SceneTreeDock::_script_dropped));
scene_tree->connect("nodes_dragged", callable_mp(this, &SceneTreeDock::_nodes_drag_begin));
+ scene_tree->get_scene_tree()->connect("gui_input", callable_mp(this, &SceneTreeDock::_scene_tree_gui_input));
scene_tree->get_scene_tree()->connect("item_icon_double_clicked", callable_mp(this, &SceneTreeDock::_focus_node));
editor_selection->connect("selection_changed", callable_mp(this, &SceneTreeDock::_selection_changed));
diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h
index e765ef2a6b..8b95cc02dd 100644
--- a/editor/scene_tree_dock.h
+++ b/editor/scene_tree_dock.h
@@ -235,6 +235,7 @@ class SceneTreeDock : public VBoxContainer {
void _nodes_drag_begin();
virtual void input(const Ref<InputEvent> &p_event) override;
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
+ void _scene_tree_gui_input(Ref<InputEvent> p_event);
void _new_scene_from(String p_file);
void _set_node_owner_recursive(Node *p_node, Node *p_owner, const HashMap<const Node *, Node *> &p_inverse_duplimap);
diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp
index 886f105efc..bccfe6d786 100644
--- a/editor/themes/editor_theme_manager.cpp
+++ b/editor/themes/editor_theme_manager.cpp
@@ -1440,7 +1440,7 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
}
p_theme->set_color("selection_fill", "GraphEdit", p_theme->get_color(SNAME("box_selection_fill_color"), EditorStringName(Editor)));
p_theme->set_color("selection_stroke", "GraphEdit", p_theme->get_color(SNAME("box_selection_stroke_color"), EditorStringName(Editor)));
- p_theme->set_color("activity", "GraphEdit", p_config.accent_color);
+ p_theme->set_color("activity", "GraphEdit", p_config.dark_theme ? Color(1, 1, 1) : Color(0, 0, 0));
p_theme->set_color("connection_hover_tint_color", "GraphEdit", p_config.dark_theme ? Color(0, 0, 0, 0.3) : Color(1, 1, 1, 0.3));
p_theme->set_color("connection_valid_target_tint_color", "GraphEdit", p_config.dark_theme ? Color(1, 1, 1, 0.4) : Color(0, 0, 0, 0.4));
diff --git a/main/main.cpp b/main/main.cpp
index dbe186d63a..49cb1ca24d 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -172,6 +172,7 @@ static bool editor = false;
static bool project_manager = false;
static bool cmdline_tool = false;
static String locale;
+static String log_file;
static bool show_help = false;
static uint64_t quit_after = 0;
static OS::ProcessID editor_pid = 0;
@@ -450,7 +451,9 @@ void Main::print_help(const char *p_binary) {
OS::get_singleton()->print(" --text-driver <driver> Text driver (Fonts, BiDi, shaping).\n");
OS::get_singleton()->print(" --tablet-driver <driver> Pen tablet input driver.\n");
OS::get_singleton()->print(" --headless Enable headless mode (--display-driver headless --audio-driver Dummy). Useful for servers and with --script.\n");
- OS::get_singleton()->print(" --write-movie <file> Writes a video to the specified path (usually with .avi or .png extension).\n");
+ OS::get_singleton()->print(" --log-file <file> Write output/error log to the specified path instead of the default location defined by the project.\n");
+ OS::get_singleton()->print(" <file> path should be absolute or relative to the project directory.\n");
+ OS::get_singleton()->print(" --write-movie <file> Write a video to the specified path (usually with .avi or .png extension).\n");
OS::get_singleton()->print(" --fixed-fps is forced when enabled, but it can be used to change movie FPS.\n");
OS::get_singleton()->print(" --disable-vsync can speed up movie writing but makes interaction more difficult.\n");
OS::get_singleton()->print(" --quit-after can be used to specify the number of frames to write.\n");
@@ -1165,6 +1168,15 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
audio_driver = NULL_AUDIO_DRIVER;
display_driver = NULL_DISPLAY_DRIVER;
+ } else if (I->get() == "--log-file") { // write to log file
+
+ if (I->next()) {
+ log_file = I->next()->get();
+ N = I->next()->next();
+ } else {
+ OS::get_singleton()->print("Missing log file path argument, aborting.\n");
+ goto error;
+ }
} else if (I->get() == "--profiling") { // enable profiling
use_debug_profiler = true;
@@ -1689,12 +1701,24 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF("debug/file_logging/log_path", "user://logs/godot.log");
GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/file_logging/max_log_files", PROPERTY_HINT_RANGE, "0,20,1,or_greater"), 5);
- if (!project_manager && !editor && FileAccess::get_create_func(FileAccess::ACCESS_USERDATA) &&
- GLOBAL_GET("debug/file_logging/enable_file_logging")) {
+ // If `--log-file` is used to override the log path, allow creating logs for the project manager or editor
+ // and even if file logging is disabled in the Project Settings.
+ // `--log-file` can be used with any path (including absolute paths outside the project folder),
+ // so check for filesystem access if it's used.
+ if (FileAccess::get_create_func(!log_file.is_empty() ? FileAccess::ACCESS_FILESYSTEM : FileAccess::ACCESS_USERDATA) &&
+ (!log_file.is_empty() || (!project_manager && !editor && GLOBAL_GET("debug/file_logging/enable_file_logging")))) {
// Don't create logs for the project manager as they would be written to
// the current working directory, which is inconvenient.
- String base_path = GLOBAL_GET("debug/file_logging/log_path");
- int max_files = GLOBAL_GET("debug/file_logging/max_log_files");
+ String base_path;
+ int max_files;
+ if (!log_file.is_empty()) {
+ base_path = log_file;
+ // Ensure log file name respects the specified override by disabling log rotation.
+ max_files = 1;
+ } else {
+ base_path = GLOBAL_GET("debug/file_logging/log_path");
+ max_files = GLOBAL_GET("debug/file_logging/max_log_files");
+ }
OS::get_singleton()->add_logger(memnew(RotatedFileLogger(base_path, max_files)));
}
@@ -2205,7 +2229,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "xr/openxr/default_action_map", PROPERTY_HINT_FILE, "*.tres"), "res://openxr_action_map.tres");
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/form_factor", PROPERTY_HINT_ENUM, "Head Mounted,Handheld"), "0");
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/view_configuration", PROPERTY_HINT_ENUM, "Mono,Stereo"), "1"); // "Mono,Stereo,Quad,Observer"
- GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/reference_space", PROPERTY_HINT_ENUM, "Local,Stage"), "1");
+ GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/reference_space", PROPERTY_HINT_ENUM, "Local,Stage,Local Floor"), "1");
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/environment_blend_mode", PROPERTY_HINT_ENUM, "Opaque,Additive,Alpha"), "0");
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/foveation_level", PROPERTY_HINT_ENUM, "Off,Low,Medium,High"), "0");
GLOBAL_DEF_BASIC("xr/openxr/foveation_dynamic", false);
diff --git a/misc/dist/shell/_godot.zsh-completion b/misc/dist/shell/_godot.zsh-completion
index 490af0a8a5..f65cf37870 100644
--- a/misc/dist/shell/_godot.zsh-completion
+++ b/misc/dist/shell/_godot.zsh-completion
@@ -51,7 +51,8 @@ _arguments \
'--text-driver[set the text driver]:text driver name' \
'--tablet-driver[set the pen tablet input driver]:tablet driver name' \
'--headless[enable headless mode (--display-driver headless --audio-driver Dummy), useful for servers and with --script]' \
- '--write-movie[writes a video to the specified path (usually with .avi or .png extension)]:path to output video file' \
+ '--log-file[write output/error log to the specified path instead of the default location defined by the project]:path to output log file' \
+ '--write-movie[write a video to the specified path (usually with .avi or .png extension)]:path to output video file' \
'(-f --fullscreen)'{-f,--fullscreen}'[request fullscreen mode]' \
'(-m --maximized)'{-m,--maximized}'[request a maximized window]' \
'(-w --windowed)'{-w,--windowed}'[request windowed mode]' \
diff --git a/misc/dist/shell/godot.bash-completion b/misc/dist/shell/godot.bash-completion
index c78f0a1f33..63efa95c10 100644
--- a/misc/dist/shell/godot.bash-completion
+++ b/misc/dist/shell/godot.bash-completion
@@ -54,6 +54,7 @@ _complete_godot_options() {
--text-driver
--tablet-driver
--headless
+--log-file
--write-movie
--fullscreen
--maximized
diff --git a/misc/dist/shell/godot.fish b/misc/dist/shell/godot.fish
index fbfa7344f1..3f0675fcb2 100644
--- a/misc/dist/shell/godot.fish
+++ b/misc/dist/shell/godot.fish
@@ -67,7 +67,8 @@ complete -c godot -l gpu-index -d "Use a specific GPU (run with --verbose to get
complete -c godot -l text-driver -d "Set the text driver" -x
complete -c godot -l tablet-driver -d "Set the pen tablet input driver" -x
complete -c godot -l headless -d "Enable headless mode (--display-driver headless --audio-driver Dummy). Useful for servers and with --script"
-complete -c godot -l write-movie -d "Writes a video to the specified path (usually with .avi or .png extension). --fixed-fps is forced when enabled" -x
+complete -c godot -l log-file -d "Write output/error log to the specified path instead of the default location defined by the project" -x
+complete -c godot -l write-movie -d "Write a video to the specified path (usually with .avi or .png extension). --fixed-fps is forced when enabled" -x
# Display options:
complete -c godot -s f -l fullscreen -d "Request fullscreen mode"
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 7026d131e3..dd9e49fb8d 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -5326,8 +5326,21 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator
return result;
}
-// TODO: Add safe/unsafe return variable (for variant cases)
bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion, const GDScriptParser::Node *p_source_node) {
+#ifdef DEBUG_ENABLED
+ if (p_source_node) {
+ if (p_target.kind == GDScriptParser::DataType::ENUM) {
+ if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) {
+ parser->push_warning(p_source_node, GDScriptWarning::INT_AS_ENUM_WITHOUT_CAST);
+ }
+ }
+ }
+#endif
+ return check_type_compatibility(p_target, p_source, p_allow_implicit_conversion, p_source_node);
+}
+
+// TODO: Add safe/unsafe return variable (for variant cases)
+bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion, const GDScriptParser::Node *p_source_node) {
// These return "true" so it doesn't affect users negatively.
ERR_FAIL_COND_V_MSG(!p_target.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset target type");
ERR_FAIL_COND_V_MSG(!p_source.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset value type");
@@ -5362,11 +5375,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
if (p_target.kind == GDScriptParser::DataType::ENUM) {
if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) {
-#ifdef DEBUG_ENABLED
- if (p_source_node) {
- parser->push_warning(p_source_node, GDScriptWarning::INT_AS_ENUM_WITHOUT_CAST);
- }
-#endif
return true;
}
if (p_source.kind == GDScriptParser::DataType::ENUM) {
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 4ed476a3df..e398ccfdbb 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -147,6 +147,7 @@ public:
Variant make_variable_default_value(GDScriptParser::VariableNode *p_variable);
const HashMap<String, Ref<GDScriptParserRef>> &get_depended_parsers();
+ static bool check_type_compatibility(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr);
GDScriptAnalyzer(GDScriptParser *p_parser);
};
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 210e2c3898..b259f93ac1 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -2034,6 +2034,21 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
default:
break;
}
+ } else {
+ if (p_context.current_class) {
+ GDScriptCompletionIdentifier base_identifier;
+
+ GDScriptCompletionIdentifier base;
+ base.value = p_context.base;
+ base.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ base.type.kind = GDScriptParser::DataType::CLASS;
+ base.type.class_type = p_context.current_class;
+ base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static;
+
+ if (_guess_identifier_type_from_base(p_context, base, p_identifier->name, base_identifier)) {
+ id_type = base_identifier.type;
+ }
+ }
}
while (suite) {
@@ -2087,8 +2102,15 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
if (last_assigned_expression && last_assign_line < p_context.current_line) {
GDScriptParser::CompletionContext c = p_context;
c.current_line = last_assign_line;
- r_type.assigned_expression = last_assigned_expression;
- if (_guess_expression_type(c, last_assigned_expression, r_type)) {
+ GDScriptCompletionIdentifier assigned_type;
+ if (_guess_expression_type(c, last_assigned_expression, assigned_type)) {
+ if (id_type.is_set() && assigned_type.type.is_set() && !GDScriptAnalyzer::check_type_compatibility(id_type, assigned_type.type)) {
+ // The assigned type is incompatible. The annotated type takes priority.
+ r_type.assigned_expression = last_assigned_expression;
+ r_type.type = id_type;
+ } else {
+ r_type = assigned_type;
+ }
return true;
}
}
@@ -2146,20 +2168,6 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
return true;
}
- // Check current class (including inheritance).
- if (p_context.current_class) {
- GDScriptCompletionIdentifier base;
- base.value = p_context.base;
- base.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- base.type.kind = GDScriptParser::DataType::CLASS;
- base.type.class_type = p_context.current_class;
- base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static;
-
- if (_guess_identifier_type_from_base(p_context, base, p_identifier->name, r_type)) {
- return true;
- }
- }
-
// Check global scripts.
if (ScriptServer::is_global_class(p_identifier->name)) {
String script = ScriptServer::get_global_class_path(p_identifier->name);
diff --git a/modules/gltf/doc_classes/GLTFPhysicsBody.xml b/modules/gltf/doc_classes/GLTFPhysicsBody.xml
index cf39721ce8..ca66cd54b0 100644
--- a/modules/gltf/doc_classes/GLTFPhysicsBody.xml
+++ b/modules/gltf/doc_classes/GLTFPhysicsBody.xml
@@ -4,7 +4,7 @@
Represents a GLTF physics body.
</brief_description>
<description>
- Represents a physics body as defined by the [code]OMI_physics_body[/code] GLTF extension. This class is an intermediary between the GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future.
+ Represents a physics body as an intermediary between the [code]OMI_physics_body[/code] GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future.
</description>
<tutorials>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
@@ -15,7 +15,7 @@
<return type="GLTFPhysicsBody" />
<param index="0" name="dictionary" type="Dictionary" />
<description>
- Creates a new GLTFPhysicsBody instance by parsing the given [Dictionary].
+ Creates a new GLTFPhysicsBody instance by parsing the given [Dictionary] in the [code]OMI_physics_body[/code] GLTF extension format.
</description>
</method>
<method name="from_node" qualifiers="static">
@@ -28,7 +28,7 @@
<method name="to_dictionary" qualifiers="const">
<return type="Dictionary" />
<description>
- Serializes this GLTFPhysicsBody instance into a [Dictionary].
+ Serializes this GLTFPhysicsBody instance into a [Dictionary]. It will be in the format expected by the [code]OMI_physics_body[/code] GLTF extension.
</description>
</method>
<method name="to_node" qualifiers="const">
@@ -42,13 +42,20 @@
<member name="angular_velocity" type="Vector3" setter="set_angular_velocity" getter="get_angular_velocity" default="Vector3(0, 0, 0)">
The angular velocity of the physics body, in radians per second. This is only used when the body type is "rigid" or "vehicle".
</member>
- <member name="body_type" type="String" setter="set_body_type" getter="get_body_type" default="&quot;static&quot;">
- The type of the body. When importing, this controls what type of [CollisionObject3D] node Godot should generate. Valid values are "static", "kinematic", "character", "rigid", "vehicle", and "trigger".
+ <member name="body_type" type="String" setter="set_body_type" getter="get_body_type" default="&quot;rigid&quot;">
+ The type of the body. When importing, this controls what type of [CollisionObject3D] node Godot should generate. Valid values are "static", "animatable", "character", "rigid", "vehicle", and "trigger". When exporting, this will be squashed down to one of "static", "kinematic", or "dynamic" motion types, or the "trigger" property.
</member>
<member name="center_of_mass" type="Vector3" setter="set_center_of_mass" getter="get_center_of_mass" default="Vector3(0, 0, 0)">
The center of mass of the body, in meters. This is in local space relative to the body. By default, the center of the mass is the body's origin.
</member>
- <member name="inertia_tensor" type="Basis" setter="set_inertia_tensor" getter="get_inertia_tensor" default="Basis(0, 0, 0, 0, 0, 0, 0, 0, 0)">
+ <member name="inertia_diagonal" type="Vector3" setter="set_inertia_diagonal" getter="get_inertia_diagonal" default="Vector3(0, 0, 0)">
+ The inertia strength of the physics body, in kilogram meter squared (kg⋅m²). This represents the inertia around the principle axes, the diagonal of the inertia tensor matrix. This is only used when the body type is "rigid" or "vehicle".
+ When converted to a Godot [RigidBody3D] node, if this value is zero, then the inertia will be calculated automatically.
+ </member>
+ <member name="inertia_orientation" type="Quaternion" setter="set_inertia_orientation" getter="get_inertia_orientation" default="Quaternion(0, 0, 0, 1)">
+ The inertia orientation of the physics body. This defines the rotation of the inertia's principle axes relative to the object's local axes. This is only used when the body type is "rigid" or "vehicle" and [member inertia_diagonal] is set to a non-zero value.
+ </member>
+ <member name="inertia_tensor" type="Basis" setter="set_inertia_tensor" getter="get_inertia_tensor" default="Basis(0, 0, 0, 0, 0, 0, 0, 0, 0)" is_deprecated="true">
The inertia tensor of the physics body, in kilogram meter squared (kg⋅m²). This is only used when the body type is "rigid" or "vehicle".
When converted to a Godot [RigidBody3D] node, if this value is zero, then the inertia will be calculated automatically.
</member>
diff --git a/modules/gltf/doc_classes/GLTFPhysicsShape.xml b/modules/gltf/doc_classes/GLTFPhysicsShape.xml
index 67382f3295..c397c660d9 100644
--- a/modules/gltf/doc_classes/GLTFPhysicsShape.xml
+++ b/modules/gltf/doc_classes/GLTFPhysicsShape.xml
@@ -4,11 +4,12 @@
Represents a GLTF physics shape.
</brief_description>
<description>
- Represents a physics shape as defined by the [code]OMI_collider[/code] GLTF extension. This class is an intermediary between the GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future.
+ Represents a physics shape as defined by the [code]OMI_physics_shape[/code] or [code]OMI_collider[/code] GLTF extensions. This class is an intermediary between the GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future.
</description>
<tutorials>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
- <link title="OMI_collider GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_collider</link>
+ <link title="OMI_physics_shape GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_shape</link>
+ <link title="OMI_collider GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/Archived/OMI_collider</link>
</tutorials>
<methods>
<method name="from_dictionary" qualifiers="static">
@@ -28,7 +29,7 @@
<method name="to_dictionary" qualifiers="const">
<return type="Dictionary" />
<description>
- Serializes this GLTFPhysicsShape instance into a [Dictionary].
+ Serializes this GLTFPhysicsShape instance into a [Dictionary] in the format defined by [code]OMI_physics_shape[/code].
</description>
</method>
<method name="to_node">
diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
index 2ba5123c31..37b8ae0634 100644
--- a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
+++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
@@ -34,13 +34,26 @@
// Import process.
Error GLTFDocumentExtensionPhysics::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) {
- if (!p_extensions.has("OMI_collider") && !p_extensions.has("OMI_physics_body")) {
+ if (!p_extensions.has("OMI_collider") && !p_extensions.has("OMI_physics_body") && !p_extensions.has("OMI_physics_shape")) {
return ERR_SKIP;
}
Dictionary state_json = p_state->get_json();
if (state_json.has("extensions")) {
Dictionary state_extensions = state_json["extensions"];
- if (state_extensions.has("OMI_collider")) {
+ if (state_extensions.has("OMI_physics_shape")) {
+ Dictionary omi_physics_shape_ext = state_extensions["OMI_physics_shape"];
+ if (omi_physics_shape_ext.has("shapes")) {
+ Array state_shape_dicts = omi_physics_shape_ext["shapes"];
+ if (state_shape_dicts.size() > 0) {
+ Array state_shapes;
+ for (int i = 0; i < state_shape_dicts.size(); i++) {
+ state_shapes.push_back(GLTFPhysicsShape::from_dictionary(state_shape_dicts[i]));
+ }
+ p_state->set_additional_data(StringName("GLTFPhysicsShapes"), state_shapes);
+ }
+ }
+#ifndef DISABLE_DEPRECATED
+ } else if (state_extensions.has("OMI_collider")) {
Dictionary omi_collider_ext = state_extensions["OMI_collider"];
if (omi_collider_ext.has("colliders")) {
Array state_collider_dicts = omi_collider_ext["colliders"];
@@ -49,9 +62,10 @@ Error GLTFDocumentExtensionPhysics::import_preflight(Ref<GLTFState> p_state, Vec
for (int i = 0; i < state_collider_dicts.size(); i++) {
state_colliders.push_back(GLTFPhysicsShape::from_dictionary(state_collider_dicts[i]));
}
- p_state->set_additional_data("GLTFPhysicsShapes", state_colliders);
+ p_state->set_additional_data(StringName("GLTFPhysicsShapes"), state_colliders);
}
}
+#endif // DISABLE_DEPRECATED
}
}
return OK;
@@ -61,49 +75,87 @@ Vector<String> GLTFDocumentExtensionPhysics::get_supported_extensions() {
Vector<String> ret;
ret.push_back("OMI_collider");
ret.push_back("OMI_physics_body");
+ ret.push_back("OMI_physics_shape");
return ret;
}
Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions) {
+#ifndef DISABLE_DEPRECATED
if (p_extensions.has("OMI_collider")) {
Dictionary node_collider_ext = p_extensions["OMI_collider"];
if (node_collider_ext.has("collider")) {
// "collider" is the index of the collider in the state colliders array.
int node_collider_index = node_collider_ext["collider"];
- Array state_colliders = p_state->get_additional_data("GLTFPhysicsShapes");
+ Array state_colliders = p_state->get_additional_data(StringName("GLTFPhysicsShapes"));
ERR_FAIL_INDEX_V_MSG(node_collider_index, state_colliders.size(), Error::ERR_FILE_CORRUPT, "GLTF Physics: On node " + p_gltf_node->get_name() + ", the collider index " + itos(node_collider_index) + " is not in the state colliders (size: " + itos(state_colliders.size()) + ").");
p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), state_colliders[node_collider_index]);
} else {
- p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), GLTFPhysicsShape::from_dictionary(p_extensions["OMI_collider"]));
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), GLTFPhysicsShape::from_dictionary(node_collider_ext));
}
}
+#endif // DISABLE_DEPRECATED
if (p_extensions.has("OMI_physics_body")) {
- p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), GLTFPhysicsBody::from_dictionary(p_extensions["OMI_physics_body"]));
+ Dictionary physics_body_ext = p_extensions["OMI_physics_body"];
+ if (physics_body_ext.has("collider")) {
+ Dictionary node_collider = physics_body_ext["collider"];
+ // "shape" is the index of the shape in the state shapes array.
+ int node_shape_index = node_collider.get("shape", -1);
+ if (node_shape_index != -1) {
+ Array state_shapes = p_state->get_additional_data(StringName("GLTFPhysicsShapes"));
+ ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "GLTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ").");
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShape"), state_shapes[node_shape_index]);
+ } else {
+ // If this node is a collider but does not have a collider
+ // shape, then it only serves to combine together shapes.
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsCompoundCollider"), true);
+ }
+ }
+ if (physics_body_ext.has("trigger")) {
+ Dictionary node_trigger = physics_body_ext["trigger"];
+ // "shape" is the index of the shape in the state shapes array.
+ int node_shape_index = node_trigger.get("shape", -1);
+ if (node_shape_index != -1) {
+ Array state_shapes = p_state->get_additional_data(StringName("GLTFPhysicsShapes"));
+ ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "GLTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ").");
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShape"), state_shapes[node_shape_index]);
+ } else {
+ // If this node is a trigger but does not have a trigger shape,
+ // then it's a trigger body, what Godot calls an Area3D node.
+ Ref<GLTFPhysicsBody> trigger_body;
+ trigger_body.instantiate();
+ trigger_body->set_body_type("trigger");
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), trigger_body);
+ }
+ }
+ if (physics_body_ext.has("motion") || physics_body_ext.has("type")) {
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), GLTFPhysicsBody::from_dictionary(physics_body_ext));
+ }
}
return OK;
}
-void _setup_collider_mesh_resource_from_index_if_needed(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_collider) {
- GLTFMeshIndex collider_mesh_index = p_collider->get_mesh_index();
- if (collider_mesh_index == -1) {
- return; // No mesh for this collider.
+void _setup_shape_mesh_resource_from_index_if_needed(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_gltf_shape) {
+ GLTFMeshIndex shape_mesh_index = p_gltf_shape->get_mesh_index();
+ if (shape_mesh_index == -1) {
+ return; // No mesh for this shape.
}
- Ref<ImporterMesh> importer_mesh = p_collider->get_importer_mesh();
+ Ref<ImporterMesh> importer_mesh = p_gltf_shape->get_importer_mesh();
if (importer_mesh.is_valid()) {
return; // The mesh resource is already set up.
}
TypedArray<GLTFMesh> state_meshes = p_state->get_meshes();
- ERR_FAIL_INDEX_MSG(collider_mesh_index, state_meshes.size(), "GLTF Physics: When importing '" + p_state->get_scene_name() + "', the collider mesh index " + itos(collider_mesh_index) + " is not in the state meshes (size: " + itos(state_meshes.size()) + ").");
- Ref<GLTFMesh> gltf_mesh = state_meshes[collider_mesh_index];
+ ERR_FAIL_INDEX_MSG(shape_mesh_index, state_meshes.size(), "GLTF Physics: When importing '" + p_state->get_scene_name() + "', the shape mesh index " + itos(shape_mesh_index) + " is not in the state meshes (size: " + itos(state_meshes.size()) + ").");
+ Ref<GLTFMesh> gltf_mesh = state_meshes[shape_mesh_index];
ERR_FAIL_COND(gltf_mesh.is_null());
importer_mesh = gltf_mesh->get_mesh();
ERR_FAIL_COND(importer_mesh.is_null());
- p_collider->set_importer_mesh(importer_mesh);
+ p_gltf_shape->set_importer_mesh(importer_mesh);
}
-CollisionObject3D *_generate_collision_with_body(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Ref<GLTFPhysicsShape> p_collider, Ref<GLTFPhysicsBody> p_physics_body) {
- print_verbose("glTF: Creating collision for: " + p_gltf_node->get_name());
- bool is_trigger = p_collider->get_is_trigger();
+#ifndef DISABLE_DEPRECATED
+CollisionObject3D *_generate_shape_with_body(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Ref<GLTFPhysicsShape> p_physics_shape, Ref<GLTFPhysicsBody> p_physics_body) {
+ print_verbose("glTF: Creating shape with body for: " + p_gltf_node->get_name());
+ bool is_trigger = p_physics_shape->get_is_trigger();
// This method is used for the case where we must generate a parent body.
// This is can happen for multiple reasons. One possibility is that this
// GLTF file is using OMI_collider but not OMI_physics_body, or at least
@@ -113,10 +165,10 @@ CollisionObject3D *_generate_collision_with_body(Ref<GLTFState> p_state, Ref<GLT
if (p_physics_body.is_valid()) {
// This code is run when the physics body is on the same GLTF node.
body = p_physics_body->to_node();
- if (is_trigger != (p_physics_body->get_body_type() == "trigger")) {
+ if (is_trigger && (p_physics_body->get_body_type() != "trigger")) {
// Edge case: If the body's trigger and the collider's trigger
// are in disagreement, we need to create another new body.
- CollisionObject3D *child = _generate_collision_with_body(p_state, p_gltf_node, p_collider, nullptr);
+ CollisionObject3D *child = _generate_shape_with_body(p_state, p_gltf_node, p_physics_shape, nullptr);
child->set_name(p_gltf_node->get_name() + (is_trigger ? String("Trigger") : String("Solid")));
body->add_child(child);
return body;
@@ -126,33 +178,131 @@ CollisionObject3D *_generate_collision_with_body(Ref<GLTFState> p_state, Ref<GLT
} else {
body = memnew(StaticBody3D);
}
- CollisionShape3D *shape = p_collider->to_node();
+ CollisionShape3D *shape = p_physics_shape->to_node();
shape->set_name(p_gltf_node->get_name() + "Shape");
body->add_child(shape);
return body;
}
+#endif // DISABLE_DEPRECATED
+
+CollisionObject3D *_get_ancestor_collision_object(Node *p_scene_parent) {
+ // Note: Despite the name of the method, at the moment this only checks
+ // the direct parent. Only check more later if Godot adds support for it.
+ if (p_scene_parent) {
+ CollisionObject3D *co = Object::cast_to<CollisionObject3D>(p_scene_parent);
+ if (likely(co)) {
+ return co;
+ }
+ }
+ return nullptr;
+}
+
+Node3D *_generate_shape_node_and_body_if_needed(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Ref<GLTFPhysicsShape> p_physics_shape, CollisionObject3D *p_col_object, bool p_is_trigger) {
+ // If we need to generate a body node, do so.
+ CollisionObject3D *body_node = nullptr;
+ if (p_is_trigger || p_physics_shape->get_is_trigger()) {
+ // If the shape wants to be a trigger but it doesn't
+ // have an Area3D parent, we need to make one.
+ if (!Object::cast_to<Area3D>(p_col_object)) {
+ body_node = memnew(Area3D);
+ }
+ } else {
+ if (!Object::cast_to<PhysicsBody3D>(p_col_object)) {
+ body_node = memnew(StaticBody3D);
+ }
+ }
+ // Generate the shape node.
+ _setup_shape_mesh_resource_from_index_if_needed(p_state, p_physics_shape);
+ CollisionShape3D *shape_node = p_physics_shape->to_node(true);
+ if (body_node) {
+ shape_node->set_name(p_gltf_node->get_name() + "Shape");
+ body_node->add_child(shape_node);
+ return body_node;
+ }
+ return shape_node;
+}
+
+// Either add the child to the parent, or return the child if there is no parent.
+Node3D *_add_physics_node_to_given_node(Node3D *p_current_node, Node3D *p_child, Ref<GLTFNode> p_gltf_node) {
+ if (!p_current_node) {
+ return p_child;
+ }
+ String suffix;
+ if (Object::cast_to<CollisionShape3D>(p_child)) {
+ suffix = "Shape";
+ } else if (Object::cast_to<Area3D>(p_child)) {
+ suffix = "Trigger";
+ } else {
+ suffix = "Collider";
+ }
+ p_child->set_name(p_gltf_node->get_name() + suffix);
+ p_current_node->add_child(p_child);
+ return p_current_node;
+}
Node3D *GLTFDocumentExtensionPhysics::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) {
- Ref<GLTFPhysicsBody> physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody"));
- Ref<GLTFPhysicsShape> collider = p_gltf_node->get_additional_data(StringName("GLTFPhysicsShape"));
- if (collider.is_valid()) {
- _setup_collider_mesh_resource_from_index_if_needed(p_state, collider);
- // If the collider has the correct type of parent, we just return one node.
- if (collider->get_is_trigger()) {
- if (Object::cast_to<Area3D>(p_scene_parent)) {
- return collider->to_node(true);
+ Ref<GLTFPhysicsBody> gltf_physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody"));
+#ifndef DISABLE_DEPRECATED
+ // This deprecated code handles OMI_collider (which we internally name "GLTFPhysicsShape").
+ Ref<GLTFPhysicsShape> gltf_physics_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsShape"));
+ if (gltf_physics_shape.is_valid()) {
+ _setup_shape_mesh_resource_from_index_if_needed(p_state, gltf_physics_shape);
+ // If this GLTF node specifies both a shape and a body, generate both.
+ if (gltf_physics_body.is_valid()) {
+ return _generate_shape_with_body(p_state, p_gltf_node, gltf_physics_shape, gltf_physics_body);
+ }
+ CollisionObject3D *ancestor_col_obj = _get_ancestor_collision_object(p_scene_parent);
+ if (gltf_physics_shape->get_is_trigger()) {
+ // If the shape wants to be a trigger and it already has a
+ // trigger parent, we only need to make the shape node.
+ if (Object::cast_to<Area3D>(ancestor_col_obj)) {
+ return gltf_physics_shape->to_node(true);
}
- } else {
- if (Object::cast_to<PhysicsBody3D>(p_scene_parent)) {
- return collider->to_node(true);
+ } else if (ancestor_col_obj != nullptr) {
+ // If the shape has a valid parent, only make the shape node.
+ return gltf_physics_shape->to_node(true);
+ }
+ // Otherwise, we need to create a new body.
+ return _generate_shape_with_body(p_state, p_gltf_node, gltf_physics_shape, nullptr);
+ }
+#endif // DISABLE_DEPRECATED
+ Node3D *ret = nullptr;
+ CollisionObject3D *ancestor_col_obj = nullptr;
+ if (gltf_physics_body.is_valid()) {
+ ancestor_col_obj = gltf_physics_body->to_node();
+ ret = ancestor_col_obj;
+ } else {
+ ancestor_col_obj = _get_ancestor_collision_object(p_scene_parent);
+ if (!Object::cast_to<PhysicsBody3D>(ancestor_col_obj)) {
+ if (p_gltf_node->get_additional_data(StringName("GLTFPhysicsCompoundCollider"))) {
+ // If the GLTF file wants this node to group solid shapes together,
+ // and there is no parent body, we need to create a static body.
+ ancestor_col_obj = memnew(StaticBody3D);
+ ret = ancestor_col_obj;
}
}
- return _generate_collision_with_body(p_state, p_gltf_node, collider, physics_body);
}
- if (physics_body.is_valid()) {
- return physics_body->to_node();
+ // Add the shapes to the tree. When an ancestor body is present, use it.
+ // If an explicit body was specified, it has already been generated and
+ // set above. If there is no ancestor body, we will either generate an
+ // Area3D or StaticBody3D implicitly, so prefer an Area3D as the base
+ // node for best compatibility with signal connections to this node.
+ Ref<GLTFPhysicsShape> gltf_physics_collider_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape"));
+ Ref<GLTFPhysicsShape> gltf_physics_trigger_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape"));
+ bool is_ancestor_col_obj_solid = Object::cast_to<PhysicsBody3D>(ancestor_col_obj);
+ if (is_ancestor_col_obj_solid && gltf_physics_collider_shape.is_valid()) {
+ Node3D *child = _generate_shape_node_and_body_if_needed(p_state, p_gltf_node, gltf_physics_collider_shape, ancestor_col_obj, false);
+ ret = _add_physics_node_to_given_node(ret, child, p_gltf_node);
}
- return nullptr;
+ if (gltf_physics_trigger_shape.is_valid()) {
+ Node3D *child = _generate_shape_node_and_body_if_needed(p_state, p_gltf_node, gltf_physics_trigger_shape, ancestor_col_obj, true);
+ ret = _add_physics_node_to_given_node(ret, child, p_gltf_node);
+ }
+ if (!is_ancestor_col_obj_solid && gltf_physics_collider_shape.is_valid()) {
+ Node3D *child = _generate_shape_node_and_body_if_needed(p_state, p_gltf_node, gltf_physics_collider_shape, ancestor_col_obj, false);
+ ret = _add_physics_node_to_given_node(ret, child, p_gltf_node);
+ }
+ return ret;
}
// Export process.
@@ -202,22 +352,26 @@ GLTFMeshIndex _get_or_insert_mesh_in_state(Ref<GLTFState> p_state, Ref<ImporterM
void GLTFDocumentExtensionPhysics::convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node) {
if (cast_to<CollisionShape3D>(p_scene_node)) {
- CollisionShape3D *shape = Object::cast_to<CollisionShape3D>(p_scene_node);
- Ref<GLTFPhysicsShape> collider = GLTFPhysicsShape::from_node(shape);
+ CollisionShape3D *godot_shape = Object::cast_to<CollisionShape3D>(p_scene_node);
+ Ref<GLTFPhysicsShape> gltf_shape = GLTFPhysicsShape::from_node(godot_shape);
{
- Ref<ImporterMesh> importer_mesh = collider->get_importer_mesh();
+ Ref<ImporterMesh> importer_mesh = gltf_shape->get_importer_mesh();
if (importer_mesh.is_valid()) {
- collider->set_mesh_index(_get_or_insert_mesh_in_state(p_state, importer_mesh));
+ gltf_shape->set_mesh_index(_get_or_insert_mesh_in_state(p_state, importer_mesh));
}
}
- p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), collider);
+ if (cast_to<Area3D>(_get_ancestor_collision_object(p_scene_node))) {
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShape"), gltf_shape);
+ } else {
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShape"), gltf_shape);
+ }
} else if (cast_to<CollisionObject3D>(p_scene_node)) {
- CollisionObject3D *body = Object::cast_to<CollisionObject3D>(p_scene_node);
- p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), GLTFPhysicsBody::from_node(body));
+ CollisionObject3D *godot_body = Object::cast_to<CollisionObject3D>(p_scene_node);
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), GLTFPhysicsBody::from_node(godot_body));
}
}
-Array _get_or_create_state_colliders_in_state(Ref<GLTFState> p_state) {
+Array _get_or_create_state_shapes_in_state(Ref<GLTFState> p_state) {
Dictionary state_json = p_state->get_json();
Dictionary state_extensions;
if (state_json.has("extensions")) {
@@ -225,48 +379,60 @@ Array _get_or_create_state_colliders_in_state(Ref<GLTFState> p_state) {
} else {
state_json["extensions"] = state_extensions;
}
- Dictionary omi_collider_ext;
- if (state_extensions.has("OMI_collider")) {
- omi_collider_ext = state_extensions["OMI_collider"];
+ Dictionary omi_physics_shape_ext;
+ if (state_extensions.has("OMI_physics_shape")) {
+ omi_physics_shape_ext = state_extensions["OMI_physics_shape"];
} else {
- state_extensions["OMI_collider"] = omi_collider_ext;
- p_state->add_used_extension("OMI_collider");
+ state_extensions["OMI_physics_shape"] = omi_physics_shape_ext;
+ p_state->add_used_extension("OMI_physics_shape");
}
- Array state_colliders;
- if (omi_collider_ext.has("colliders")) {
- state_colliders = omi_collider_ext["colliders"];
+ Array state_shapes;
+ if (omi_physics_shape_ext.has("shapes")) {
+ state_shapes = omi_physics_shape_ext["shapes"];
} else {
- omi_collider_ext["colliders"] = state_colliders;
+ omi_physics_shape_ext["shapes"] = state_shapes;
+ }
+ return state_shapes;
+}
+
+Dictionary _export_node_shape(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_physics_shape) {
+ Array state_shapes = _get_or_create_state_shapes_in_state(p_state);
+ int size = state_shapes.size();
+ Dictionary shape_property;
+ Dictionary shape_dict = p_physics_shape->to_dictionary();
+ for (int i = 0; i < size; i++) {
+ Dictionary other = state_shapes[i];
+ if (other == shape_dict) {
+ // De-duplication: If we already have an identical shape,
+ // set the shape index to the existing one and return.
+ shape_property["shape"] = i;
+ return shape_property;
+ }
}
- return state_colliders;
+ // If we don't have an identical shape, add it to the array.
+ state_shapes.push_back(shape_dict);
+ shape_property["shape"] = size;
+ return shape_property;
}
Error GLTFDocumentExtensionPhysics::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_node_json, Node *p_node) {
- Dictionary node_extensions = r_node_json["extensions"];
+ Dictionary physics_body_ext;
Ref<GLTFPhysicsBody> physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody"));
if (physics_body.is_valid()) {
- node_extensions["OMI_physics_body"] = physics_body->to_dictionary();
- p_state->add_used_extension("OMI_physics_body");
+ physics_body_ext = physics_body->to_dictionary();
}
- Ref<GLTFPhysicsShape> collider = p_gltf_node->get_additional_data(StringName("GLTFPhysicsShape"));
- if (collider.is_valid()) {
- Array state_colliders = _get_or_create_state_colliders_in_state(p_state);
- int size = state_colliders.size();
- Dictionary omi_collider_ext;
- node_extensions["OMI_collider"] = omi_collider_ext;
- Dictionary collider_dict = collider->to_dictionary();
- for (int i = 0; i < size; i++) {
- Dictionary other = state_colliders[i];
- if (other == collider_dict) {
- // De-duplication: If we already have an identical collider,
- // set the collider index to the existing one and return.
- omi_collider_ext["collider"] = i;
- return OK;
- }
- }
- // If we don't have an identical collider, add it to the array.
- state_colliders.push_back(collider_dict);
- omi_collider_ext["collider"] = size;
+ Ref<GLTFPhysicsShape> collider_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape"));
+ if (collider_shape.is_valid()) {
+ physics_body_ext["collider"] = _export_node_shape(p_state, collider_shape);
+ }
+ Ref<GLTFPhysicsShape> trigger_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape"));
+ if (trigger_shape.is_valid()) {
+ physics_body_ext["trigger"] = _export_node_shape(p_state, trigger_shape);
+ }
+ if (!physics_body_ext.is_empty()) {
+ Dictionary node_extensions = r_node_json["extensions"];
+ node_extensions["OMI_physics_body"] = physics_body_ext;
+ p_state->add_used_extension("OMI_physics_body");
}
return OK;
}
diff --git a/modules/gltf/extensions/physics/gltf_physics_body.cpp b/modules/gltf/extensions/physics/gltf_physics_body.cpp
index b80f4348c2..271bb9b332 100644
--- a/modules/gltf/extensions/physics/gltf_physics_body.cpp
+++ b/modules/gltf/extensions/physics/gltf_physics_body.cpp
@@ -50,22 +50,70 @@ void GLTFPhysicsBody::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_angular_velocity", "angular_velocity"), &GLTFPhysicsBody::set_angular_velocity);
ClassDB::bind_method(D_METHOD("get_center_of_mass"), &GLTFPhysicsBody::get_center_of_mass);
ClassDB::bind_method(D_METHOD("set_center_of_mass", "center_of_mass"), &GLTFPhysicsBody::set_center_of_mass);
+ ClassDB::bind_method(D_METHOD("get_inertia_diagonal"), &GLTFPhysicsBody::get_inertia_diagonal);
+ ClassDB::bind_method(D_METHOD("set_inertia_diagonal", "inertia_diagonal"), &GLTFPhysicsBody::set_inertia_diagonal);
+ ClassDB::bind_method(D_METHOD("get_inertia_orientation"), &GLTFPhysicsBody::get_inertia_orientation);
+ ClassDB::bind_method(D_METHOD("set_inertia_orientation", "inertia_orientation"), &GLTFPhysicsBody::set_inertia_orientation);
+#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("get_inertia_tensor"), &GLTFPhysicsBody::get_inertia_tensor);
ClassDB::bind_method(D_METHOD("set_inertia_tensor", "inertia_tensor"), &GLTFPhysicsBody::set_inertia_tensor);
+#endif // DISABLE_DEPRECATED
ADD_PROPERTY(PropertyInfo(Variant::STRING, "body_type"), "set_body_type", "get_body_type");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass"), "set_mass", "get_mass");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity"), "set_linear_velocity", "get_linear_velocity");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "angular_velocity"), "set_angular_velocity", "get_angular_velocity");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_of_mass"), "set_center_of_mass", "get_center_of_mass");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "inertia_diagonal"), "set_inertia_diagonal", "get_inertia_diagonal");
+ ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "inertia_orientation"), "set_inertia_orientation", "get_inertia_orientation");
+#ifndef DISABLE_DEPRECATED
ADD_PROPERTY(PropertyInfo(Variant::BASIS, "inertia_tensor"), "set_inertia_tensor", "get_inertia_tensor");
+#endif // DISABLE_DEPRECATED
}
String GLTFPhysicsBody::get_body_type() const {
- return body_type;
+ switch (body_type) {
+ case PhysicsBodyType::STATIC:
+ return "static";
+ case PhysicsBodyType::ANIMATABLE:
+ return "animatable";
+ case PhysicsBodyType::CHARACTER:
+ return "character";
+ case PhysicsBodyType::RIGID:
+ return "rigid";
+ case PhysicsBodyType::VEHICLE:
+ return "vehicle";
+ case PhysicsBodyType::TRIGGER:
+ return "trigger";
+ }
+ // Unreachable, the switch cases handle all values the enum can take.
+ // Omitting this works on Clang but not GCC or MSVC. If reached, it's UB.
+ return "rigid";
}
void GLTFPhysicsBody::set_body_type(String p_body_type) {
+ if (p_body_type == "static") {
+ body_type = PhysicsBodyType::STATIC;
+ } else if (p_body_type == "animatable") {
+ body_type = PhysicsBodyType::ANIMATABLE;
+ } else if (p_body_type == "character") {
+ body_type = PhysicsBodyType::CHARACTER;
+ } else if (p_body_type == "rigid") {
+ body_type = PhysicsBodyType::RIGID;
+ } else if (p_body_type == "vehicle") {
+ body_type = PhysicsBodyType::VEHICLE;
+ } else if (p_body_type == "trigger") {
+ body_type = PhysicsBodyType::TRIGGER;
+ } else {
+ ERR_PRINT("Error setting GLTF physics body type: The body type must be one of \"static\", \"animatable\", \"character\", \"rigid\", \"vehicle\", or \"trigger\".");
+ }
+}
+
+GLTFPhysicsBody::PhysicsBodyType GLTFPhysicsBody::get_physics_body_type() const {
+ return body_type;
+}
+
+void GLTFPhysicsBody::set_physics_body_type(PhysicsBodyType p_body_type) {
body_type = p_body_type;
}
@@ -101,140 +149,215 @@ void GLTFPhysicsBody::set_center_of_mass(const Vector3 &p_center_of_mass) {
center_of_mass = p_center_of_mass;
}
+Vector3 GLTFPhysicsBody::get_inertia_diagonal() const {
+ return inertia_diagonal;
+}
+
+void GLTFPhysicsBody::set_inertia_diagonal(const Vector3 &p_inertia_diagonal) {
+ inertia_diagonal = p_inertia_diagonal;
+}
+
+Quaternion GLTFPhysicsBody::get_inertia_orientation() const {
+ return inertia_orientation;
+}
+
+void GLTFPhysicsBody::set_inertia_orientation(const Quaternion &p_inertia_orientation) {
+ inertia_orientation = p_inertia_orientation;
+}
+
+#ifndef DISABLE_DEPRECATED
Basis GLTFPhysicsBody::get_inertia_tensor() const {
- return inertia_tensor;
+ return Basis::from_scale(inertia_diagonal);
}
void GLTFPhysicsBody::set_inertia_tensor(Basis p_inertia_tensor) {
- inertia_tensor = p_inertia_tensor;
+ inertia_diagonal = p_inertia_tensor.get_main_diagonal();
}
+#endif // DISABLE_DEPRECATED
Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_node(const CollisionObject3D *p_body_node) {
Ref<GLTFPhysicsBody> physics_body;
physics_body.instantiate();
ERR_FAIL_NULL_V_MSG(p_body_node, physics_body, "Tried to create a GLTFPhysicsBody from a CollisionObject3D node, but the given node was null.");
if (cast_to<CharacterBody3D>(p_body_node)) {
- physics_body->body_type = "character";
+ physics_body->body_type = PhysicsBodyType::CHARACTER;
} else if (cast_to<AnimatableBody3D>(p_body_node)) {
- physics_body->body_type = "kinematic";
+ physics_body->body_type = PhysicsBodyType::ANIMATABLE;
} else if (cast_to<RigidBody3D>(p_body_node)) {
const RigidBody3D *body = cast_to<const RigidBody3D>(p_body_node);
physics_body->mass = body->get_mass();
physics_body->linear_velocity = body->get_linear_velocity();
physics_body->angular_velocity = body->get_angular_velocity();
physics_body->center_of_mass = body->get_center_of_mass();
- Vector3 inertia_diagonal = body->get_inertia();
- physics_body->inertia_tensor = Basis(inertia_diagonal.x, 0, 0, 0, inertia_diagonal.y, 0, 0, 0, inertia_diagonal.z);
+ physics_body->inertia_diagonal = body->get_inertia();
if (body->get_center_of_mass() != Vector3()) {
WARN_PRINT("GLTFPhysicsBody: This rigid body has a center of mass offset from the origin, which will be ignored when exporting to GLTF.");
}
if (cast_to<VehicleBody3D>(p_body_node)) {
- physics_body->body_type = "vehicle";
+ physics_body->body_type = PhysicsBodyType::VEHICLE;
} else {
- physics_body->body_type = "rigid";
+ physics_body->body_type = PhysicsBodyType::RIGID;
}
} else if (cast_to<StaticBody3D>(p_body_node)) {
- physics_body->body_type = "static";
+ physics_body->body_type = PhysicsBodyType::STATIC;
} else if (cast_to<Area3D>(p_body_node)) {
- physics_body->body_type = "trigger";
+ physics_body->body_type = PhysicsBodyType::TRIGGER;
}
return physics_body;
}
CollisionObject3D *GLTFPhysicsBody::to_node() const {
- if (body_type == "character") {
- CharacterBody3D *body = memnew(CharacterBody3D);
- return body;
- }
- if (body_type == "kinematic") {
- AnimatableBody3D *body = memnew(AnimatableBody3D);
- return body;
- }
- if (body_type == "vehicle") {
- VehicleBody3D *body = memnew(VehicleBody3D);
- body->set_mass(mass);
- body->set_linear_velocity(linear_velocity);
- body->set_angular_velocity(angular_velocity);
- body->set_inertia(inertia_tensor.get_main_diagonal());
- body->set_center_of_mass_mode(RigidBody3D::CENTER_OF_MASS_MODE_CUSTOM);
- body->set_center_of_mass(center_of_mass);
- return body;
- }
- if (body_type == "rigid") {
- RigidBody3D *body = memnew(RigidBody3D);
- body->set_mass(mass);
- body->set_linear_velocity(linear_velocity);
- body->set_angular_velocity(angular_velocity);
- body->set_inertia(inertia_tensor.get_main_diagonal());
- body->set_center_of_mass_mode(RigidBody3D::CENTER_OF_MASS_MODE_CUSTOM);
- body->set_center_of_mass(center_of_mass);
- return body;
- }
- if (body_type == "static") {
- StaticBody3D *body = memnew(StaticBody3D);
- return body;
- }
- if (body_type == "trigger") {
- Area3D *body = memnew(Area3D);
- return body;
+ switch (body_type) {
+ case PhysicsBodyType::CHARACTER: {
+ CharacterBody3D *body = memnew(CharacterBody3D);
+ return body;
+ }
+ case PhysicsBodyType::ANIMATABLE: {
+ AnimatableBody3D *body = memnew(AnimatableBody3D);
+ return body;
+ }
+ case PhysicsBodyType::VEHICLE: {
+ VehicleBody3D *body = memnew(VehicleBody3D);
+ body->set_mass(mass);
+ body->set_linear_velocity(linear_velocity);
+ body->set_angular_velocity(angular_velocity);
+ body->set_inertia(inertia_diagonal);
+ body->set_center_of_mass_mode(RigidBody3D::CENTER_OF_MASS_MODE_CUSTOM);
+ body->set_center_of_mass(center_of_mass);
+ return body;
+ }
+ case PhysicsBodyType::RIGID: {
+ RigidBody3D *body = memnew(RigidBody3D);
+ body->set_mass(mass);
+ body->set_linear_velocity(linear_velocity);
+ body->set_angular_velocity(angular_velocity);
+ body->set_inertia(inertia_diagonal);
+ body->set_center_of_mass_mode(RigidBody3D::CENTER_OF_MASS_MODE_CUSTOM);
+ body->set_center_of_mass(center_of_mass);
+ return body;
+ }
+ case PhysicsBodyType::STATIC: {
+ StaticBody3D *body = memnew(StaticBody3D);
+ return body;
+ }
+ case PhysicsBodyType::TRIGGER: {
+ Area3D *body = memnew(Area3D);
+ return body;
+ }
}
- ERR_FAIL_V_MSG(nullptr, "Error converting GLTFPhysicsBody to a node: Body type '" + body_type + "' is unknown.");
+ // Unreachable, the switch cases handle all values the enum can take.
+ // Omitting this works on Clang but not GCC or MSVC. If reached, it's UB.
+ return nullptr;
}
Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_dictionary) {
Ref<GLTFPhysicsBody> physics_body;
physics_body.instantiate();
- ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), physics_body, "Failed to parse GLTF physics body, missing required field 'type'.");
- const String &body_type = p_dictionary["type"];
- physics_body->body_type = body_type;
-
- if (p_dictionary.has("mass")) {
- physics_body->mass = p_dictionary["mass"];
+ Dictionary motion;
+ if (p_dictionary.has("motion")) {
+ motion = p_dictionary["motion"];
+#ifndef DISABLE_DEPRECATED
+ } else {
+ motion = p_dictionary;
+#endif // DISABLE_DEPRECATED
}
- if (p_dictionary.has("linearVelocity")) {
- const Array &arr = p_dictionary["linearVelocity"];
+ if (motion.has("type")) {
+ // Read the body type. This representation sits between glTF's and Godot's physics nodes.
+ // While we may only read "static", "kinematic", or "dynamic" from a valid glTF file, we
+ // want to allow another extension to override this to another Godot node type mid-import.
+ // For example, a vehicle extension may want to override the body type to "vehicle"
+ // so Godot generates a VehicleBody3D node. Therefore we distinguish by importing
+ // "dynamic" as "rigid", and "kinematic" as "animatable", in the GLTFPhysicsBody code.
+ String body_type_string = motion["type"];
+ if (body_type_string == "static") {
+ physics_body->body_type = PhysicsBodyType::STATIC;
+ } else if (body_type_string == "kinematic") {
+ physics_body->body_type = PhysicsBodyType::ANIMATABLE;
+ } else if (body_type_string == "dynamic") {
+ physics_body->body_type = PhysicsBodyType::RIGID;
+#ifndef DISABLE_DEPRECATED
+ } else if (body_type_string == "character") {
+ physics_body->body_type = PhysicsBodyType::CHARACTER;
+ } else if (body_type_string == "rigid") {
+ physics_body->body_type = PhysicsBodyType::RIGID;
+ } else if (body_type_string == "vehicle") {
+ physics_body->body_type = PhysicsBodyType::VEHICLE;
+ } else if (body_type_string == "trigger") {
+ physics_body->body_type = PhysicsBodyType::TRIGGER;
+#endif // DISABLE_DEPRECATED
+ } else {
+ ERR_PRINT("Error parsing GLTF physics body: The body type in the GLTF file \"" + body_type_string + "\" was not recognized.");
+ }
+ }
+ if (motion.has("mass")) {
+ physics_body->mass = motion["mass"];
+ }
+ if (motion.has("linearVelocity")) {
+ const Array &arr = motion["linearVelocity"];
if (arr.size() == 3) {
physics_body->set_linear_velocity(Vector3(arr[0], arr[1], arr[2]));
} else {
ERR_PRINT("Error parsing GLTF physics body: The linear velocity vector must have exactly 3 numbers.");
}
}
- if (p_dictionary.has("angularVelocity")) {
- const Array &arr = p_dictionary["angularVelocity"];
+ if (motion.has("angularVelocity")) {
+ const Array &arr = motion["angularVelocity"];
if (arr.size() == 3) {
physics_body->set_angular_velocity(Vector3(arr[0], arr[1], arr[2]));
} else {
ERR_PRINT("Error parsing GLTF physics body: The angular velocity vector must have exactly 3 numbers.");
}
}
- if (p_dictionary.has("centerOfMass")) {
- const Array &arr = p_dictionary["centerOfMass"];
+ if (motion.has("centerOfMass")) {
+ const Array &arr = motion["centerOfMass"];
if (arr.size() == 3) {
physics_body->set_center_of_mass(Vector3(arr[0], arr[1], arr[2]));
} else {
ERR_PRINT("Error parsing GLTF physics body: The center of mass vector must have exactly 3 numbers.");
}
}
- if (p_dictionary.has("inertiaTensor")) {
- const Array &arr = p_dictionary["inertiaTensor"];
- if (arr.size() == 9) {
- // Only use the diagonal elements of the inertia tensor matrix (principal axes).
- physics_body->set_inertia_tensor(Basis(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6], arr[7], arr[8]));
+ if (motion.has("inertiaDiagonal")) {
+ const Array &arr = motion["inertiaDiagonal"];
+ if (arr.size() == 3) {
+ physics_body->set_inertia_diagonal(Vector3(arr[0], arr[1], arr[2]));
} else {
- ERR_PRINT("Error parsing GLTF physics body: The inertia tensor must be a 3x3 matrix (9 number array).");
+ ERR_PRINT("Error parsing GLTF physics body: The inertia diagonal vector must have exactly 3 numbers.");
}
}
- if (body_type != "character" && body_type != "kinematic" && body_type != "rigid" && body_type != "static" && body_type != "trigger" && body_type != "vehicle") {
- ERR_PRINT("Error parsing GLTF physics body: Body type '" + body_type + "' is unknown.");
+ if (motion.has("inertiaOrientation")) {
+ const Array &arr = motion["inertiaOrientation"];
+ if (arr.size() == 4) {
+ physics_body->set_inertia_orientation(Quaternion(arr[0], arr[1], arr[2], arr[3]));
+ } else {
+ ERR_PRINT("Error parsing GLTF physics body: The inertia orientation quaternion must have exactly 4 numbers.");
+ }
}
return physics_body;
}
Dictionary GLTFPhysicsBody::to_dictionary() const {
- Dictionary d;
- d["type"] = body_type;
+ Dictionary ret;
+ if (body_type == PhysicsBodyType::TRIGGER) {
+ // The equivalent of a Godot Area3D node in glTF is a node that
+ // defines that it is a trigger, but does not have a shape.
+ Dictionary trigger;
+ ret["trigger"] = trigger;
+ return ret;
+ }
+ // All non-trigger body types are defined using the motion property.
+ Dictionary motion;
+ // When stored in memory, the body type can correspond to a Godot
+ // node type. However, when exporting to glTF, we need to squash
+ // this down to one of "static", "kinematic", or "dynamic".
+ if (body_type == PhysicsBodyType::STATIC) {
+ motion["type"] = "static";
+ } else if (body_type == PhysicsBodyType::ANIMATABLE || body_type == PhysicsBodyType::CHARACTER) {
+ motion["type"] = "kinematic";
+ } else {
+ motion["type"] = "dynamic";
+ }
if (mass != 1.0) {
- d["mass"] = mass;
+ motion["mass"] = mass;
}
if (linear_velocity != Vector3()) {
Array velocity_array;
@@ -242,7 +365,7 @@ Dictionary GLTFPhysicsBody::to_dictionary() const {
velocity_array[0] = linear_velocity.x;
velocity_array[1] = linear_velocity.y;
velocity_array[2] = linear_velocity.z;
- d["linearVelocity"] = velocity_array;
+ motion["linearVelocity"] = velocity_array;
}
if (angular_velocity != Vector3()) {
Array velocity_array;
@@ -250,7 +373,7 @@ Dictionary GLTFPhysicsBody::to_dictionary() const {
velocity_array[0] = angular_velocity.x;
velocity_array[1] = angular_velocity.y;
velocity_array[2] = angular_velocity.z;
- d["angularVelocity"] = velocity_array;
+ motion["angularVelocity"] = velocity_array;
}
if (center_of_mass != Vector3()) {
Array center_of_mass_array;
@@ -258,22 +381,25 @@ Dictionary GLTFPhysicsBody::to_dictionary() const {
center_of_mass_array[0] = center_of_mass.x;
center_of_mass_array[1] = center_of_mass.y;
center_of_mass_array[2] = center_of_mass.z;
- d["centerOfMass"] = center_of_mass_array;
+ motion["centerOfMass"] = center_of_mass_array;
+ }
+ if (inertia_diagonal != Vector3()) {
+ Array inertia_array;
+ inertia_array.resize(3);
+ inertia_array[0] = inertia_diagonal[0];
+ inertia_array[1] = inertia_diagonal[1];
+ inertia_array[2] = inertia_diagonal[2];
+ motion["inertiaDiagonal"] = inertia_array;
}
- if (inertia_tensor != Basis(0, 0, 0, 0, 0, 0, 0, 0, 0)) {
+ if (inertia_orientation != Quaternion()) {
Array inertia_array;
- inertia_array.resize(9);
- inertia_array.fill(0.0);
- inertia_array[0] = inertia_tensor[0][0];
- inertia_array[1] = inertia_tensor[0][1];
- inertia_array[2] = inertia_tensor[0][2];
- inertia_array[3] = inertia_tensor[1][0];
- inertia_array[4] = inertia_tensor[1][1];
- inertia_array[5] = inertia_tensor[1][2];
- inertia_array[6] = inertia_tensor[2][0];
- inertia_array[7] = inertia_tensor[2][1];
- inertia_array[8] = inertia_tensor[2][2];
- d["inertiaTensor"] = inertia_array;
+ inertia_array.resize(4);
+ inertia_array[0] = inertia_orientation[0];
+ inertia_array[1] = inertia_orientation[1];
+ inertia_array[2] = inertia_orientation[2];
+ inertia_array[3] = inertia_orientation[3];
+ motion["inertiaDiagonal"] = inertia_array;
}
- return d;
+ ret["motion"] = motion;
+ return ret;
}
diff --git a/modules/gltf/extensions/physics/gltf_physics_body.h b/modules/gltf/extensions/physics/gltf_physics_body.h
index 391b4b873f..6b21639a7b 100644
--- a/modules/gltf/extensions/physics/gltf_physics_body.h
+++ b/modules/gltf/extensions/physics/gltf_physics_body.h
@@ -33,27 +33,47 @@
#include "scene/3d/physics_body_3d.h"
-// GLTFPhysicsBody is an intermediary between OMI_physics_body and Godot's physics body nodes.
+// GLTFPhysicsBody is an intermediary between Godot's physics body nodes
+// and the OMI_physics_body extension.
// https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_body
class GLTFPhysicsBody : public Resource {
GDCLASS(GLTFPhysicsBody, Resource)
+public:
+ // These values map to Godot's physics body types.
+ // When importing, the body type will be set to the closest match, and
+ // user code can change this to make Godot generate a different node type.
+ // When exporting, this will be squashed down to one of "static",
+ // "kinematic", or "dynamic" motion types, or the "trigger" property.
+ enum class PhysicsBodyType {
+ STATIC,
+ ANIMATABLE,
+ CHARACTER,
+ RIGID,
+ VEHICLE,
+ TRIGGER,
+ };
+
protected:
static void _bind_methods();
private:
- String body_type = "static";
+ PhysicsBodyType body_type = PhysicsBodyType::RIGID;
real_t mass = 1.0;
Vector3 linear_velocity;
Vector3 angular_velocity;
Vector3 center_of_mass;
- Basis inertia_tensor = Basis(0, 0, 0, 0, 0, 0, 0, 0, 0);
+ Vector3 inertia_diagonal;
+ Quaternion inertia_orientation;
public:
String get_body_type() const;
void set_body_type(String p_body_type);
+ PhysicsBodyType get_physics_body_type() const;
+ void set_physics_body_type(PhysicsBodyType p_body_type);
+
real_t get_mass() const;
void set_mass(real_t p_mass);
@@ -66,8 +86,16 @@ public:
Vector3 get_center_of_mass() const;
void set_center_of_mass(const Vector3 &p_center_of_mass);
+ Vector3 get_inertia_diagonal() const;
+ void set_inertia_diagonal(const Vector3 &p_inertia_diagonal);
+
+ Quaternion get_inertia_orientation() const;
+ void set_inertia_orientation(const Quaternion &p_inertia_orientation);
+
+#ifndef DISABLE_DEPRECATED
Basis get_inertia_tensor() const;
void set_inertia_tensor(Basis p_inertia_tensor);
+#endif // DISABLE_DEPRECATED
static Ref<GLTFPhysicsBody> from_node(const CollisionObject3D *p_body_node);
CollisionObject3D *to_node() const;
diff --git a/modules/gltf/extensions/physics/gltf_physics_shape.cpp b/modules/gltf/extensions/physics/gltf_physics_shape.cpp
index d3c56c0da9..af4ac10313 100644
--- a/modules/gltf/extensions/physics/gltf_physics_shape.cpp
+++ b/modules/gltf/extensions/physics/gltf_physics_shape.cpp
@@ -129,16 +129,16 @@ void GLTFPhysicsShape::set_importer_mesh(Ref<ImporterMesh> p_importer_mesh) {
importer_mesh = p_importer_mesh;
}
-Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_node(const CollisionShape3D *p_collider_node) {
+Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_node(const CollisionShape3D *p_godot_shape_node) {
Ref<GLTFPhysicsShape> gltf_shape;
gltf_shape.instantiate();
- ERR_FAIL_NULL_V_MSG(p_collider_node, gltf_shape, "Tried to create a GLTFPhysicsShape from a CollisionShape3D node, but the given node was null.");
- Node *parent = p_collider_node->get_parent();
+ ERR_FAIL_NULL_V_MSG(p_godot_shape_node, gltf_shape, "Tried to create a GLTFPhysicsShape from a CollisionShape3D node, but the given node was null.");
+ Node *parent = p_godot_shape_node->get_parent();
if (cast_to<const Area3D>(parent)) {
gltf_shape->set_is_trigger(true);
}
// All the code for working with the shape is below this comment.
- Ref<Shape3D> shape_resource = p_collider_node->get_shape();
+ Ref<Shape3D> shape_resource = p_godot_shape_node->get_shape();
ERR_FAIL_COND_V_MSG(shape_resource.is_null(), gltf_shape, "Tried to create a GLTFPhysicsShape from a CollisionShape3D node, but the given node had a null shape.");
gltf_shape->_shape_cache = shape_resource;
if (cast_to<BoxShape3D>(shape_resource.ptr())) {
@@ -160,7 +160,7 @@ Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_node(const CollisionShape3D *p_coll
Ref<SphereShape3D> sphere = shape_resource;
gltf_shape->set_radius(sphere->get_radius());
} else if (cast_to<const ConvexPolygonShape3D>(shape_resource.ptr())) {
- gltf_shape->shape_type = "hull";
+ gltf_shape->shape_type = "convex";
Ref<ConvexPolygonShape3D> convex = shape_resource;
Vector<Vector3> hull_points = convex->get_points();
ERR_FAIL_COND_V_MSG(hull_points.size() < 3, gltf_shape, "GLTFPhysicsShape: Convex hull has fewer points (" + itos(hull_points.size()) + ") than the minimum of 3. At least 3 points are required in order to save to GLTF, since it uses a mesh to represent convex hulls.");
@@ -206,7 +206,7 @@ Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_node(const CollisionShape3D *p_coll
}
CollisionShape3D *GLTFPhysicsShape::to_node(bool p_cache_shapes) {
- CollisionShape3D *gltf_shape = memnew(CollisionShape3D);
+ CollisionShape3D *godot_shape_node = memnew(CollisionShape3D);
if (!p_cache_shapes || _shape_cache == nullptr) {
if (shape_type == "box") {
Ref<BoxShape3D> box;
@@ -230,80 +230,88 @@ CollisionShape3D *GLTFPhysicsShape::to_node(bool p_cache_shapes) {
sphere.instantiate();
sphere->set_radius(radius);
_shape_cache = sphere;
- } else if (shape_type == "hull") {
- ERR_FAIL_COND_V_MSG(importer_mesh.is_null(), gltf_shape, "GLTFPhysicsShape: Error converting convex hull shape to a node: The mesh resource is null.");
+ } else if (shape_type == "convex") {
+ ERR_FAIL_COND_V_MSG(importer_mesh.is_null(), godot_shape_node, "GLTFPhysicsShape: Error converting convex hull shape to a node: The mesh resource is null.");
Ref<ConvexPolygonShape3D> convex = importer_mesh->get_mesh()->create_convex_shape();
_shape_cache = convex;
} else if (shape_type == "trimesh") {
- ERR_FAIL_COND_V_MSG(importer_mesh.is_null(), gltf_shape, "GLTFPhysicsShape: Error converting concave mesh shape to a node: The mesh resource is null.");
+ ERR_FAIL_COND_V_MSG(importer_mesh.is_null(), godot_shape_node, "GLTFPhysicsShape: Error converting concave mesh shape to a node: The mesh resource is null.");
Ref<ConcavePolygonShape3D> concave = importer_mesh->create_trimesh_shape();
_shape_cache = concave;
} else {
ERR_PRINT("GLTFPhysicsShape: Error converting to a node: Shape type '" + shape_type + "' is unknown.");
}
}
- gltf_shape->set_shape(_shape_cache);
- return gltf_shape;
+ godot_shape_node->set_shape(_shape_cache);
+ return godot_shape_node;
}
Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_dictionary(const Dictionary p_dictionary) {
ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFPhysicsShape>(), "Failed to parse GLTFPhysicsShape, missing required field 'type'.");
Ref<GLTFPhysicsShape> gltf_shape;
gltf_shape.instantiate();
- const String &shape_type = p_dictionary["type"];
+ String shape_type = p_dictionary["type"];
+ if (shape_type == "hull") {
+ shape_type = "convex";
+ }
gltf_shape->shape_type = shape_type;
- if (shape_type != "box" && shape_type != "capsule" && shape_type != "cylinder" && shape_type != "sphere" && shape_type != "hull" && shape_type != "trimesh") {
- ERR_PRINT("GLTFPhysicsShape: Error parsing unknown shape type '" + shape_type + "'. Only box, capsule, cylinder, sphere, hull, and trimesh are supported.");
+ if (shape_type != "box" && shape_type != "capsule" && shape_type != "cylinder" && shape_type != "sphere" && shape_type != "convex" && shape_type != "trimesh") {
+ ERR_PRINT("GLTFPhysicsShape: Error parsing unknown shape type '" + shape_type + "'. Only box, capsule, cylinder, sphere, convex, and trimesh are supported.");
+ }
+ Dictionary properties;
+ if (p_dictionary.has(shape_type)) {
+ properties = p_dictionary[shape_type];
+ } else {
+ properties = p_dictionary;
}
- if (p_dictionary.has("radius")) {
- gltf_shape->set_radius(p_dictionary["radius"]);
+ if (properties.has("radius")) {
+ gltf_shape->set_radius(properties["radius"]);
}
- if (p_dictionary.has("height")) {
- gltf_shape->set_height(p_dictionary["height"]);
+ if (properties.has("height")) {
+ gltf_shape->set_height(properties["height"]);
}
- if (p_dictionary.has("size")) {
- const Array &arr = p_dictionary["size"];
+ if (properties.has("size")) {
+ const Array &arr = properties["size"];
if (arr.size() == 3) {
gltf_shape->set_size(Vector3(arr[0], arr[1], arr[2]));
} else {
ERR_PRINT("GLTFPhysicsShape: Error parsing the size, it must have exactly 3 numbers.");
}
}
- if (p_dictionary.has("isTrigger")) {
- gltf_shape->set_is_trigger(p_dictionary["isTrigger"]);
+ if (properties.has("isTrigger")) {
+ gltf_shape->set_is_trigger(properties["isTrigger"]);
}
- if (p_dictionary.has("mesh")) {
- gltf_shape->set_mesh_index(p_dictionary["mesh"]);
+ if (properties.has("mesh")) {
+ gltf_shape->set_mesh_index(properties["mesh"]);
}
- if (unlikely(gltf_shape->get_mesh_index() < 0 && (shape_type == "hull" || shape_type == "trimesh"))) {
+ if (unlikely(gltf_shape->get_mesh_index() < 0 && (shape_type == "convex" || shape_type == "trimesh"))) {
ERR_PRINT("Error parsing GLTFPhysicsShape: The mesh-based shape type '" + shape_type + "' does not have a valid mesh index.");
}
return gltf_shape;
}
Dictionary GLTFPhysicsShape::to_dictionary() const {
- Dictionary d;
- d["type"] = shape_type;
+ Dictionary gltf_shape;
+ gltf_shape["type"] = shape_type;
+ Dictionary sub;
if (shape_type == "box") {
Array size_array;
size_array.resize(3);
size_array[0] = size.x;
size_array[1] = size.y;
size_array[2] = size.z;
- d["size"] = size_array;
+ sub["size"] = size_array;
} else if (shape_type == "capsule") {
- d["radius"] = get_radius();
- d["height"] = get_height();
+ sub["radius"] = get_radius();
+ sub["height"] = get_height();
} else if (shape_type == "cylinder") {
- d["radius"] = get_radius();
- d["height"] = get_height();
+ sub["radius"] = get_radius();
+ sub["height"] = get_height();
} else if (shape_type == "sphere") {
- d["radius"] = get_radius();
- } else if (shape_type == "trimesh" || shape_type == "hull") {
- d["mesh"] = get_mesh_index();
- }
- if (is_trigger) {
- d["isTrigger"] = is_trigger;
+ sub["radius"] = get_radius();
+ } else if (shape_type == "trimesh" || shape_type == "convex") {
+ sub["mesh"] = get_mesh_index();
}
- return d;
+ gltf_shape[shape_type] = sub;
+ return gltf_shape;
}
diff --git a/modules/gltf/extensions/physics/gltf_physics_shape.h b/modules/gltf/extensions/physics/gltf_physics_shape.h
index efecf27e1b..4f7ac39292 100644
--- a/modules/gltf/extensions/physics/gltf_physics_shape.h
+++ b/modules/gltf/extensions/physics/gltf_physics_shape.h
@@ -37,8 +37,9 @@
class ImporterMesh;
-// GLTFPhysicsShape is an intermediary between OMI_collider and Godot's collision shape nodes.
-// https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_collider
+// GLTFPhysicsShape is an intermediary between Godot's collision shape nodes
+// and the OMI_physics_shape extension.
+// https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_shape
class GLTFPhysicsShape : public Resource {
GDCLASS(GLTFPhysicsShape, Resource)
diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub
index 4cf810f905..8a15c91417 100644
--- a/modules/openxr/SCsub
+++ b/modules/openxr/SCsub
@@ -104,6 +104,7 @@ if env["vulkan"]:
if env["opengl3"] and env["platform"] != "macos":
env_openxr.add_source_files(module_obj, "extensions/openxr_opengl_extension.cpp")
+env_openxr.add_source_files(module_obj, "extensions/openxr_local_floor_extension.cpp")
env_openxr.add_source_files(module_obj, "extensions/openxr_palm_pose_extension.cpp")
env_openxr.add_source_files(module_obj, "extensions/openxr_composition_layer_depth_extension.cpp")
env_openxr.add_source_files(module_obj, "extensions/openxr_eye_gaze_interaction.cpp")
diff --git a/modules/openxr/extensions/openxr_local_floor_extension.cpp b/modules/openxr/extensions/openxr_local_floor_extension.cpp
new file mode 100644
index 0000000000..8e06dd8ed5
--- /dev/null
+++ b/modules/openxr/extensions/openxr_local_floor_extension.cpp
@@ -0,0 +1,59 @@
+/**************************************************************************/
+/* openxr_local_floor_extension.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "openxr_local_floor_extension.h"
+
+#include "core/string/print_string.h"
+
+OpenXRLocalFloorExtension *OpenXRLocalFloorExtension::singleton = nullptr;
+
+OpenXRLocalFloorExtension *OpenXRLocalFloorExtension::get_singleton() {
+ return singleton;
+}
+
+OpenXRLocalFloorExtension::OpenXRLocalFloorExtension() {
+ singleton = this;
+}
+
+OpenXRLocalFloorExtension::~OpenXRLocalFloorExtension() {
+ singleton = nullptr;
+}
+
+HashMap<String, bool *> OpenXRLocalFloorExtension::get_requested_extensions() {
+ HashMap<String, bool *> request_extensions;
+
+ request_extensions[XR_EXT_LOCAL_FLOOR_EXTENSION_NAME] = &available;
+
+ return request_extensions;
+}
+
+bool OpenXRLocalFloorExtension::is_available() {
+ return available;
+}
diff --git a/modules/openxr/extensions/openxr_local_floor_extension.h b/modules/openxr/extensions/openxr_local_floor_extension.h
new file mode 100644
index 0000000000..dff97d9954
--- /dev/null
+++ b/modules/openxr/extensions/openxr_local_floor_extension.h
@@ -0,0 +1,53 @@
+/**************************************************************************/
+/* openxr_local_floor_extension.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef OPENXR_LOCAL_FLOOR_EXTENSION_H
+#define OPENXR_LOCAL_FLOOR_EXTENSION_H
+
+#include "openxr_extension_wrapper.h"
+
+class OpenXRLocalFloorExtension : public OpenXRExtensionWrapper {
+public:
+ static OpenXRLocalFloorExtension *get_singleton();
+
+ OpenXRLocalFloorExtension();
+ virtual ~OpenXRLocalFloorExtension() override;
+
+ virtual HashMap<String, bool *> get_requested_extensions() override;
+
+ bool is_available();
+
+private:
+ static OpenXRLocalFloorExtension *singleton;
+
+ bool available = false;
+};
+
+#endif // OPENXR_LOCAL_FLOOR_EXTENSION_H
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index da6fd2e9b2..8483e8aa24 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -665,13 +665,6 @@ bool OpenXRAPI::load_supported_reference_spaces() {
print_verbose(String("OpenXR: Found supported reference space ") + OpenXRUtil::get_reference_space_name(supported_reference_spaces[i]));
}
- // Check value we loaded at startup...
- if (!is_reference_space_supported(reference_space)) {
- print_verbose(String("OpenXR: ") + OpenXRUtil::get_reference_space_name(reference_space) + String(" isn't supported, defaulting to ") + OpenXRUtil::get_reference_space_name(supported_reference_spaces[0]));
-
- reference_space = supported_reference_spaces[0];
- }
-
return true;
}
@@ -699,16 +692,31 @@ bool OpenXRAPI::setup_spaces() {
// create play space
{
- if (!is_reference_space_supported(reference_space)) {
- print_line("OpenXR: reference space ", OpenXRUtil::get_reference_space_name(reference_space), " is not supported.");
- return false;
+ emulating_local_floor = false;
+
+ if (is_reference_space_supported(requested_reference_space)) {
+ reference_space = requested_reference_space;
+ } else if (requested_reference_space == XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT && is_reference_space_supported(XR_REFERENCE_SPACE_TYPE_STAGE)) {
+ print_verbose("OpenXR: LOCAL_FLOOR space isn't supported, emulating using STAGE and LOCAL spaces.");
+
+ reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
+ emulating_local_floor = true;
+
+ // We'll use the STAGE space to get the floor height, but we can't do that until
+ // after xrWaitFrame(), so just set this flag for now.
+ should_reset_emulated_floor_height = true;
+
+ } else {
+ // Fallback on LOCAL, which all OpenXR runtimes are required to support.
+ print_verbose(String("OpenXR: ") + OpenXRUtil::get_reference_space_name(requested_reference_space) + String(" isn't supported, defaulting to LOCAL space."));
+ reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
}
XrReferenceSpaceCreateInfo play_space_create_info = {
XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
nullptr, // next
reference_space, // referenceSpaceType
- identityPose // poseInReferenceSpace
+ identityPose, // poseInReferenceSpace
};
result = xrCreateReferenceSpace(session, &play_space_create_info, &play_space);
@@ -742,6 +750,80 @@ bool OpenXRAPI::setup_spaces() {
return true;
}
+bool OpenXRAPI::reset_emulated_floor_height() {
+ ERR_FAIL_COND_V(!emulating_local_floor, false);
+
+ // This is based on the example code in the OpenXR spec which shows how to
+ // emulate LOCAL_FLOOR if it's not supported.
+ // See: https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_EXT_local_floor
+
+ XrResult result;
+
+ XrPosef identityPose = {
+ { 0.0, 0.0, 0.0, 1.0 },
+ { 0.0, 0.0, 0.0 }
+ };
+
+ XrSpace local_space = XR_NULL_HANDLE;
+ XrSpace stage_space = XR_NULL_HANDLE;
+
+ XrReferenceSpaceCreateInfo create_info = {
+ XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
+ nullptr, // next
+ XR_REFERENCE_SPACE_TYPE_LOCAL, // referenceSpaceType
+ identityPose, // poseInReferenceSpace
+ };
+
+ result = xrCreateReferenceSpace(session, &create_info, &local_space);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Failed to create LOCAL space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]");
+ return false;
+ }
+
+ create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
+ result = xrCreateReferenceSpace(session, &create_info, &stage_space);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Failed to create STAGE space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]");
+ xrDestroySpace(local_space);
+ return false;
+ }
+
+ XrSpaceLocation stage_location = {
+ XR_TYPE_SPACE_LOCATION, // type
+ nullptr, // next
+ 0, // locationFlags
+ identityPose, // pose
+ };
+
+ result = xrLocateSpace(stage_space, local_space, get_next_frame_time(), &stage_location);
+
+ xrDestroySpace(local_space);
+ xrDestroySpace(stage_space);
+
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Failed to locate STAGE space in LOCAL space, in order to emulate LOCAL_FLOOR [", get_error_string(result), "]");
+ return false;
+ }
+
+ XrSpace new_play_space;
+ create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
+ create_info.poseInReferenceSpace.position.y = stage_location.pose.position.y;
+ result = xrCreateReferenceSpace(session, &create_info, &new_play_space);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Failed to recreate emulated LOCAL_FLOOR play space with latest floor estimate [", get_error_string(result), "]");
+ return false;
+ }
+
+ xrDestroySpace(play_space);
+ play_space = new_play_space;
+
+ // If we've made it this far, it means we can properly emulate LOCAL_FLOOR, so we'll
+ // report that as the reference space to the outside world.
+ reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT;
+
+ return true;
+}
+
bool OpenXRAPI::load_supported_swapchain_formats() {
ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
@@ -1180,10 +1262,10 @@ void OpenXRAPI::set_view_configuration(XrViewConfigurationType p_view_configurat
view_configuration = p_view_configuration;
}
-void OpenXRAPI::set_reference_space(XrReferenceSpaceType p_reference_space) {
+void OpenXRAPI::set_requested_reference_space(XrReferenceSpaceType p_requested_reference_space) {
ERR_FAIL_COND(is_initialized());
- reference_space = p_reference_space;
+ requested_reference_space = p_requested_reference_space;
}
void OpenXRAPI::set_submit_depth_buffer(bool p_submit_depth_buffer) {
@@ -1628,6 +1710,9 @@ bool OpenXRAPI::poll_events() {
XrEventDataReferenceSpaceChangePending *event = (XrEventDataReferenceSpaceChangePending *)&runtimeEvent;
print_verbose(String("OpenXR EVENT: reference space type ") + OpenXRUtil::get_reference_space_name(event->referenceSpaceType) + " change pending!");
+ if (emulating_local_floor) {
+ should_reset_emulated_floor_height = true;
+ }
if (event->poseValid && xr_interface) {
xr_interface->on_pose_recentered();
}
@@ -1783,6 +1868,11 @@ void OpenXRAPI::pre_render() {
frame_state.predictedDisplayPeriod = 0;
}
+ if (unlikely(should_reset_emulated_floor_height)) {
+ reset_emulated_floor_height();
+ should_reset_emulated_floor_height = false;
+ }
+
for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
wrapper->on_pre_render();
}
@@ -2136,10 +2226,13 @@ OpenXRAPI::OpenXRAPI() {
int reference_space_setting = GLOBAL_GET("xr/openxr/reference_space");
switch (reference_space_setting) {
case 0: {
- reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
+ requested_reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
} break;
case 1: {
- reference_space = XR_REFERENCE_SPACE_TYPE_STAGE;
+ requested_reference_space = XR_REFERENCE_SPACE_TYPE_STAGE;
+ } break;
+ case 2: {
+ requested_reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT;
} break;
default:
break;
diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h
index efa32b7544..6e55020aef 100644
--- a/modules/openxr/openxr_api.h
+++ b/modules/openxr/openxr_api.h
@@ -98,7 +98,8 @@ private:
// configuration
XrFormFactor form_factor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
XrViewConfigurationType view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
- XrReferenceSpaceType reference_space = XR_REFERENCE_SPACE_TYPE_STAGE;
+ XrReferenceSpaceType requested_reference_space = XR_REFERENCE_SPACE_TYPE_STAGE;
+ XrReferenceSpaceType reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
bool submit_depth_buffer = false; // if set to true we submit depth buffers to OpenXR if a suitable extension is enabled.
// blend mode
@@ -149,6 +150,10 @@ private:
bool view_pose_valid = false;
XRPose::TrackingConfidence head_pose_confidence = XRPose::XR_TRACKING_CONFIDENCE_NONE;
+ bool emulating_local_floor = false;
+ bool should_reset_emulated_floor_height = false;
+ bool reset_emulated_floor_height();
+
bool load_layer_properties();
bool load_supported_extensions();
bool is_extension_supported(const String &p_extension) const;
@@ -333,7 +338,8 @@ public:
void set_view_configuration(XrViewConfigurationType p_view_configuration);
XrViewConfigurationType get_view_configuration() const { return view_configuration; }
- void set_reference_space(XrReferenceSpaceType p_reference_space);
+ void set_requested_reference_space(XrReferenceSpaceType p_requested_reference_space);
+ XrReferenceSpaceType get_requested_reference_space() const { return requested_reference_space; }
XrReferenceSpaceType get_reference_space() const { return reference_space; }
void set_submit_depth_buffer(bool p_submit_depth_buffer);
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index ceeb1b0278..6b311b73a8 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -686,15 +686,48 @@ Dictionary OpenXRInterface::get_system_info() {
}
bool OpenXRInterface::supports_play_area_mode(XRInterface::PlayAreaMode p_mode) {
- return false;
+ if (p_mode == XRInterface::XR_PLAY_AREA_3DOF) {
+ return false;
+ }
+ return true;
}
XRInterface::PlayAreaMode OpenXRInterface::get_play_area_mode() const {
+ if (!openxr_api || !initialized) {
+ return XRInterface::XR_PLAY_AREA_UNKNOWN;
+ }
+
+ XrReferenceSpaceType reference_space = openxr_api->get_reference_space();
+
+ if (reference_space == XR_REFERENCE_SPACE_TYPE_LOCAL) {
+ return XRInterface::XR_PLAY_AREA_SITTING;
+ } else if (reference_space == XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT) {
+ return XRInterface::XR_PLAY_AREA_ROOMSCALE;
+ } else if (reference_space == XR_REFERENCE_SPACE_TYPE_STAGE) {
+ return XRInterface::XR_PLAY_AREA_STAGE;
+ }
+
return XRInterface::XR_PLAY_AREA_UNKNOWN;
}
bool OpenXRInterface::set_play_area_mode(XRInterface::PlayAreaMode p_mode) {
- return false;
+ ERR_FAIL_COND_V_MSG(initialized, false, "Cannot change play area mode after OpenXR interface has been initialized");
+ ERR_FAIL_NULL_V(openxr_api, false);
+
+ XrReferenceSpaceType reference_space;
+
+ if (p_mode == XRInterface::XR_PLAY_AREA_SITTING) {
+ reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
+ } else if (p_mode == XRInterface::XR_PLAY_AREA_ROOMSCALE) {
+ reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT;
+ } else if (p_mode == XRInterface::XR_PLAY_AREA_STAGE) {
+ reference_space = XR_REFERENCE_SPACE_TYPE_STAGE;
+ } else {
+ return false;
+ }
+
+ openxr_api->set_requested_reference_space(reference_space);
+ return true;
}
PackedVector3Array OpenXRInterface::get_play_area() const {
diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp
index 5cc793dca3..3609558309 100644
--- a/modules/openxr/register_types.cpp
+++ b/modules/openxr/register_types.cpp
@@ -49,6 +49,7 @@
#include "extensions/openxr_htc_controller_extension.h"
#include "extensions/openxr_htc_vive_tracker_extension.h"
#include "extensions/openxr_huawei_controller_extension.h"
+#include "extensions/openxr_local_floor_extension.h"
#include "extensions/openxr_meta_controller_extension.h"
#include "extensions/openxr_ml2_controller_extension.h"
#include "extensions/openxr_palm_pose_extension.h"
@@ -107,6 +108,7 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) {
// register our other extensions
OpenXRAPI::register_extension_wrapper(memnew(OpenXRPalmPoseExtension));
+ OpenXRAPI::register_extension_wrapper(memnew(OpenXRLocalFloorExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRPicoControllerExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRCompositionLayerDepthExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCControllerExtension));
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 5e42d8a967..459f5a5983 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -255,7 +255,7 @@ static const char *AAB_ASSETS_DIRECTORY = "res://android/build/assetPacks/instal
static const int OPENGL_MIN_SDK_VERSION = 21; // Should match the value in 'platform/android/java/app/config.gradle#minSdk'
static const int VULKAN_MIN_SDK_VERSION = 24;
-static const int DEFAULT_TARGET_SDK_VERSION = 33; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk'
+static const int DEFAULT_TARGET_SDK_VERSION = 34; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk'
#ifndef ANDROID_ENABLED
void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml
index 079f629b12..4abc6548bf 100644
--- a/platform/android/java/app/AndroidManifest.xml
+++ b/platform/android/java/app/AndroidManifest.xml
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- package="com.godot.game"
android:versionCode="1"
android:versionName="1.0"
android:installLocation="auto" >
diff --git a/platform/android/java/app/assetPacks/installTime/build.gradle b/platform/android/java/app/assetPacks/installTime/build.gradle
index b06faac374..46fa046ed4 100644
--- a/platform/android/java/app/assetPacks/installTime/build.gradle
+++ b/platform/android/java/app/assetPacks/installTime/build.gradle
@@ -1,4 +1,6 @@
-apply plugin: 'com.android.asset-pack'
+plugins {
+ id 'com.android.asset-pack'
+}
assetPack {
packName = "installTime" // Directory name for the asset pack
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index 8a543a8550..f084c60209 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -1,17 +1,4 @@
// Gradle build config for Godot Engine's Android port.
-buildscript {
- apply from: 'config.gradle'
-
- repositories {
- google()
- mavenCentral()
- }
- dependencies {
- classpath libraries.androidGradlePlugin
- classpath libraries.kotlinGradlePlugin
- }
-}
-
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
@@ -23,6 +10,8 @@ allprojects {
repositories {
google()
mavenCentral()
+ gradlePluginPortal()
+ maven { url "https://plugins.gradle.org/m2/" }
// Godot user plugins custom maven repos
String[] mavenRepos = getGodotPluginsMavenRepos()
@@ -42,8 +31,7 @@ configurations {
}
dependencies {
- implementation libraries.kotlinStdLib
- implementation libraries.androidxFragment
+ implementation "androidx.fragment:fragment:$versions.fragmentVersion"
if (rootProject.findProject(":lib")) {
implementation project(":lib")
@@ -88,6 +76,8 @@ android {
assetPacks = [":assetPacks:installTime"]
+ namespace = 'com.godot.game'
+
defaultConfig {
// The default ignore pattern for the 'assets' directory includes hidden files and directories which are used by Godot projects.
aaptOptions {
@@ -250,3 +240,17 @@ task validateJavaVersion {
throw new GradleException("Invalid Java version ${JavaVersion.current()}. Version ${versions.javaVersion} is the required Java version for Godot gradle builds.")
}
}
+
+/*
+When they're scheduled to run, the copy*AARToAppModule tasks generate dependencies for the 'app'
+module, so we're ensuring the ':app:preBuild' task is set to run after those tasks.
+ */
+if (rootProject.tasks.findByPath("copyDebugAARToAppModule") != null) {
+ preBuild.mustRunAfter(rootProject.tasks.named("copyDebugAARToAppModule"))
+}
+if (rootProject.tasks.findByPath("copyDevAARToAppModule") != null) {
+ preBuild.mustRunAfter(rootProject.tasks.named("copyDevAARToAppModule"))
+}
+if (rootProject.tasks.findByPath("copyReleaseAARToAppModule") != null) {
+ preBuild.mustRunAfter(rootProject.tasks.named("copyReleaseAARToAppModule"))
+}
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index bf091098b4..7224765f28 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -1,27 +1,20 @@
ext.versions = [
- androidGradlePlugin: '7.2.1',
- compileSdk : 33,
+ androidGradlePlugin: '8.2.0',
+ compileSdk : 34,
// Also update 'platform/android/export/export_plugin.cpp#OPENGL_MIN_SDK_VERSION'
minSdk : 21,
// Also update 'platform/android/export/export_plugin.cpp#DEFAULT_TARGET_SDK_VERSION'
- targetSdk : 33,
- buildTools : '33.0.2',
- kotlinVersion : '1.7.0',
- fragmentVersion : '1.3.6',
- nexusPublishVersion: '1.1.0',
+ targetSdk : 34,
+ buildTools : '34.0.0',
+ kotlinVersion : '1.9.20',
+ fragmentVersion : '1.6.2',
+ nexusPublishVersion: '1.3.0',
javaVersion : JavaVersion.VERSION_17,
// Also update 'platform/android/detect.py#get_ndk_version()' when this is updated.
ndkVersion : '23.2.8568313'
]
-ext.libraries = [
- androidGradlePlugin: "com.android.tools.build:gradle:$versions.androidGradlePlugin",
- kotlinGradlePlugin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlinVersion",
- kotlinStdLib : "org.jetbrains.kotlin:kotlin-stdlib:$versions.kotlinVersion",
- androidxFragment : "androidx.fragment:fragment:$versions.fragmentVersion",
-]
-
ext.getExportPackageName = { ->
// Retrieve the app id from the project property set by the Godot build command.
String appId = project.hasProperty("export_package_name") ? project.property("export_package_name") : ""
diff --git a/platform/android/java/app/settings.gradle b/platform/android/java/app/settings.gradle
index b4524a3f60..dcac44e393 100644
--- a/platform/android/java/app/settings.gradle
+++ b/platform/android/java/app/settings.gradle
@@ -7,8 +7,10 @@ pluginManagement {
id 'org.jetbrains.kotlin.android' version versions.kotlinVersion
}
repositories {
- gradlePluginPortal()
google()
+ mavenCentral()
+ gradlePluginPortal()
+ maven { url "https://plugins.gradle.org/m2/" }
}
}
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index f94454e2a7..a8cabc7045 100644
--- a/platform/android/java/build.gradle
+++ b/platform/android/java/build.gradle
@@ -1,18 +1,3 @@
-buildscript {
- apply from: 'app/config.gradle'
-
- repositories {
- google()
- mavenCentral()
- maven { url "https://plugins.gradle.org/m2/" }
- }
- dependencies {
- classpath libraries.androidGradlePlugin
- classpath libraries.kotlinGradlePlugin
- classpath 'io.github.gradle-nexus:publish-plugin:1.3.0'
- }
-}
-
plugins {
id 'io.github.gradle-nexus.publish-plugin'
}
@@ -31,6 +16,8 @@ allprojects {
repositories {
google()
mavenCentral()
+ gradlePluginPortal()
+ maven { url "https://plugins.gradle.org/m2/" }
}
}
@@ -303,7 +290,7 @@ task generateGodotTemplates {
*/
task generateDevTemplate {
// add parameter to set symbols to true
- gradle.startParameter.projectProperties += [doNotStrip: true]
+ gradle.startParameter.projectProperties += [doNotStrip: "true"]
gradle.startParameter.excludedTaskNames += templateExcludedBuildTask()
dependsOn = templateBuildTasks()
diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle
index 38034aa47c..0f7ffeecae 100644
--- a/platform/android/java/editor/build.gradle
+++ b/platform/android/java/editor/build.gradle
@@ -2,14 +2,14 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
+ id 'base'
}
dependencies {
- implementation libraries.kotlinStdLib
- implementation libraries.androidxFragment
+ implementation "androidx.fragment:fragment:$versions.fragmentVersion"
implementation project(":lib")
- implementation "androidx.window:window:1.0.0"
+ implementation "androidx.window:window:1.2.0"
}
ext {
@@ -81,6 +81,8 @@ android {
buildToolsVersion versions.buildTools
ndkVersion versions.ndkVersion
+ namespace = "org.godotengine.editor"
+
defaultConfig {
// The 'applicationId' suffix allows to install Godot 3.x(v3) and 4.x(v4) on the same device
applicationId "org.godotengine.editor.v4"
@@ -90,7 +92,10 @@ android {
targetSdkVersion versions.targetSdk
missingDimensionStrategy 'products', 'editor'
- setProperty("archivesBaseName", "android_editor")
+ }
+
+ base {
+ archivesName = "android_editor"
}
compileOptions {
@@ -111,6 +116,10 @@ android {
}
}
+ buildFeatures {
+ buildConfig = true
+ }
+
buildTypes {
dev {
initWith debug
diff --git a/platform/android/java/editor/src/main/AndroidManifest.xml b/platform/android/java/editor/src/main/AndroidManifest.xml
index 1405b6c737..78dcddac0e 100644
--- a/platform/android/java/editor/src/main/AndroidManifest.xml
+++ b/platform/android/java/editor/src/main/AndroidManifest.xml
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- package="org.godotengine.editor"
android:installLocation="auto">
<supports-screens
diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.properties b/platform/android/java/gradle/wrapper/gradle-wrapper.properties
index aa991fceae..471fefaf90 100644
--- a/platform/android/java/gradle/wrapper/gradle-wrapper.properties
+++ b/platform/android/java/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,6 @@
+#Wed Jan 17 12:08:26 PST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/platform/android/java/lib/AndroidManifest.xml b/platform/android/java/lib/AndroidManifest.xml
index f03a1dd47a..8240843876 100644
--- a/platform/android/java/lib/AndroidManifest.xml
+++ b/platform/android/java/lib/AndroidManifest.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.godotengine.godot"
android:versionCode="1"
android:versionName="1.0">
diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle
index 4340250ad3..61ae0cd58a 100644
--- a/platform/android/java/lib/build.gradle
+++ b/platform/android/java/lib/build.gradle
@@ -10,8 +10,7 @@ ext {
apply from: "../scripts/publish-module.gradle"
dependencies {
- implementation libraries.kotlinStdLib
- implementation libraries.androidxFragment
+ implementation "androidx.fragment:fragment:$versions.fragmentVersion"
}
def pathToRootDir = "../../../../"
@@ -39,6 +38,11 @@ android {
jvmTarget = versions.javaVersion
}
+ buildFeatures {
+ aidl = true
+ buildConfig = true
+ }
+
buildTypes {
dev {
initWith debug
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
index e26c9d39c2..89fbb9f580 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
@@ -210,21 +210,23 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
}
override fun onScroll(
- originEvent: MotionEvent,
+ originEvent: MotionEvent?,
terminusEvent: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
if (scaleInProgress) {
if (dragInProgress) {
- // Cancel the drag
- GodotInputHandler.handleMotionEvent(
- originEvent.source,
- MotionEvent.ACTION_CANCEL,
- originEvent.buttonState,
- originEvent.x,
- originEvent.y
- )
+ if (originEvent != null) {
+ // Cancel the drag
+ GodotInputHandler.handleMotionEvent(
+ originEvent.source,
+ MotionEvent.ACTION_CANCEL,
+ originEvent.buttonState,
+ originEvent.x,
+ originEvent.y
+ )
+ }
dragInProgress = false
}
}
diff --git a/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml b/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml
index dc180375d5..8072ee00db 100644
--- a/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml
+++ b/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
-<manifest package="org.godotengine.godot" />
+<manifest />
diff --git a/platform/android/java/nativeSrcsConfigs/build.gradle b/platform/android/java/nativeSrcsConfigs/build.gradle
index 5e810ae1ba..a728241181 100644
--- a/platform/android/java/nativeSrcsConfigs/build.gradle
+++ b/platform/android/java/nativeSrcsConfigs/build.gradle
@@ -9,6 +9,8 @@ android {
buildToolsVersion versions.buildTools
ndkVersion versions.ndkVersion
+ namespace = "org.godotengine.godot"
+
defaultConfig {
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
diff --git a/platform/android/java/settings.gradle b/platform/android/java/settings.gradle
index 466ffebf22..3137e74244 100644
--- a/platform/android/java/settings.gradle
+++ b/platform/android/java/settings.gradle
@@ -5,12 +5,15 @@ pluginManagement {
plugins {
id 'com.android.application' version versions.androidGradlePlugin
id 'com.android.library' version versions.androidGradlePlugin
+ id 'com.android.asset-pack' version versions.androidGradlePlugin
id 'org.jetbrains.kotlin.android' version versions.kotlinVersion
id 'io.github.gradle-nexus.publish-plugin' version versions.nexusPublishVersion
}
repositories {
- gradlePluginPortal()
google()
+ mavenCentral()
+ gradlePluginPortal()
+ maven { url "https://plugins.gradle.org/m2/" }
}
}
diff --git a/scene/3d/reflection_probe.cpp b/scene/3d/reflection_probe.cpp
index 2e34f6aad0..b4dd6d09be 100644
--- a/scene/3d/reflection_probe.cpp
+++ b/scene/3d/reflection_probe.cpp
@@ -165,6 +165,15 @@ uint32_t ReflectionProbe::get_cull_mask() const {
return cull_mask;
}
+void ReflectionProbe::set_reflection_mask(uint32_t p_layers) {
+ reflection_mask = p_layers;
+ RS::get_singleton()->reflection_probe_set_reflection_mask(probe, p_layers);
+}
+
+uint32_t ReflectionProbe::get_reflection_mask() const {
+ return reflection_mask;
+}
+
void ReflectionProbe::set_update_mode(UpdateMode p_mode) {
update_mode = p_mode;
RS::get_singleton()->reflection_probe_set_update_mode(probe, RS::ReflectionProbeUpdateMode(p_mode));
@@ -237,6 +246,9 @@ void ReflectionProbe::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_cull_mask", "layers"), &ReflectionProbe::set_cull_mask);
ClassDB::bind_method(D_METHOD("get_cull_mask"), &ReflectionProbe::get_cull_mask);
+ ClassDB::bind_method(D_METHOD("set_reflection_mask", "layers"), &ReflectionProbe::set_reflection_mask);
+ ClassDB::bind_method(D_METHOD("get_reflection_mask"), &ReflectionProbe::get_reflection_mask);
+
ClassDB::bind_method(D_METHOD("set_update_mode", "mode"), &ReflectionProbe::set_update_mode);
ClassDB::bind_method(D_METHOD("get_update_mode"), &ReflectionProbe::get_update_mode);
@@ -249,6 +261,7 @@ void ReflectionProbe::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interior"), "set_as_interior", "is_set_as_interior");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_shadows"), "set_enable_shadows", "are_shadows_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_cull_mask", "get_cull_mask");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "reflection_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_reflection_mask", "get_reflection_mask");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mesh_lod_threshold", PROPERTY_HINT_RANGE, "0,1024,0.1"), "set_mesh_lod_threshold", "get_mesh_lod_threshold");
ADD_GROUP("Ambient", "ambient_");
diff --git a/scene/3d/reflection_probe.h b/scene/3d/reflection_probe.h
index 5438219d5e..425fbb5bc2 100644
--- a/scene/3d/reflection_probe.h
+++ b/scene/3d/reflection_probe.h
@@ -63,6 +63,7 @@ private:
float mesh_lod_threshold = 1.0;
uint32_t cull_mask = (1 << 20) - 1;
+ uint32_t reflection_mask = (1 << 20) - 1;
UpdateMode update_mode = UPDATE_ONCE;
protected:
@@ -113,6 +114,9 @@ public:
void set_cull_mask(uint32_t p_layers);
uint32_t get_cull_mask() const;
+ void set_reflection_mask(uint32_t p_layers);
+ uint32_t get_reflection_mask() const;
+
void set_update_mode(UpdateMode p_mode);
UpdateMode get_update_mode() const;
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index ec5f8187a9..ee30f60c72 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -94,6 +94,19 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) {
set_bone_pose_rotation(which, p_value);
} else if (what == "scale") {
set_bone_pose_scale(which, p_value);
+#ifndef DISABLE_DEPRECATED
+ } else if (what == "pose") {
+ // Kept for compatibility from 3.x to 4.x.
+ WARN_DEPRECATED_MSG("Skeleton uses old pose format, which is deprecated (and loads slower). Consider re-importing or re-saving the scene." +
+ (is_inside_tree() ? vformat(" Path: \"%s\"", get_path()) : String()));
+ // Old Skeleton poses were relative to rest, new ones are absolute, so we need to recompute the pose.
+ // Skeleton3D nodes were always written with rest before pose, so this *SHOULD* work...
+ Transform3D rest = get_bone_rest(which);
+ Transform3D pose = rest * (Transform3D)p_value;
+ set_bone_pose_position(which, pose.origin);
+ set_bone_pose_rotation(which, pose.basis.get_rotation_quaternion());
+ set_bone_pose_scale(which, pose.basis.get_scale());
+#endif
} else {
return false;
}
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index b7bed59c00..36f1cd01f4 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -241,6 +241,16 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f
make_animation_instance(cd.from->name, pi);
}
+float AnimationPlayer::get_current_blend_amount() {
+ Playback &c = playback;
+ float blend = 1.0;
+ for (List<Blend>::Element *E = c.blend.front(); E; E = E->next()) {
+ Blend &b = E->get();
+ blend = blend - b.blend_left;
+ }
+ return MAX(0, blend);
+}
+
void AnimationPlayer::_blend_playback_data(double p_delta, bool p_started) {
Playback &c = playback;
@@ -250,16 +260,8 @@ void AnimationPlayer::_blend_playback_data(double p_delta, bool p_started) {
c.seeked = false;
}
- // First, calc all blends weight.
- float blend = 1.0;
- for (List<Blend>::Element *E = c.blend.front(); E; E = E->next()) {
- Blend &b = E->get();
- blend = MAX(0, blend - b.blend_left);
- b.blend_left = MAX(0, b.blend_left - Math::absf(speed_scale * p_delta) / b.blend_time);
- }
-
// Second, process current animation to check if the animation end reached.
- _process_playback_data(c.current, p_delta, blend, seeked, p_started, true);
+ _process_playback_data(c.current, p_delta, get_current_blend_amount(), seeked, p_started, true);
// Finally, if not end the animation, do blending.
if (end_reached) {
@@ -269,6 +271,7 @@ void AnimationPlayer::_blend_playback_data(double p_delta, bool p_started) {
List<List<Blend>::Element *> to_erase;
for (List<Blend>::Element *E = c.blend.front(); E; E = E->next()) {
Blend &b = E->get();
+ b.blend_left = MAX(0, b.blend_left - Math::absf(speed_scale * p_delta) / b.blend_time);
if (b.blend_left <= 0) {
to_erase.push_back(E);
b.blend_left = CMP_EPSILON; // May want to play last frame.
@@ -405,7 +408,7 @@ void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, floa
if (blend_time > 0) {
Blend b;
b.data = c.current;
- b.blend_left = 1.0;
+ b.blend_left = get_current_blend_amount();
b.blend_time = blend_time;
c.blend.push_back(b);
} else {
diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h
index 74f9323e2b..16bca45d4b 100644
--- a/scene/animation/animation_player.h
+++ b/scene/animation/animation_player.h
@@ -113,6 +113,8 @@ private:
void _stop_internal(bool p_reset, bool p_keep_state);
void _check_immediately_after_start();
+ float get_current_blend_amount();
+
bool playing = false;
protected:
diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp
index 222cdd15e4..a4eddb832d 100644
--- a/scene/gui/button.cpp
+++ b/scene/gui/button.cpp
@@ -167,20 +167,21 @@ void Button::_notification(int p_what) {
const int h_separation = MAX(0, theme_cache.h_separation);
+ float left_internal_margin_with_h_separation = _internal_margin[SIDE_LEFT];
+ float right_internal_margin_with_h_separation = _internal_margin[SIDE_RIGHT];
{ // The width reserved for internal element in derived classes (and h_separation if need).
- float internal_margin = _internal_margin[SIDE_LEFT] + _internal_margin[SIDE_RIGHT];
if (!xl_text.is_empty() || h_separation_is_valid_when_no_text) {
if (_internal_margin[SIDE_LEFT] > 0.0f) {
- internal_margin += h_separation;
+ left_internal_margin_with_h_separation += h_separation;
}
if (_internal_margin[SIDE_RIGHT] > 0.0f) {
- internal_margin += h_separation;
+ right_internal_margin_with_h_separation += h_separation;
}
}
- drawable_size_remained.width -= internal_margin; // The size after the internal element is stripped.
+ drawable_size_remained.width -= left_internal_margin_with_h_separation + right_internal_margin_with_h_separation; // The size after the internal element is stripped.
}
HorizontalAlignment icon_align_rtl_checked = horizontal_icon_alignment;
@@ -308,12 +309,12 @@ void Button::_notification(int p_what) {
case HORIZONTAL_ALIGNMENT_FILL:
case HORIZONTAL_ALIGNMENT_LEFT: {
icon_ofs.x += style_margin_left;
- icon_ofs.x += _internal_margin[SIDE_LEFT];
+ icon_ofs.x += left_internal_margin_with_h_separation;
} break;
case HORIZONTAL_ALIGNMENT_RIGHT: {
icon_ofs.x = size.x - style_margin_right;
- icon_ofs.x -= _internal_margin[SIDE_RIGHT];
+ icon_ofs.x -= right_internal_margin_with_h_separation;
icon_ofs.x -= icon_size.width;
} break;
}
@@ -368,7 +369,7 @@ void Button::_notification(int p_what) {
case HORIZONTAL_ALIGNMENT_LEFT:
case HORIZONTAL_ALIGNMENT_RIGHT: {
text_ofs.x += style_margin_left;
- text_ofs.x += _internal_margin[SIDE_LEFT];
+ text_ofs.x += left_internal_margin_with_h_separation;
if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) {
// Offset by the space's width that occupied by icon and h_separation together.
text_ofs.x += custom_element_size.width - drawable_size_remained.width;
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 2414278e52..dc586e86c9 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -2086,6 +2086,26 @@ void PopupMenu::set_item_indent(int p_idx, int p_indent) {
_menu_changed();
}
+void PopupMenu::set_item_max_states(int p_idx, int p_max_states) {
+ if (p_idx < 0) {
+ p_idx += get_item_count();
+ }
+ ERR_FAIL_INDEX(p_idx, items.size());
+
+ if (items[p_idx].max_states == p_max_states) {
+ return;
+ }
+
+ items.write[p_idx].max_states = p_max_states;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_max_states(global_menu_name, p_idx, p_max_states);
+ }
+
+ control->queue_redraw();
+ _menu_changed();
+}
+
void PopupMenu::set_item_multistate(int p_idx, int p_state) {
if (p_idx < 0) {
p_idx += get_item_count();
@@ -2724,6 +2744,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_item_shortcut", "index", "shortcut", "global"), &PopupMenu::set_item_shortcut, DEFVAL(false));
ClassDB::bind_method(D_METHOD("set_item_indent", "index", "indent"), &PopupMenu::set_item_indent);
ClassDB::bind_method(D_METHOD("set_item_multistate", "index", "state"), &PopupMenu::set_item_multistate);
+ ClassDB::bind_method(D_METHOD("set_item_multistate_max", "index", "max_states"), &PopupMenu::set_item_max_states);
ClassDB::bind_method(D_METHOD("set_item_shortcut_disabled", "index", "disabled"), &PopupMenu::set_item_shortcut_disabled);
ClassDB::bind_method(D_METHOD("toggle_item_checked", "index"), &PopupMenu::toggle_item_checked);
@@ -2750,6 +2771,9 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_item_shortcut", "index"), &PopupMenu::get_item_shortcut);
ClassDB::bind_method(D_METHOD("get_item_indent", "index"), &PopupMenu::get_item_indent);
+ ClassDB::bind_method(D_METHOD("get_item_multistate_max", "index"), &PopupMenu::get_item_max_states);
+ ClassDB::bind_method(D_METHOD("get_item_multistate", "index"), &PopupMenu::get_item_state);
+
ClassDB::bind_method(D_METHOD("set_focused_item", "index"), &PopupMenu::set_focused_item);
ClassDB::bind_method(D_METHOD("get_focused_item"), &PopupMenu::get_focused_item);
ClassDB::bind_method(D_METHOD("set_item_count", "count"), &PopupMenu::set_item_count);
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index 9783f9d57b..35ababd913 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -264,6 +264,7 @@ public:
void set_item_tooltip(int p_idx, const String &p_tooltip);
void set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bool p_global = false);
void set_item_indent(int p_idx, int p_indent);
+ void set_item_max_states(int p_idx, int p_max_states);
void set_item_multistate(int p_idx, int p_state);
void toggle_item_multistate(int p_idx);
void set_item_shortcut_disabled(int p_idx, bool p_disabled);
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index c67c3cd98d..4ecbc173b7 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -584,12 +584,30 @@ Variant TreeItem::get_metadata(int p_column) const {
return cells[p_column].meta;
}
+#ifndef DISABLE_DEPRECATED
void TreeItem::set_custom_draw(int p_column, Object *p_object, const StringName &p_callback) {
+ WARN_DEPRECATED_MSG(R"*(The "set_custom_draw()" method is deprecated, use "set_custom_draw_callback()" instead.)*");
ERR_FAIL_INDEX(p_column, cells.size());
ERR_FAIL_NULL(p_object);
- cells.write[p_column].custom_draw_obj = p_object->get_instance_id();
+ cells.write[p_column].custom_draw_callback = Callable(p_object, p_callback);
+
+ _changed_notify(p_column);
+}
+#endif // DISABLE_DEPRECATED
+
+void TreeItem::set_custom_draw_callback(int p_column, const Callable &p_callback) {
+ ERR_FAIL_INDEX(p_column, cells.size());
+
cells.write[p_column].custom_draw_callback = p_callback;
+
+ _changed_notify(p_column);
+}
+
+Callable TreeItem::get_custom_draw_callback(int p_column) const {
+ ERR_FAIL_INDEX_V(p_column, cells.size(), Callable());
+
+ return cells[p_column].custom_draw_callback;
}
void TreeItem::set_collapsed(bool p_collapsed) {
@@ -1306,8 +1324,14 @@ void TreeItem::clear_custom_color(int p_column) {
void TreeItem::set_custom_font(int p_column, const Ref<Font> &p_font) {
ERR_FAIL_INDEX(p_column, cells.size());
+ if (cells[p_column].custom_font == p_font) {
+ return;
+ }
+
cells.write[p_column].custom_font = p_font;
cells.write[p_column].cached_minimum_size_dirty = true;
+
+ _changed_notify(p_column);
}
Ref<Font> TreeItem::get_custom_font(int p_column) const {
@@ -1318,8 +1342,14 @@ Ref<Font> TreeItem::get_custom_font(int p_column) const {
void TreeItem::set_custom_font_size(int p_column, int p_font_size) {
ERR_FAIL_INDEX(p_column, cells.size());
+ if (cells[p_column].custom_font_size == p_font_size) {
+ return;
+ }
+
cells.write[p_column].custom_font_size = p_font_size;
cells.write[p_column].cached_minimum_size_dirty = true;
+
+ _changed_notify(p_column);
}
int TreeItem::get_custom_font_size(int p_column) const {
@@ -1368,8 +1398,14 @@ Color TreeItem::get_custom_bg_color(int p_column) const {
void TreeItem::set_custom_as_button(int p_column, bool p_button) {
ERR_FAIL_INDEX(p_column, cells.size());
+ if (cells[p_column].custom_button == p_button) {
+ return;
+ }
+
cells.write[p_column].custom_button = p_button;
cells.write[p_column].cached_minimum_size_dirty = true;
+
+ _changed_notify(p_column);
}
bool TreeItem::is_custom_set_as_button(int p_column) const {
@@ -1574,7 +1610,11 @@ void TreeItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_metadata", "column", "meta"), &TreeItem::set_metadata);
ClassDB::bind_method(D_METHOD("get_metadata", "column"), &TreeItem::get_metadata);
+#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("set_custom_draw", "column", "object", "callback"), &TreeItem::set_custom_draw);
+#endif // DISABLE_DEPRECATED
+ ClassDB::bind_method(D_METHOD("set_custom_draw_callback", "column", "callback"), &TreeItem::set_custom_draw_callback);
+ ClassDB::bind_method(D_METHOD("get_custom_draw_callback", "column"), &TreeItem::get_custom_draw_callback);
ClassDB::bind_method(D_METHOD("set_collapsed", "enable"), &TreeItem::set_collapsed);
ClassDB::bind_method(D_METHOD("is_collapsed"), &TreeItem::is_collapsed);
@@ -2297,10 +2337,15 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
} break;
case TreeItem::CELL_MODE_CUSTOM: {
- if (p_item->cells[i].custom_draw_obj.is_valid()) {
- Object *cdo = ObjectDB::get_instance(p_item->cells[i].custom_draw_obj);
- if (cdo) {
- cdo->call(p_item->cells[i].custom_draw_callback, p_item, Rect2(item_rect));
+ if (p_item->cells[i].custom_draw_callback.is_valid()) {
+ Variant args[] = { p_item, Rect2(item_rect) };
+ const Variant *argptrs[] = { &args[0], &args[1] };
+
+ Callable::CallError ce;
+ Variant ret;
+ p_item->cells[i].custom_draw_callback.callp(argptrs, 2, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT("Error calling custom draw method: " + Variant::get_callable_error_text(p_item->cells[i].custom_draw_callback, argptrs, 2, ce) + ".");
}
}
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 2dda408dd7..8ec003be9c 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -99,8 +99,7 @@ private:
Variant meta;
String tooltip;
- ObjectID custom_draw_obj;
- StringName custom_draw_callback;
+ Callable custom_draw_callback;
struct Button {
int id = 0;
@@ -285,7 +284,11 @@ public:
void set_metadata(int p_column, const Variant &p_meta);
Variant get_metadata(int p_column) const;
+#ifndef DISABLE_DEPRECATED
void set_custom_draw(int p_column, Object *p_object, const StringName &p_callback);
+#endif // DISABLE_DEPRECATED
+ void set_custom_draw_callback(int p_column, const Callable &p_callback);
+ Callable get_custom_draw_callback(int p_column) const;
void set_collapsed(bool p_collapsed);
bool is_collapsed();
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index f7d695bf31..704ff3e978 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -1214,6 +1214,11 @@ String Node::validate_child_name(Node *p_child) {
_generate_serial_child_name(p_child, name);
return name;
}
+
+String Node::prevalidate_child_name(Node *p_child, StringName p_name) {
+ _generate_serial_child_name(p_child, p_name);
+ return p_name;
+}
#endif
String Node::adjust_name_casing(const String &p_name) {
diff --git a/scene/main/node.h b/scene/main/node.h
index 8130c61a34..c82300e6a0 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -614,6 +614,7 @@ public:
#ifdef TOOLS_ENABLED
String validate_child_name(Node *p_child);
+ String prevalidate_child_name(Node *p_child, StringName p_name);
#endif
static String adjust_name_casing(const String &p_name);
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index fe02d97586..f92ab76753 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -3316,6 +3316,16 @@ void Viewport::push_input(const Ref<InputEvent> &p_event, bool p_local_coords) {
}
local_input_handled = false;
+ if (!handle_input_locally) {
+ Viewport *vp = this;
+ while (true) {
+ if (Object::cast_to<Window>(vp) || !vp->get_parent()) {
+ break;
+ }
+ vp = vp->get_parent()->get_viewport();
+ }
+ vp->local_input_handled = false;
+ }
Ref<InputEvent> ev;
if (!p_local_coords) {
diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp
index 6f12539a6d..0abf878659 100644
--- a/scene/resources/mesh.cpp
+++ b/scene/resources/mesh.cpp
@@ -1558,6 +1558,7 @@ void ArrayMesh::_create_if_empty() const {
mesh = RS::get_singleton()->mesh_create();
RS::get_singleton()->mesh_set_blend_shape_mode(mesh, (RS::BlendShapeMode)blend_shape_mode);
RS::get_singleton()->mesh_set_blend_shape_count(mesh, blend_shapes.size());
+ RS::get_singleton()->mesh_set_path(mesh, get_path());
}
}
@@ -1666,6 +1667,7 @@ void ArrayMesh::_set_surfaces(const Array &p_surfaces) {
// we can create it with a single call, which is a lot more efficient and thread friendly
mesh = RS::get_singleton()->mesh_create_from_surfaces(surface_data, blend_shapes.size());
RS::get_singleton()->mesh_set_blend_shape_mode(mesh, (RS::BlendShapeMode)blend_shape_mode);
+ RS::get_singleton()->mesh_set_path(mesh, get_path());
}
surfaces.clear();
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 8ff5b54fbe..41660767ab 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -246,6 +246,20 @@ void VisualShaderNode::set_input_port_connected(int p_port, bool p_connected) {
connected_input_ports[p_port] = p_connected;
}
+bool VisualShaderNode::is_any_port_connected() const {
+ for (const KeyValue<int, bool> &E : connected_input_ports) {
+ if (E.value) {
+ return true;
+ }
+ }
+ for (const KeyValue<int, int> &E : connected_output_ports) {
+ if (E.value > 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
bool VisualShaderNode::is_generate_input_var(int p_port) const {
return true;
}
diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h
index 501a538c86..7faebb86ab 100644
--- a/scene/resources/visual_shader.h
+++ b/scene/resources/visual_shader.h
@@ -314,6 +314,7 @@ public:
void set_output_port_connected(int p_port, bool p_connected);
bool is_input_port_connected(int p_port) const;
void set_input_port_connected(int p_port, bool p_connected);
+ bool is_any_port_connected() const;
virtual bool is_generate_input_var(int p_port) const;
virtual bool has_output_port_preview(int p_port) const;
diff --git a/scene/theme/theme_db.cpp b/scene/theme/theme_db.cpp
index 8dc9d288e2..6841a9e1d4 100644
--- a/scene/theme/theme_db.cpp
+++ b/scene/theme/theme_db.cpp
@@ -49,8 +49,8 @@ void ThemeDB::initialize_theme() {
// Allow creating the default theme at a different scale to suit higher/lower base resolutions.
float default_theme_scale = GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "gui/theme/default_theme_scale", PROPERTY_HINT_RANGE, "0.5,8,0.01", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), 1.0);
- String project_theme_path = GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "gui/theme/custom", PROPERTY_HINT_FILE, "*.tres,*.res,*.theme", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), "");
- String project_font_path = GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "gui/theme/custom_font", PROPERTY_HINT_FILE, "*.tres,*.res,*.otf,*.ttf,*.woff,*.woff2,*.fnt,*.font,*.pfb,*.pfm", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), "");
+ String project_theme_path = GLOBAL_DEF_RST_BASIC(PropertyInfo(Variant::STRING, "gui/theme/custom", PROPERTY_HINT_FILE, "*.tres,*.res,*.theme", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), "");
+ String project_font_path = GLOBAL_DEF_RST_BASIC(PropertyInfo(Variant::STRING, "gui/theme/custom_font", PROPERTY_HINT_FILE, "*.tres,*.res,*.otf,*.ttf,*.woff,*.woff2,*.fnt,*.font,*.pfb,*.pfm", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), "");
TextServer::FontAntialiasing font_antialiasing = (TextServer::FontAntialiasing)(int)GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "gui/theme/default_font_antialiasing", PROPERTY_HINT_ENUM, "None,Grayscale,LCD Subpixel", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), 1);
TextServer::Hinting font_hinting = (TextServer::Hinting)(int)GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "gui/theme/default_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), TextServer::HINTING_LIGHT);
diff --git a/servers/debugger/servers_debugger.cpp b/servers/debugger/servers_debugger.cpp
index bf7dd6c29e..8da3a10ce9 100644
--- a/servers/debugger/servers_debugger.cpp
+++ b/servers/debugger/servers_debugger.cpp
@@ -342,10 +342,12 @@ public:
}
ServerInfo &srv = server_data[name];
- ServerFunctionInfo fi;
- fi.name = p_data[1];
- fi.time = p_data[2];
- srv.functions.push_back(fi);
+ for (int idx = 1; idx < p_data.size() - 1; idx += 2) {
+ ServerFunctionInfo fi;
+ fi.name = p_data[idx];
+ fi.time = p_data[idx + 1];
+ srv.functions.push_back(fi);
+ }
}
void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) {
diff --git a/servers/rendering/dummy/storage/light_storage.h b/servers/rendering/dummy/storage/light_storage.h
index a9a7beb387..61a825f8c5 100644
--- a/servers/rendering/dummy/storage/light_storage.h
+++ b/servers/rendering/dummy/storage/light_storage.h
@@ -109,6 +109,7 @@ public:
virtual void reflection_probe_set_enable_box_projection(RID p_probe, bool p_enable) override {}
virtual void reflection_probe_set_enable_shadows(RID p_probe, bool p_enable) override {}
virtual void reflection_probe_set_cull_mask(RID p_probe, uint32_t p_layers) override {}
+ virtual void reflection_probe_set_reflection_mask(RID p_probe, uint32_t p_layers) override {}
virtual void reflection_probe_set_resolution(RID p_probe, int p_resolution) override {}
virtual void reflection_probe_set_mesh_lod_threshold(RID p_probe, float p_ratio) override {}
virtual float reflection_probe_get_mesh_lod_threshold(RID p_probe) const override { return 0.0; }
@@ -116,6 +117,7 @@ public:
virtual AABB reflection_probe_get_aabb(RID p_probe) const override { return AABB(); }
virtual RS::ReflectionProbeUpdateMode reflection_probe_get_update_mode(RID p_probe) const override { return RenderingServer::REFLECTION_PROBE_UPDATE_ONCE; }
virtual uint32_t reflection_probe_get_cull_mask(RID p_probe) const override { return 0; }
+ virtual uint32_t reflection_probe_get_reflection_mask(RID p_probe) const override { return 0; }
virtual Vector3 reflection_probe_get_size(RID p_probe) const override { return Vector3(); }
virtual Vector3 reflection_probe_get_origin_offset(RID p_probe) const override { return Vector3(); }
virtual float reflection_probe_get_origin_max_distance(RID p_probe) const override { return 0.0; }
diff --git a/servers/rendering/dummy/storage/material_storage.cpp b/servers/rendering/dummy/storage/material_storage.cpp
new file mode 100644
index 0000000000..5a2c135ff5
--- /dev/null
+++ b/servers/rendering/dummy/storage/material_storage.cpp
@@ -0,0 +1,136 @@
+/**************************************************************************/
+/* material_storage.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "material_storage.h"
+
+using namespace RendererDummy;
+
+MaterialStorage *MaterialStorage::singleton = nullptr;
+
+MaterialStorage::MaterialStorage() {
+ singleton = this;
+ ShaderCompiler::DefaultIdentifierActions actions;
+ dummy_compiler.initialize(actions);
+}
+
+MaterialStorage::~MaterialStorage() {
+ singleton = nullptr;
+}
+
+RID MaterialStorage::shader_allocate() {
+ return shader_owner.allocate_rid();
+}
+
+void MaterialStorage::shader_initialize(RID p_rid) {
+ shader_owner.initialize_rid(p_rid, DummyShader());
+}
+
+void MaterialStorage::shader_free(RID p_rid) {
+ DummyShader *shader = shader_owner.get_or_null(p_rid);
+ ERR_FAIL_NULL(shader);
+
+ shader_owner.free(p_rid);
+}
+
+void MaterialStorage::shader_set_code(RID p_shader, const String &p_code) {
+ DummyShader *shader = shader_owner.get_or_null(p_shader);
+ ERR_FAIL_NULL(shader);
+ if (p_code.is_empty()) {
+ return;
+ }
+
+ String mode_string = ShaderLanguage::get_shader_type(p_code);
+
+ RS::ShaderMode new_mode;
+ if (mode_string == "canvas_item") {
+ new_mode = RS::SHADER_CANVAS_ITEM;
+ } else if (mode_string == "particles") {
+ new_mode = RS::SHADER_PARTICLES;
+ } else if (mode_string == "spatial") {
+ new_mode = RS::SHADER_SPATIAL;
+ } else if (mode_string == "sky") {
+ new_mode = RS::SHADER_SKY;
+ } else if (mode_string == "fog") {
+ new_mode = RS::SHADER_FOG;
+ } else {
+ new_mode = RS::SHADER_MAX;
+ }
+ ShaderCompiler::IdentifierActions actions;
+ actions.uniforms = &shader->uniforms;
+ ShaderCompiler::GeneratedCode gen_code;
+
+ Error err = MaterialStorage::get_singleton()->dummy_compiler.compile(new_mode, p_code, &actions, "", gen_code);
+ ERR_FAIL_COND_MSG(err != OK, "Shader compilation failed.");
+}
+
+void MaterialStorage::get_shader_parameter_list(RID p_shader, List<PropertyInfo> *p_param_list) const {
+ DummyShader *shader = shader_owner.get_or_null(p_shader);
+ ERR_FAIL_NULL(shader);
+
+ SortArray<Pair<StringName, int>, ShaderLanguage::UniformOrderComparator> sorter;
+ LocalVector<Pair<StringName, int>> filtered_uniforms;
+
+ for (const KeyValue<StringName, ShaderLanguage::ShaderNode::Uniform> &E : shader->uniforms) {
+ if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL) {
+ continue;
+ }
+ if (E.value.texture_order >= 0) {
+ filtered_uniforms.push_back(Pair<StringName, int>(E.key, E.value.texture_order + 100000));
+ } else {
+ filtered_uniforms.push_back(Pair<StringName, int>(E.key, E.value.order));
+ }
+ }
+ int uniform_count = filtered_uniforms.size();
+ sorter.sort(filtered_uniforms.ptr(), uniform_count);
+
+ String last_group;
+ for (int i = 0; i < uniform_count; i++) {
+ const StringName &uniform_name = filtered_uniforms[i].first;
+ const ShaderLanguage::ShaderNode::Uniform &uniform = shader->uniforms[uniform_name];
+
+ String group = uniform.group;
+ if (!uniform.subgroup.is_empty()) {
+ group += "::" + uniform.subgroup;
+ }
+
+ if (group != last_group) {
+ PropertyInfo pi;
+ pi.usage = PROPERTY_USAGE_GROUP;
+ pi.name = group;
+ p_param_list->push_back(pi);
+
+ last_group = group;
+ }
+
+ PropertyInfo pi = ShaderLanguage::uniform_to_property_info(uniform);
+ pi.name = uniform_name;
+ p_param_list->push_back(pi);
+ }
+}
diff --git a/servers/rendering/dummy/storage/material_storage.h b/servers/rendering/dummy/storage/material_storage.h
index e8766b4a21..4fd178ac88 100644
--- a/servers/rendering/dummy/storage/material_storage.h
+++ b/servers/rendering/dummy/storage/material_storage.h
@@ -31,13 +31,31 @@
#ifndef MATERIAL_STORAGE_DUMMY_H
#define MATERIAL_STORAGE_DUMMY_H
+#include "servers/rendering/shader_compiler.h"
+#include "servers/rendering/shader_language.h"
#include "servers/rendering/storage/material_storage.h"
#include "servers/rendering/storage/utilities.h"
namespace RendererDummy {
class MaterialStorage : public RendererMaterialStorage {
+private:
+ static MaterialStorage *singleton;
+
+ struct DummyShader {
+ HashMap<StringName, ShaderLanguage::ShaderNode::Uniform> uniforms;
+ };
+
+ mutable RID_Owner<DummyShader> shader_owner;
+
+ ShaderCompiler dummy_compiler;
+
public:
+ static MaterialStorage *get_singleton() { return singleton; }
+
+ MaterialStorage();
+ ~MaterialStorage();
+
/* GLOBAL SHADER UNIFORM API */
virtual void global_shader_parameter_add(const StringName &p_name, RS::GlobalShaderParameterType p_type, const Variant &p_value) override {}
@@ -58,15 +76,15 @@ public:
/* SHADER API */
- virtual RID shader_allocate() override { return RID(); }
- virtual void shader_initialize(RID p_rid) override {}
- virtual void shader_free(RID p_rid) override{};
+ virtual RID shader_allocate() override;
+ virtual void shader_initialize(RID p_rid) override;
+ virtual void shader_free(RID p_rid) override;
- virtual void shader_set_code(RID p_shader, const String &p_code) override {}
+ virtual void shader_set_code(RID p_shader, const String &p_code) override;
virtual void shader_set_path_hint(RID p_shader, const String &p_code) override {}
virtual String shader_get_code(RID p_shader) const override { return ""; }
- virtual void get_shader_parameter_list(RID p_shader, List<PropertyInfo> *p_param_list) const override {}
+ virtual void get_shader_parameter_list(RID p_shader, List<PropertyInfo> *p_param_list) const override;
virtual void shader_set_default_texture_parameter(RID p_shader, const StringName &p_name, RID p_texture, int p_index) override {}
virtual RID shader_get_default_texture_parameter(RID p_shader, const StringName &p_name, int p_index) const override { return RID(); }
diff --git a/servers/rendering/dummy/storage/mesh_storage.h b/servers/rendering/dummy/storage/mesh_storage.h
index 89cccec54b..d287a1c387 100644
--- a/servers/rendering/dummy/storage/mesh_storage.h
+++ b/servers/rendering/dummy/storage/mesh_storage.h
@@ -51,9 +51,7 @@ private:
mutable RID_Owner<DummyMesh> mesh_owner;
public:
- static MeshStorage *get_singleton() {
- return singleton;
- };
+ static MeshStorage *get_singleton() { return singleton; }
MeshStorage();
~MeshStorage();
@@ -113,8 +111,11 @@ public:
virtual void mesh_set_custom_aabb(RID p_mesh, const AABB &p_aabb) override {}
virtual AABB mesh_get_custom_aabb(RID p_mesh) const override { return AABB(); }
-
virtual AABB mesh_get_aabb(RID p_mesh, RID p_skeleton = RID()) override { return AABB(); }
+
+ virtual void mesh_set_path(RID p_mesh, const String &p_path) override {}
+ virtual String mesh_get_path(RID p_mesh) const override { return String(); }
+
virtual void mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) override {}
virtual void mesh_clear(RID p_mesh) override;
diff --git a/servers/rendering/dummy/storage/texture_storage.h b/servers/rendering/dummy/storage/texture_storage.h
index b3a5323e66..1331ca72c2 100644
--- a/servers/rendering/dummy/storage/texture_storage.h
+++ b/servers/rendering/dummy/storage/texture_storage.h
@@ -46,9 +46,7 @@ private:
mutable RID_PtrOwner<DummyTexture> texture_owner;
public:
- static TextureStorage *get_singleton() {
- return singleton;
- };
+ static TextureStorage *get_singleton() { return singleton; }
TextureStorage();
~TextureStorage();
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
index 0c0062662a..3bd0fb6e2b 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -3730,7 +3730,9 @@ void RenderForwardClustered::_geometry_instance_add_surface_with_material(Geomet
uint64_t format = RendererRD::MeshStorage::get_singleton()->mesh_surface_get_format(sdcache->surface);
if (p_material->shader_data->uses_tangent && !(format & RS::ARRAY_FORMAT_TANGENT)) {
- WARN_PRINT_ED("Attempting to use a shader that requires tangents with a mesh that doesn't contain tangents. Ensure that meshes are imported with the 'ensure_tangents' option. If creating your own meshes, add an `ARRAY_TANGENT` array (when using ArrayMesh) or call `generate_tangents()` (when using SurfaceTool).");
+ String shader_path = p_material->shader_data->path.is_empty() ? "" : "(" + p_material->shader_data->path + ")";
+ String mesh_path = mesh_storage->mesh_get_path(p_mesh).is_empty() ? "" : "(" + mesh_storage->mesh_get_path(p_mesh) + ")";
+ WARN_PRINT_ED(vformat("Attempting to use a shader %s that requires tangents with a mesh %s that doesn't contain tangents. Ensure that meshes are imported with the 'ensure_tangents' option. If creating your own meshes, add an `ARRAY_TANGENT` array (when using ArrayMesh) or call `generate_tangents()` (when using SurfaceTool).", shader_path, mesh_path));
}
}
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
index da04e6f938..02719933e9 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
@@ -2437,7 +2437,9 @@ void RenderForwardMobile::_geometry_instance_add_surface_with_material(GeometryI
uint64_t format = RendererRD::MeshStorage::get_singleton()->mesh_surface_get_format(sdcache->surface);
if (p_material->shader_data->uses_tangent && !(format & RS::ARRAY_FORMAT_TANGENT)) {
- WARN_PRINT_ED("Attempting to use a shader that requires tangents with a mesh that doesn't contain tangents. Ensure that meshes are imported with the 'ensure_tangents' option. If creating your own meshes, add an `ARRAY_TANGENT` array (when using ArrayMesh) or call `generate_tangents()` (when using SurfaceTool).");
+ String shader_path = p_material->shader_data->path.is_empty() ? "" : "(" + p_material->shader_data->path + ")";
+ String mesh_path = mesh_storage->mesh_get_path(p_mesh).is_empty() ? "" : "(" + mesh_storage->mesh_get_path(p_mesh) + ")";
+ WARN_PRINT_ED(vformat("Attempting to use a shader %s that requires tangents with a mesh %s that doesn't contain tangents. Ensure that meshes are imported with the 'ensure_tangents' option. If creating your own meshes, add an `ARRAY_TANGENT` array (when using ArrayMesh) or call `generate_tangents()` (when using SurfaceTool).", shader_path, mesh_path));
}
}
diff --git a/servers/rendering/renderer_rd/shaders/canvas.glsl b/servers/rendering/renderer_rd/shaders/canvas.glsl
index 7a13ac7207..a234798e77 100644
--- a/servers/rendering/renderer_rd/shaders/canvas.glsl
+++ b/servers/rendering/renderer_rd/shaders/canvas.glsl
@@ -516,6 +516,9 @@ void main() {
if (normal_used || (using_light && bool(draw_data.flags & FLAGS_DEFAULT_NORMAL_MAP_USED))) {
normal.xy = texture(sampler2D(normal_texture, texture_sampler), uv).xy * vec2(2.0, -2.0) - vec2(1.0, -1.0);
+ if (bool(draw_data.flags & FLAGS_TRANSPOSE_RECT)) {
+ normal.xy = normal.yx;
+ }
if (bool(draw_data.flags & FLAGS_FLIP_H)) {
normal.x = -normal.x;
}
diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
index 150e82c25c..2786af65eb 100644
--- a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
@@ -1119,6 +1119,14 @@ void LightStorage::reflection_probe_set_cull_mask(RID p_probe, uint32_t p_layers
reflection_probe->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_REFLECTION_PROBE);
}
+void LightStorage::reflection_probe_set_reflection_mask(RID p_probe, uint32_t p_layers) {
+ ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe);
+ ERR_FAIL_NULL(reflection_probe);
+
+ reflection_probe->reflection_mask = p_layers;
+ reflection_probe->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_REFLECTION_PROBE);
+}
+
void LightStorage::reflection_probe_set_resolution(RID p_probe, int p_resolution) {
ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe);
ERR_FAIL_NULL(reflection_probe);
@@ -1168,6 +1176,13 @@ uint32_t LightStorage::reflection_probe_get_cull_mask(RID p_probe) const {
return reflection_probe->cull_mask;
}
+uint32_t LightStorage::reflection_probe_get_reflection_mask(RID p_probe) const {
+ const ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe);
+ ERR_FAIL_NULL_V(reflection_probe, 0);
+
+ return reflection_probe->reflection_mask;
+}
+
Vector3 LightStorage::reflection_probe_get_size(RID p_probe) const {
const ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe);
ERR_FAIL_NULL_V(reflection_probe, Vector3());
@@ -1681,7 +1696,7 @@ void LightStorage::update_reflection_probe_buffer(RenderDataRD *p_render_data, c
Vector3 extents = probe->size / 2;
- rpi->cull_mask = probe->cull_mask;
+ rpi->cull_mask = probe->reflection_mask;
reflection_ubo.box_extents[0] = extents.x;
reflection_ubo.box_extents[1] = extents.y;
@@ -1693,7 +1708,7 @@ void LightStorage::update_reflection_probe_buffer(RenderDataRD *p_render_data, c
reflection_ubo.box_offset[0] = origin_offset.x;
reflection_ubo.box_offset[1] = origin_offset.y;
reflection_ubo.box_offset[2] = origin_offset.z;
- reflection_ubo.mask = probe->cull_mask;
+ reflection_ubo.mask = probe->reflection_mask;
reflection_ubo.intensity = probe->intensity;
reflection_ubo.ambient_mode = probe->ambient_mode;
diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.h b/servers/rendering/renderer_rd/storage_rd/light_storage.h
index f5b846362a..45226ec47c 100644
--- a/servers/rendering/renderer_rd/storage_rd/light_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/light_storage.h
@@ -232,6 +232,7 @@ private:
bool box_projection = false;
bool enable_shadows = false;
uint32_t cull_mask = (1 << 20) - 1;
+ uint32_t reflection_mask = (1 << 20) - 1;
float mesh_lod_threshold = 0.01;
float baked_exposure = 1.0;
@@ -797,6 +798,7 @@ public:
virtual void reflection_probe_set_enable_box_projection(RID p_probe, bool p_enable) override;
virtual void reflection_probe_set_enable_shadows(RID p_probe, bool p_enable) override;
virtual void reflection_probe_set_cull_mask(RID p_probe, uint32_t p_layers) override;
+ virtual void reflection_probe_set_reflection_mask(RID p_probe, uint32_t p_layers) override;
virtual void reflection_probe_set_resolution(RID p_probe, int p_resolution) override;
virtual void reflection_probe_set_mesh_lod_threshold(RID p_probe, float p_ratio) override;
@@ -805,6 +807,7 @@ public:
virtual AABB reflection_probe_get_aabb(RID p_probe) const override;
virtual RS::ReflectionProbeUpdateMode reflection_probe_get_update_mode(RID p_probe) const override;
virtual uint32_t reflection_probe_get_cull_mask(RID p_probe) const override;
+ virtual uint32_t reflection_probe_get_reflection_mask(RID p_probe) const override;
virtual Vector3 reflection_probe_get_size(RID p_probe) const override;
virtual Vector3 reflection_probe_get_origin_offset(RID p_probe) const override;
virtual float reflection_probe_get_origin_max_distance(RID p_probe) const override;
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
index b97ce2d006..245dac94ac 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
@@ -768,6 +768,20 @@ AABB MeshStorage::mesh_get_aabb(RID p_mesh, RID p_skeleton) {
return aabb;
}
+void MeshStorage::mesh_set_path(RID p_mesh, const String &p_path) {
+ Mesh *mesh = mesh_owner.get_or_null(p_mesh);
+ ERR_FAIL_NULL(mesh);
+
+ mesh->path = p_path;
+}
+
+String MeshStorage::mesh_get_path(RID p_mesh) const {
+ Mesh *mesh = mesh_owner.get_or_null(p_mesh);
+ ERR_FAIL_NULL_V(mesh, String());
+
+ return mesh->path;
+}
+
void MeshStorage::mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) {
Mesh *mesh = mesh_owner.get_or_null(p_mesh);
ERR_FAIL_NULL(mesh);
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
index a1e2ffcf7e..771ac6d380 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
@@ -162,6 +162,8 @@ private:
RID shadow_mesh;
HashSet<Mesh *> shadow_owners;
+ String path;
+
Dependency dependency;
};
@@ -378,6 +380,9 @@ public:
virtual AABB mesh_get_aabb(RID p_mesh, RID p_skeleton = RID()) override;
virtual void mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) override;
+ virtual void mesh_set_path(RID p_mesh, const String &p_path) override;
+ virtual String mesh_get_path(RID p_mesh) const override;
+
virtual void mesh_clear(RID p_mesh) override;
virtual bool mesh_needs_instance(RID p_mesh, bool p_has_skeleton) override;
diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
index d8baf260f9..7784ad5d49 100644
--- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
@@ -2552,7 +2552,7 @@ void TextureStorage::update_decal_atlas() {
//generate atlas
Vector<DecalAtlas::SortItem> itemsv;
itemsv.resize(decal_atlas.textures.size());
- int base_size = 8;
+ uint32_t base_size = 8;
int idx = 0;
@@ -2565,7 +2565,7 @@ void TextureStorage::update_decal_atlas() {
si.size.height = (src_tex->height / border) + 1;
si.pixel_size = Size2i(src_tex->width, src_tex->height);
- if (base_size < si.size.width) {
+ if (base_size < (uint32_t)si.size.width) {
base_size = nearest_power_of_2_templated(si.size.width);
}
@@ -2596,7 +2596,7 @@ void TextureStorage::update_decal_atlas() {
DecalAtlas::SortItem &si = items[i];
int best_idx = -1;
int best_height = 0x7FFFFFFF;
- for (int j = 0; j <= base_size - si.size.width; j++) {
+ for (uint32_t j = 0; j <= base_size - si.size.width; j++) {
int height = 0;
for (int k = 0; k < si.size.width; k++) {
int h = v_offsets[k + j];
@@ -2627,7 +2627,7 @@ void TextureStorage::update_decal_atlas() {
}
}
- if (max_height <= base_size * 2) {
+ if ((uint32_t)max_height <= base_size * 2) {
atlas_height = max_height;
break; //good ratio, break;
}
diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp
index 73aacf311f..2f7e4fef06 100644
--- a/servers/rendering/renderer_scene_cull.cpp
+++ b/servers/rendering/renderer_scene_cull.cpp
@@ -4071,7 +4071,7 @@ bool RendererSceneCull::free(RID p_rid) {
scenario_owner.free(p_rid);
RendererSceneOcclusionCull::get_singleton()->remove_scenario(p_rid);
- } else if (RendererSceneOcclusionCull::get_singleton()->is_occluder(p_rid)) {
+ } else if (RendererSceneOcclusionCull::get_singleton() && RendererSceneOcclusionCull::get_singleton()->is_occluder(p_rid)) {
RendererSceneOcclusionCull::get_singleton()->free_occluder(p_rid);
} else if (instance_owner.owns(p_rid)) {
// delete the instance
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index 8e03796d33..0c95301402 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -510,7 +510,7 @@ Vector<uint8_t> RenderingDevice::buffer_get_data(RID p_buffer, uint32_t p_offset
_flush(true);
uint8_t *buffer_mem = driver->buffer_map(tmp_buffer);
- ERR_FAIL_COND_V(!buffer_mem, Vector<uint8_t>());
+ ERR_FAIL_NULL_V(buffer_mem, Vector<uint8_t>());
Vector<uint8_t> buffer_data;
{
@@ -773,12 +773,12 @@ RID RenderingDevice::texture_create_shared(const TextureView &p_view, RID p_with
_THREAD_SAFE_METHOD_
Texture *src_texture = texture_owner.get_or_null(p_with_texture);
- ERR_FAIL_COND_V(!src_texture, RID());
+ ERR_FAIL_NULL_V(src_texture, RID());
if (src_texture->owner.is_valid()) { // Ahh this is a share. The RenderingDeviceDriver needs the actual owner.
p_with_texture = src_texture->owner;
src_texture = texture_owner.get_or_null(src_texture->owner);
- ERR_FAIL_COND_V(!src_texture, RID()); // This is a bug.
+ ERR_FAIL_NULL_V(src_texture, RID()); // This is a bug.
}
// Create view.
@@ -866,12 +866,12 @@ RID RenderingDevice::texture_create_shared_from_slice(const TextureView &p_view,
_THREAD_SAFE_METHOD_
Texture *src_texture = texture_owner.get_or_null(p_with_texture);
- ERR_FAIL_COND_V(!src_texture, RID());
+ ERR_FAIL_NULL_V(src_texture, RID());
if (src_texture->owner.is_valid()) { // // Ahh this is a share. The RenderingDeviceDriver needs the actual owner.
p_with_texture = src_texture->owner;
src_texture = texture_owner.get_or_null(src_texture->owner);
- ERR_FAIL_COND_V(!src_texture, RID()); // This is a bug.
+ ERR_FAIL_NULL_V(src_texture, RID()); // This is a bug.
}
ERR_FAIL_COND_V_MSG(p_slice_type == TEXTURE_SLICE_CUBEMAP && (src_texture->type != TEXTURE_TYPE_CUBE && src_texture->type != TEXTURE_TYPE_CUBE_ARRAY), RID(),
@@ -1247,7 +1247,7 @@ Vector<uint8_t> RenderingDevice::texture_get_data(RID p_texture, uint32_t p_laye
_THREAD_SAFE_METHOD_
Texture *tex = texture_owner.get_or_null(p_texture);
- ERR_FAIL_COND_V(!tex, Vector<uint8_t>());
+ ERR_FAIL_NULL_V(tex, Vector<uint8_t>());
ERR_FAIL_COND_V_MSG(tex->bound, Vector<uint8_t>(),
"Texture can't be retrieved while a draw list that uses it as part of a framebuffer is being created. Ensure the draw list is finalized (and that the color/depth texture using it is not set to `RenderingDevice.FINAL_ACTION_CONTINUE`) to retrieve this texture.");
@@ -1362,7 +1362,7 @@ bool RenderingDevice::texture_is_shared(RID p_texture) {
_THREAD_SAFE_METHOD_
Texture *tex = texture_owner.get_or_null(p_texture);
- ERR_FAIL_COND_V(!tex, false);
+ ERR_FAIL_NULL_V(tex, false);
return tex->owner.is_valid();
}
@@ -1374,7 +1374,7 @@ RD::TextureFormat RenderingDevice::texture_get_format(RID p_texture) {
_THREAD_SAFE_METHOD_
Texture *tex = texture_owner.get_or_null(p_texture);
- ERR_FAIL_COND_V(!tex, TextureFormat());
+ ERR_FAIL_NULL_V(tex, TextureFormat());
TextureFormat tf;
@@ -1397,7 +1397,7 @@ Size2i RenderingDevice::texture_size(RID p_texture) {
_THREAD_SAFE_METHOD_
Texture *tex = texture_owner.get_or_null(p_texture);
- ERR_FAIL_COND_V(!tex, Size2i());
+ ERR_FAIL_NULL_V(tex, Size2i());
return Size2i(tex->width, tex->height);
}
@@ -2041,7 +2041,7 @@ void RenderingDevice::framebuffer_set_invalidation_callback(RID p_framebuffer, I
_THREAD_SAFE_METHOD_
Framebuffer *framebuffer = framebuffer_owner.get_or_null(p_framebuffer);
- ERR_FAIL_COND(!framebuffer);
+ ERR_FAIL_NULL(framebuffer);
framebuffer->invalidated_callback = p_callback;
framebuffer->invalidated_callback_userdata = p_userdata;
@@ -2522,7 +2522,7 @@ RID RenderingDevice::uniform_set_create(const Vector<Uniform> &p_uniforms, RID p
for (uint32_t j = 0; j < uniform.get_id_count(); j++) {
RDD::SamplerID *sampler_driver_id = sampler_owner.get_or_null(uniform.get_id(j));
- ERR_FAIL_COND_V_MSG(!sampler_driver_id, RID(), "Sampler (binding: " + itos(uniform.binding) + ", index " + itos(j) + ") is not a valid sampler.");
+ ERR_FAIL_NULL_V_MSG(sampler_driver_id, RID(), "Sampler (binding: " + itos(uniform.binding) + ", index " + itos(j) + ") is not a valid sampler.");
driver_uniform.ids.push_back(*sampler_driver_id);
}
@@ -2538,7 +2538,7 @@ RID RenderingDevice::uniform_set_create(const Vector<Uniform> &p_uniforms, RID p
for (uint32_t j = 0; j < uniform.get_id_count(); j += 2) {
RDD::SamplerID *sampler_driver_id = sampler_owner.get_or_null(uniform.get_id(j + 0));
- ERR_FAIL_COND_V_MSG(!sampler_driver_id, RID(), "SamplerBuffer (binding: " + itos(uniform.binding) + ", index " + itos(j + 1) + ") is not a valid sampler.");
+ ERR_FAIL_NULL_V_MSG(sampler_driver_id, RID(), "SamplerBuffer (binding: " + itos(uniform.binding) + ", index " + itos(j + 1) + ") is not a valid sampler.");
RID texture_id = uniform.get_id(j + 1);
Texture *texture = texture_owner.get_or_null(texture_id);
@@ -2687,7 +2687,7 @@ RID RenderingDevice::uniform_set_create(const Vector<Uniform> &p_uniforms, RID p
for (uint32_t j = 0; j < uniform.get_id_count(); j += 2) {
RDD::SamplerID *sampler_driver_id = sampler_owner.get_or_null(uniform.get_id(j + 0));
- ERR_FAIL_COND_V_MSG(!sampler_driver_id, RID(), "SamplerBuffer (binding: " + itos(uniform.binding) + ", index " + itos(j + 1) + ") is not a valid sampler.");
+ ERR_FAIL_NULL_V_MSG(sampler_driver_id, RID(), "SamplerBuffer (binding: " + itos(uniform.binding) + ", index " + itos(j + 1) + ") is not a valid sampler.");
RID buffer_id = uniform.get_id(j + 1);
Buffer *buffer = texture_buffer_owner.get_or_null(buffer_id);
@@ -3748,7 +3748,7 @@ uint32_t RenderingDevice::draw_list_get_current_pass() {
RenderingDevice::DrawListID RenderingDevice::draw_list_switch_to_next_pass() {
_THREAD_SAFE_METHOD_
- ERR_FAIL_COND_V(draw_list == nullptr, INVALID_ID);
+ ERR_FAIL_NULL_V(draw_list, INVALID_ID);
ERR_FAIL_COND_V(draw_list_current_subpass >= draw_list_subpass_count - 1, INVALID_FORMAT_ID);
draw_list_current_subpass++;
@@ -3795,7 +3795,7 @@ void RenderingDevice::_draw_list_free(Rect2i *r_last_viewport) {
void RenderingDevice::draw_list_end() {
_THREAD_SAFE_METHOD_
- ERR_FAIL_COND_MSG(!draw_list, "Immediate draw list is already inactive.");
+ ERR_FAIL_NULL_MSG(draw_list, "Immediate draw list is already inactive.");
draw_graph.add_draw_list_end();
@@ -4090,7 +4090,7 @@ void RenderingDevice::compute_list_dispatch_indirect(ComputeListID p_list, RID p
ComputeList *cl = compute_list;
Buffer *buffer = storage_buffer_owner.get_or_null(p_buffer);
- ERR_FAIL_COND(!buffer);
+ ERR_FAIL_NULL(buffer);
ERR_FAIL_COND_MSG(!buffer->usage.has_flag(RDD::BUFFER_USAGE_INDIRECT_BIT), "Buffer provided was not created to do indirect dispatch.");
@@ -4210,7 +4210,7 @@ bool RenderingDevice::_texture_make_mutable(Texture *p_texture, RID p_texture_id
if (p_texture->owner.is_valid()) {
// Texture has an owner.
Texture *owner_texture = texture_owner.get_or_null(p_texture->owner);
- ERR_FAIL_COND_V(!owner_texture, false);
+ ERR_FAIL_NULL_V(owner_texture, false);
if (owner_texture->draw_tracker != nullptr) {
// Create a tracker for this dependency in particular.
diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h
index e8b20692f0..3bb6ba1c51 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -315,6 +315,9 @@ public:
FUNC2(mesh_set_custom_aabb, RID, const AABB &)
FUNC1RC(AABB, mesh_get_custom_aabb, RID)
+ FUNC2(mesh_set_path, RID, const String &)
+ FUNC1RC(String, mesh_get_path, RID)
+
FUNC2(mesh_set_shadow_mesh, RID, RID)
FUNC1(mesh_clear, RID)
@@ -401,6 +404,7 @@ public:
FUNC2(reflection_probe_set_enable_box_projection, RID, bool)
FUNC2(reflection_probe_set_enable_shadows, RID, bool)
FUNC2(reflection_probe_set_cull_mask, RID, uint32_t)
+ FUNC2(reflection_probe_set_reflection_mask, RID, uint32_t)
FUNC2(reflection_probe_set_resolution, RID, int)
FUNC2(reflection_probe_set_mesh_lod_threshold, RID, float)
diff --git a/servers/rendering/storage/light_storage.h b/servers/rendering/storage/light_storage.h
index c1f79cfc49..27407305d1 100644
--- a/servers/rendering/storage/light_storage.h
+++ b/servers/rendering/storage/light_storage.h
@@ -118,11 +118,13 @@ public:
virtual void reflection_probe_set_enable_box_projection(RID p_probe, bool p_enable) = 0;
virtual void reflection_probe_set_enable_shadows(RID p_probe, bool p_enable) = 0;
virtual void reflection_probe_set_cull_mask(RID p_probe, uint32_t p_layers) = 0;
+ virtual void reflection_probe_set_reflection_mask(RID p_probe, uint32_t p_layers) = 0;
virtual void reflection_probe_set_mesh_lod_threshold(RID p_probe, float p_ratio) = 0;
virtual AABB reflection_probe_get_aabb(RID p_probe) const = 0;
virtual RS::ReflectionProbeUpdateMode reflection_probe_get_update_mode(RID p_probe) const = 0;
virtual uint32_t reflection_probe_get_cull_mask(RID p_probe) const = 0;
+ virtual uint32_t reflection_probe_get_reflection_mask(RID p_probe) const = 0;
virtual Vector3 reflection_probe_get_size(RID p_probe) const = 0;
virtual Vector3 reflection_probe_get_origin_offset(RID p_probe) const = 0;
virtual float reflection_probe_get_origin_max_distance(RID p_probe) const = 0;
diff --git a/servers/rendering/storage/mesh_storage.h b/servers/rendering/storage/mesh_storage.h
index 76e46a696a..3c1b2b495d 100644
--- a/servers/rendering/storage/mesh_storage.h
+++ b/servers/rendering/storage/mesh_storage.h
@@ -67,9 +67,11 @@ public:
virtual void mesh_set_custom_aabb(RID p_mesh, const AABB &p_aabb) = 0;
virtual AABB mesh_get_custom_aabb(RID p_mesh) const = 0;
-
virtual AABB mesh_get_aabb(RID p_mesh, RID p_skeleton = RID()) = 0;
+ virtual void mesh_set_path(RID p_mesh, const String &p_path) = 0;
+ virtual String mesh_get_path(RID p_mesh) const = 0;
+
virtual void mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) = 0;
virtual void mesh_clear(RID p_mesh) = 0;
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 27e677bce0..99b1c5631a 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -2551,6 +2551,7 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("reflection_probe_set_enable_box_projection", "probe", "enable"), &RenderingServer::reflection_probe_set_enable_box_projection);
ClassDB::bind_method(D_METHOD("reflection_probe_set_enable_shadows", "probe", "enable"), &RenderingServer::reflection_probe_set_enable_shadows);
ClassDB::bind_method(D_METHOD("reflection_probe_set_cull_mask", "probe", "layers"), &RenderingServer::reflection_probe_set_cull_mask);
+ ClassDB::bind_method(D_METHOD("reflection_probe_set_reflection_mask", "probe", "layers"), &RenderingServer::reflection_probe_set_reflection_mask);
ClassDB::bind_method(D_METHOD("reflection_probe_set_resolution", "probe", "resolution"), &RenderingServer::reflection_probe_set_resolution);
ClassDB::bind_method(D_METHOD("reflection_probe_set_mesh_lod_threshold", "probe", "pixels"), &RenderingServer::reflection_probe_set_mesh_lod_threshold);
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index 958c21f893..d63283ddbe 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -385,6 +385,9 @@ public:
virtual void mesh_set_custom_aabb(RID p_mesh, const AABB &p_aabb) = 0;
virtual AABB mesh_get_custom_aabb(RID p_mesh) const = 0;
+ virtual void mesh_set_path(RID p_mesh, const String &p_path) = 0;
+ virtual String mesh_get_path(RID p_mesh) const = 0;
+
virtual void mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) = 0;
virtual void mesh_clear(RID p_mesh) = 0;
@@ -572,6 +575,7 @@ public:
virtual void reflection_probe_set_enable_box_projection(RID p_probe, bool p_enable) = 0;
virtual void reflection_probe_set_enable_shadows(RID p_probe, bool p_enable) = 0;
virtual void reflection_probe_set_cull_mask(RID p_probe, uint32_t p_layers) = 0;
+ virtual void reflection_probe_set_reflection_mask(RID p_probe, uint32_t p_layers) = 0;
virtual void reflection_probe_set_resolution(RID p_probe, int p_resolution) = 0;
virtual void reflection_probe_set_mesh_lod_threshold(RID p_probe, float p_pixels) = 0;
diff --git a/servers/xr/xr_interface.h b/servers/xr/xr_interface.h
index c76d0fbf68..d7bd212449 100644
--- a/servers/xr/xr_interface.h
+++ b/servers/xr/xr_interface.h
@@ -75,7 +75,7 @@ public:
XR_PLAY_AREA_3DOF, /* Only support orientation tracking, no positional tracking, area will center around player */
XR_PLAY_AREA_SITTING, /* Player is in seated position, limited positional tracking, fixed guardian around player */
XR_PLAY_AREA_ROOMSCALE, /* Player is free to move around, full positional tracking */
- XR_PLAY_AREA_STAGE, /* Same as roomscale but origin point is fixed to the center of the physical space, XRServer.center_on_hmd disabled */
+ XR_PLAY_AREA_STAGE, /* Same as roomscale but origin point is fixed to the center of the physical space */
};
enum EnvironmentBlendMode {
diff --git a/servers/xr_server.cpp b/servers/xr_server.cpp
index dae342a037..0bc8dbee18 100644
--- a/servers/xr_server.cpp
+++ b/servers/xr_server.cpp
@@ -129,12 +129,6 @@ void XRServer::center_on_hmd(RotationMode p_rotation_mode, bool p_keep_height) {
return;
}
- if (primary_interface->get_play_area_mode() == XRInterface::XR_PLAY_AREA_STAGE) {
- // center_on_hmd is not available in this mode
- reference_frame = Transform3D();
- return;
- }
-
// clear our current reference frame or we'll end up double adjusting it
reference_frame = Transform3D();