summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.yml2
-rw-r--r--doc/classes/EditorPlugin.xml10
-rw-r--r--doc/classes/ProjectSettings.xml6
-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--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--editor/connections_dialog.cpp88
-rw-r--r--editor/editor_data.cpp6
-rw-r--r--editor/editor_data.h1
-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/groups_editor.cpp3
-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/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.cpp34
-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/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--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/tree.cpp20
-rw-r--r--scene/main/viewport.cpp10
-rw-r--r--scene/resources/visual_shader.cpp14
-rw-r--r--scene/resources/visual_shader.h1
-rw-r--r--servers/debugger/servers_debugger.cpp10
-rw-r--r--servers/rendering/dummy/storage/light_storage.h2
-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/texture_storage.cpp8
-rw-r--r--servers/rendering/rendering_device.cpp34
-rw-r--r--servers/rendering/rendering_server_default.h1
-rw-r--r--servers/rendering/storage/light_storage.h2
-rw-r--r--servers/rendering_server.cpp1
-rw-r--r--servers/rendering_server.h1
72 files changed, 1191 insertions, 385 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/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/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/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/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/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_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/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/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/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..1e0869e7f5 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)));
}
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/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/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/tree.cpp b/scene/gui/tree.cpp
index c67c3cd98d..11d5a01908 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -590,6 +590,8 @@ void TreeItem::set_custom_draw(int p_column, Object *p_object, const StringName
cells.write[p_column].custom_draw_obj = p_object->get_instance_id();
cells.write[p_column].custom_draw_callback = p_callback;
+
+ _changed_notify(p_column);
}
void TreeItem::set_collapsed(bool p_collapsed) {
@@ -1306,8 +1308,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 +1326,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 +1382,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 {
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/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/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/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/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/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..9ad1d8b8dc 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -401,6 +401,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_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..b395104629 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -572,6 +572,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;