diff options
35 files changed, 511 insertions, 94 deletions
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index ba7c44e405..d0d940c47d 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1799,7 +1799,7 @@ static void _register_variant_builtin_methods() { bind_method(Vector2, dot, sarray("with"), varray()); bind_method(Vector2, slide, sarray("n"), varray()); bind_method(Vector2, bounce, sarray("n"), varray()); - bind_method(Vector2, reflect, sarray("n"), varray()); + bind_method(Vector2, reflect, sarray("line"), varray()); bind_method(Vector2, cross, sarray("with"), varray()); bind_method(Vector2, abs, sarray(), varray()); bind_method(Vector2, sign, sarray(), varray()); @@ -1896,7 +1896,7 @@ static void _register_variant_builtin_methods() { bind_method(Vector3, project, sarray("b"), varray()); bind_method(Vector3, slide, sarray("n"), varray()); bind_method(Vector3, bounce, sarray("n"), varray()); - bind_method(Vector3, reflect, sarray("n"), varray()); + bind_method(Vector3, reflect, sarray("direction"), varray()); bind_method(Vector3, sign, sarray(), varray()); bind_method(Vector3, octahedron_encode, sarray(), varray()); bind_static_method(Vector3, octahedron_decode, sarray("uv"), varray()); diff --git a/doc/classes/Area3D.xml b/doc/classes/Area3D.xml index 4ee16d499d..8eedd3cdf2 100644 --- a/doc/classes/Area3D.xml +++ b/doc/classes/Area3D.xml @@ -124,12 +124,15 @@ </member> <member name="wind_attenuation_factor" type="float" setter="set_wind_attenuation_factor" getter="get_wind_attenuation_factor" default="0.0"> The exponential rate at which wind force decreases with distance from its origin. + [b]Note:[/b] This wind force only applies to [SoftBody3D] nodes. Other physics bodies are currently not affected by wind. </member> <member name="wind_force_magnitude" type="float" setter="set_wind_force_magnitude" getter="get_wind_force_magnitude" default="0.0"> The magnitude of area-specific wind force. + [b]Note:[/b] This wind force only applies to [SoftBody3D] nodes. Other physics bodies are currently not affected by wind. </member> <member name="wind_source_path" type="NodePath" setter="set_wind_source_path" getter="get_wind_source_path" default="NodePath("")"> The [Node3D] which is used to specify the direction and origin of an area-specific wind force. The direction is opposite to the z-axis of the [Node3D]'s local transform, and its origin is the origin of the [Node3D]'s local transform. + [b]Note:[/b] This wind force only applies to [SoftBody3D] nodes. Other physics bodies are currently not affected by wind. </member> </members> <signals> diff --git a/doc/classes/CameraServer.xml b/doc/classes/CameraServer.xml index 983bd6cd91..020b5d887b 100644 --- a/doc/classes/CameraServer.xml +++ b/doc/classes/CameraServer.xml @@ -6,7 +6,7 @@ <description> The [CameraServer] keeps track of different cameras accessible in Godot. These are external cameras such as webcams or the cameras on your phone. It is notably used to provide AR modules with a video feed from the camera. - [b]Note:[/b] This class is currently only implemented on macOS and iOS. On other platforms, no [CameraFeed]s will be available. + [b]Note:[/b] This class is currently only implemented on macOS and iOS. To get a [CameraFeed] on iOS, the camera plugin from [url=https://github.com/godotengine/godot-ios-plugins]godot-ios-plugins[/url] is required. On other platforms, no [CameraFeed]s will be available. </description> <tutorials> </tutorials> diff --git a/doc/classes/CharacterBody2D.xml b/doc/classes/CharacterBody2D.xml index df409cb3c4..ede4d63cfc 100644 --- a/doc/classes/CharacterBody2D.xml +++ b/doc/classes/CharacterBody2D.xml @@ -30,7 +30,8 @@ <method name="get_floor_normal" qualifiers="const"> <return type="Vector2" /> <description> - Returns the surface normal of the floor at the last collision point. Only valid after calling [method move_and_slide] and when [method is_on_floor] returns [code]true[/code]. + Returns the collision normal of the floor at the last collision point. Only valid after calling [method move_and_slide] and when [method is_on_floor] returns [code]true[/code]. + [b]Warning:[/b] The collision normal is not always the same as the surface normal. </description> </method> <method name="get_last_motion" qualifiers="const"> @@ -94,7 +95,8 @@ <method name="get_wall_normal" qualifiers="const"> <return type="Vector2" /> <description> - Returns the surface normal of the wall at the last collision point. Only valid after calling [method move_and_slide] and when [method is_on_wall] returns [code]true[/code]. + Returns the collision normal of the wall at the last collision point. Only valid after calling [method move_and_slide] and when [method is_on_wall] returns [code]true[/code]. + [b]Warning:[/b] The collision normal is not always the same as the surface normal. </description> </method> <method name="is_on_ceiling" qualifiers="const"> diff --git a/doc/classes/CharacterBody3D.xml b/doc/classes/CharacterBody3D.xml index 498149b9c0..474adfc6ff 100644 --- a/doc/classes/CharacterBody3D.xml +++ b/doc/classes/CharacterBody3D.xml @@ -87,7 +87,8 @@ <method name="get_wall_normal" qualifiers="const"> <return type="Vector3" /> <description> - Returns the surface normal of the wall at the last collision point. Only valid after calling [method move_and_slide] and when [method is_on_wall] returns [code]true[/code]. + Returns the collision normal of the wall at the last collision point. Only valid after calling [method move_and_slide] and when [method is_on_wall] returns [code]true[/code]. + [b]Warning:[/b] The collision normal is not always the same as the surface normal. </description> </method> <method name="is_on_ceiling" qualifiers="const"> diff --git a/doc/classes/MeshInstance3D.xml b/doc/classes/MeshInstance3D.xml index f56b03bdc4..e5187cf7a1 100644 --- a/doc/classes/MeshInstance3D.xml +++ b/doc/classes/MeshInstance3D.xml @@ -70,6 +70,12 @@ Returns the value of the blend shape at the given [param blend_shape_idx]. Returns [code]0.0[/code] and produces an error if [member mesh] is [code]null[/code] or doesn't have a blend shape at that index. </description> </method> + <method name="get_skin_reference" qualifiers="const"> + <return type="SkinReference" /> + <description> + Returns the internal [SkinReference] containing the skeleton's [RID] attached to this RID. See also [method Resource.get_rid], [method SkinReference.get_skeleton], and [method RenderingServer.instance_attach_skeleton]. + </description> + </method> <method name="get_surface_override_material" qualifiers="const"> <return type="Material" /> <param index="0" name="surface" type="int" /> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index d9138b059c..aea4082dbe 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -795,9 +795,8 @@ <return type="void" /> <param index="0" name="node" type="Node" /> <param index="1" name="keep_groups" type="bool" default="false" /> - <param index="2" name="keep_children" type="bool" default="true" /> <description> - Replaces this node by the given [param node]. If [param keep_children] is [code]true[/code] all children of this node are moved to [param node]. + Replaces this node by the given [param node]. All children of this node are moved to [param node]. If [param keep_groups] is [code]true[/code], the [param node] is added to the same groups that the replaced node is in (see [method add_to_group]). [b]Warning:[/b] The replaced node is removed from the tree, but it is [b]not[/b] deleted. To prevent memory leaks, store a reference to the node in a variable, or use [method Object.free]. </description> diff --git a/doc/classes/PhysicsServer3D.xml b/doc/classes/PhysicsServer3D.xml index 7dcb185834..4a4a1ad025 100644 --- a/doc/classes/PhysicsServer3D.xml +++ b/doc/classes/PhysicsServer3D.xml @@ -1628,7 +1628,7 @@ Constant to set/get the priority (order of processing) of an area. </constant> <constant name="AREA_PARAM_WIND_FORCE_MAGNITUDE" value="10" enum="AreaParameter"> - Constant to set/get the magnitude of area-specific wind force. + Constant to set/get the magnitude of area-specific wind force. This wind force only applies to [SoftBody3D] nodes. Other physics bodies are currently not affected by wind. </constant> <constant name="AREA_PARAM_WIND_SOURCE" value="11" enum="AreaParameter"> Constant to set/get the 3D vector that specifies the origin from which an area-specific wind blows. diff --git a/doc/classes/SkinReference.xml b/doc/classes/SkinReference.xml index 466dbe2500..cb0c44cefa 100644 --- a/doc/classes/SkinReference.xml +++ b/doc/classes/SkinReference.xml @@ -1,8 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="SkinReference" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> + A reference-counted holder object for a skeleton RID used in the [RenderingServer]. </brief_description> <description> + An internal object containing a mapping from a [Skin] used within the context of a particular [MeshInstance3D] to refer to the skeleton's [RID] in the RenderingServer. + See also [method MeshInstance3D.get_skin_reference] and [method RenderingServer.instance_attach_skeleton]. + Note that despite the similar naming, the skeleton RID used in the [RenderingServer] does not have a direct one-to-one correspondence to a [Skeleton3D] node. + In particular, a [Skeleton3D] node with no [MeshInstance3D] children may be unknown to the [RenderingServer]. + On the other hand, a [Skeleton3D] with multiple [MeshInstance3D] nodes which each have different [member MeshInstance3D.skin] objects may have multiple SkinReference instances (and hence, multiple skeleton [RID]s). </description> <tutorials> </tutorials> @@ -10,11 +16,14 @@ <method name="get_skeleton" qualifiers="const"> <return type="RID" /> <description> + Returns the [RID] owned by this SkinReference, as returned by [method RenderingServer.skeleton_create]. </description> </method> <method name="get_skin" qualifiers="const"> <return type="Skin" /> <description> + Returns the [Skin] connected to this SkinReference. In the case of [MeshInstance3D] with no [member MeshInstance3D.skin] assigned, this will reference an internal default [Skin] owned by that [MeshInstance3D]. + Note that a single [Skin] may have more than one [SkinReference] in the case that it is shared by meshes across multiple [Skeleton3D] nodes. </description> </method> </methods> diff --git a/doc/classes/SoftBody3D.xml b/doc/classes/SoftBody3D.xml index a4d80a7c3e..195196b78c 100644 --- a/doc/classes/SoftBody3D.xml +++ b/doc/classes/SoftBody3D.xml @@ -5,6 +5,7 @@ </brief_description> <description> A deformable 3D physics mesh. Used to create elastic or deformable objects such as cloth, rubber, or other flexible materials. + Additionally, [SoftBody3D] is subject to wind forces defined in [Area3D] (see [member Area3D.wind_source_path], [member Area3D.wind_force_magnitude], and [member Area3D.wind_attenuation_factor]). [b]Note:[/b] There are many known bugs in [SoftBody3D]. Therefore, it's not recommended to use them for things that can affect gameplay (such as trampolines). </description> <tutorials> diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml index 0b39743a72..7b166a4fb0 100644 --- a/doc/classes/Vector2.xml +++ b/doc/classes/Vector2.xml @@ -110,7 +110,8 @@ <return type="Vector2" /> <param index="0" name="n" type="Vector2" /> <description> - Returns a new vector "bounced off" from a plane defined by the given normal. + Returns the vector "bounced off" from a line defined by the given normal [param n] perpendicular to the line. + [b]Note:[/b] [method bounce] performs the operation that most engines and frameworks call [code skip-lint]reflect()[/code]. </description> </method> <method name="ceil" qualifiers="const"> @@ -321,9 +322,10 @@ </method> <method name="reflect" qualifiers="const"> <return type="Vector2" /> - <param index="0" name="n" type="Vector2" /> + <param index="0" name="line" type="Vector2" /> <description> - Returns the result of reflecting the vector from a line defined by the given direction vector [param n]. + Returns the result of reflecting the vector from a line defined by the given direction vector [param line]. + [b]Note:[/b] [method reflect] differs from what other engines and frameworks call [code skip-lint]reflect()[/code]. In other engines, [code skip-lint]reflect()[/code] takes a normal direction which is a direction perpendicular to the line. In Godot, you specify the direction of the line directly. See also [method bounce] which does what most engines call [code skip-lint]reflect()[/code]. </description> </method> <method name="rotated" qualifiers="const"> diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml index af1383fe22..031d91af78 100644 --- a/doc/classes/Vector3.xml +++ b/doc/classes/Vector3.xml @@ -86,7 +86,8 @@ <return type="Vector3" /> <param index="0" name="n" type="Vector3" /> <description> - Returns the vector "bounced off" from a plane defined by the given normal. + Returns the vector "bounced off" from a plane defined by the given normal [param n]. + [b]Note:[/b] [method bounce] performs the operation that most engines and frameworks call [code skip-lint]reflect()[/code]. </description> </method> <method name="ceil" qualifiers="const"> @@ -306,9 +307,10 @@ </method> <method name="reflect" qualifiers="const"> <return type="Vector3" /> - <param index="0" name="n" type="Vector3" /> + <param index="0" name="direction" type="Vector3" /> <description> - Returns the result of reflecting the vector from a plane defined by the given normal [param n]. + Returns the result of reflecting the vector from a plane defined by the given direction vector [param direction]. + [b]Note:[/b] [method reflect] differs from what other engines and frameworks call [code skip-lint]reflect()[/code]. In other engines, [code skip-lint]reflect()[/code] takes a normal direction which is a direction perpendicular to the plane. In Godot, you specify a direction parallel to the plane. See also [method bounce] which does what most engines call [code skip-lint]reflect()[/code]. </description> </method> <method name="rotated" qualifiers="const"> diff --git a/drivers/gles3/storage/light_storage.h b/drivers/gles3/storage/light_storage.h index 8107a3ad4d..b6e64c9492 100644 --- a/drivers/gles3/storage/light_storage.h +++ b/drivers/gles3/storage/light_storage.h @@ -441,6 +441,29 @@ public: virtual void light_instance_set_shadow_transform(RID p_light_instance, const Projection &p_projection, const Transform3D &p_transform, float p_far, float p_split, int p_pass, float p_shadow_texel_size, float p_bias_scale = 1.0, float p_range_begin = 0, const Vector2 &p_uv_scale = Vector2()) override; virtual void light_instance_mark_visible(RID p_light_instance) override; + virtual bool light_instance_is_shadow_visible_at_position(RID p_light_instance, const Vector3 &p_position) const override { + const LightInstance *light_instance = light_instance_owner.get_or_null(p_light_instance); + ERR_FAIL_NULL_V(light_instance, false); + const Light *light = light_owner.get_or_null(light_instance->light); + ERR_FAIL_NULL_V(light, false); + + if (!light->shadow) { + return false; + } + + if (!light->distance_fade) { + return true; + } + + real_t distance = p_position.distance_to(light_instance->transform.origin); + + if (distance > light->distance_fade_shadow + light->distance_fade_length) { + return false; + } + + return true; + } + _FORCE_INLINE_ RID light_instance_get_base_light(RID p_light_instance) { LightInstance *li = light_instance_owner.get_or_null(p_light_instance); return li->light; diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index 72225fd454..bdc6504417 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -722,8 +722,7 @@ bool EditorData::check_and_update_scene(int p_idx) { new_scene->set_scene_file_path(edited_scene[p_idx].root->get_scene_file_path()); Node *old_root = edited_scene[p_idx].root; - edited_scene.write[p_idx].root = new_scene; - old_root->replace_by(new_scene, false, false); + EditorNode::get_singleton()->set_edited_scene(new_scene); memdelete(old_root); edited_scene.write[p_idx].selection = new_selection; diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index cd498ce089..b3fc87e2c8 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -3562,8 +3562,11 @@ void EditorHelpHighlighter::reset_cache() { } EditorHelpHighlighter::EditorHelpHighlighter() { + const Color text_color = EDITOR_GET("text_editor/theme/highlighting/text_color"); + #ifdef MODULE_GDSCRIPT_ENABLED TextEdit *gdscript_text_edit = memnew(TextEdit); + gdscript_text_edit->add_theme_color_override("font_color", text_color); Ref<GDScript> gdscript; gdscript.instantiate(); @@ -3580,6 +3583,7 @@ EditorHelpHighlighter::EditorHelpHighlighter() { #ifdef MODULE_MONO_ENABLED TextEdit *csharp_text_edit = memnew(TextEdit); + csharp_text_edit->add_theme_color_override("font_color", text_color); // See GH-89610. //Ref<CSharpScript> csharp; diff --git a/editor/editor_layouts_dialog.cpp b/editor/editor_layouts_dialog.cpp index db6fa2c035..52047c569d 100644 --- a/editor/editor_layouts_dialog.cpp +++ b/editor/editor_layouts_dialog.cpp @@ -31,8 +31,6 @@ #include "editor_layouts_dialog.h" #include "core/io/config_file.h" -#include "core/object/class_db.h" -#include "core/os/keyboard.h" #include "editor/editor_settings.h" #include "editor/themes/editor_scale.h" #include "scene/gui/item_list.h" @@ -60,7 +58,7 @@ void EditorLayoutsDialog::_update_ok_disable_state() { if (layout_names->is_anything_selected()) { get_ok_button()->set_disabled(false); } else { - get_ok_button()->set_disabled(!name->is_visible() || name->get_text().is_empty()); + get_ok_button()->set_disabled(!name->is_visible() || name->get_text().strip_edges().is_empty()); } } @@ -80,8 +78,8 @@ void EditorLayoutsDialog::ok_pressed() { for (int i = 0; i < selected_items.size(); ++i) { emit_signal(SNAME("name_confirmed"), layout_names->get_item_text(selected_items[i])); } - } else if (name->is_visible() && !name->get_text().is_empty()) { - emit_signal(SNAME("name_confirmed"), name->get_text()); + } else if (name->is_visible() && !name->get_text().strip_edges().is_empty()) { + emit_signal(SNAME("name_confirmed"), name->get_text().strip_edges()); } } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 0e6c814b44..26ba43cac5 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -2136,10 +2136,6 @@ void EditorNode::_dialog_action(String p_file) { } break; case SETTINGS_LAYOUT_DELETE: { - if (p_file.is_empty()) { - return; - } - Ref<ConfigFile> config; config.instantiate(); Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config()); @@ -3674,9 +3670,7 @@ void EditorNode::set_edited_scene(Node *p_scene) { if (old_edited_scene_root->get_parent() == scene_root) { scene_root->remove_child(old_edited_scene_root); } - if (old_edited_scene_root->is_connected("replacing_by", callable_mp(this, &EditorNode::set_edited_scene))) { - old_edited_scene_root->disconnect(SNAME("replacing_by"), callable_mp(this, &EditorNode::set_edited_scene)); - } + old_edited_scene_root->disconnect(SNAME("replacing_by"), callable_mp(this, &EditorNode::set_edited_scene)); } get_editor_data().set_edited_scene_root(p_scene); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 2087c8cee6..3b6ce8d396 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -2531,6 +2531,14 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } } break; + case FILE_COPY_ABSOLUTE_PATH: { + if (!p_selected.is_empty()) { + const String &fpath = p_selected[0]; + const String absolute_path = ProjectSettings::get_singleton()->globalize_path(fpath); + DisplayServer::get_singleton()->clipboard_set(absolute_path); + } + } break; + case FILE_COPY_UID: { if (!p_selected.is_empty()) { ResourceUID::ID uid = ResourceLoader::get_resource_uid(p_selected[0]); @@ -3193,6 +3201,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect if (p_paths.size() == 1) { p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("ActionCopy")), ED_GET_SHORTCUT("filesystem_dock/copy_path"), FILE_COPY_PATH); + p_popup->add_shortcut(ED_GET_SHORTCUT("filesystem_dock/copy_absolute_path"), FILE_COPY_ABSOLUTE_PATH); if (ResourceLoader::get_resource_uid(p_paths[0]) != ResourceUID::INVALID_ID) { p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Instance")), ED_GET_SHORTCUT("filesystem_dock/copy_uid"), FILE_COPY_UID); } @@ -3481,6 +3490,8 @@ void FileSystemDock::_tree_gui_input(Ref<InputEvent> p_event) { _tree_rmb_option(FILE_DUPLICATE); } else if (ED_IS_SHORTCUT("filesystem_dock/copy_path", p_event)) { _tree_rmb_option(FILE_COPY_PATH); + } else if (ED_IS_SHORTCUT("filesystem_dock/copy_absolute_path", p_event)) { + _tree_rmb_option(FILE_COPY_ABSOLUTE_PATH); } else if (ED_IS_SHORTCUT("filesystem_dock/copy_uid", p_event)) { _tree_rmb_option(FILE_COPY_UID); } else if (ED_IS_SHORTCUT("filesystem_dock/delete", p_event)) { @@ -3553,6 +3564,8 @@ void FileSystemDock::_file_list_gui_input(Ref<InputEvent> p_event) { _file_list_rmb_option(FILE_DUPLICATE); } else if (ED_IS_SHORTCUT("filesystem_dock/copy_path", p_event)) { _file_list_rmb_option(FILE_COPY_PATH); + } else if (ED_IS_SHORTCUT("filesystem_dock/copy_absolute_path", p_event)) { + _file_list_rmb_option(FILE_COPY_ABSOLUTE_PATH); } else if (ED_IS_SHORTCUT("filesystem_dock/delete", p_event)) { _file_list_rmb_option(FILE_REMOVE); } else if (ED_IS_SHORTCUT("filesystem_dock/rename", p_event)) { @@ -3856,6 +3869,7 @@ FileSystemDock::FileSystemDock() { // `KeyModifierMask::CMD_OR_CTRL | Key::C` conflicts with other editor shortcuts. ED_SHORTCUT("filesystem_dock/copy_path", TTR("Copy Path"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::C); + ED_SHORTCUT("filesystem_dock/copy_absolute_path", TTR("Copy Absolute Path")); ED_SHORTCUT("filesystem_dock/copy_uid", TTR("Copy UID")); ED_SHORTCUT("filesystem_dock/duplicate", TTR("Duplicate..."), KeyModifierMask::CMD_OR_CTRL | Key::D); ED_SHORTCUT("filesystem_dock/delete", TTR("Delete"), Key::KEY_DELETE); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index b950075928..c9fe3a8baf 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -129,6 +129,7 @@ private: FILE_OPEN_EXTERNAL, FILE_OPEN_IN_TERMINAL, FILE_COPY_PATH, + FILE_COPY_ABSOLUTE_PATH, FILE_COPY_UID, FOLDER_EXPAND_ALL, FOLDER_COLLAPSE_ALL, diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected index bb10b422dc..9cd732813e 100644 --- a/misc/extension_api_validation/4.2-stable.expected +++ b/misc/extension_api_validation/4.2-stable.expected @@ -241,13 +241,6 @@ Validate extension JSON: Error: Field 'classes/AcceptDialog/methods/remove_butto Changed argument type to the more specific one actually expected by the method. Compatibility method registered. -GH-89992 --------- -Validate extension JSON: Error: Field 'classes/Node/methods/replace_by/arguments': size changed value in new API, from 2 to 3. - -Added optional argument to prevent children to be reparented during replace_by. Compatibility method registered. - - GH-88047 -------- Validate extension JSON: Error: Field 'classes/AStar2D/methods/get_id_path/arguments': size changed value in new API, from 2 to 3. diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 5438f5020c..1fe402341b 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -1204,10 +1204,12 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) { depth_views[i].subImage.imageRect.offset.y = 0; depth_views[i].subImage.imageRect.extent.width = main_swapchain_size.width; depth_views[i].subImage.imageRect.extent.height = main_swapchain_size.height; + // OpenXR spec says that: minDepth < maxDepth. depth_views[i].minDepth = 0.0; depth_views[i].maxDepth = 1.0; - depth_views[i].nearZ = 0.01; // Near and far Z will be set to the correct values in fill_projection_matrix - depth_views[i].farZ = 100.0; + // But we can reverse near and far for reverse-Z. + depth_views[i].nearZ = 100.0; // Near and far Z will be set to the correct values in fill_projection_matrix + depth_views[i].farZ = 0.01; } }; @@ -1802,8 +1804,9 @@ bool OpenXRAPI::get_view_projection(uint32_t p_view, double p_z_near, double p_z // if we're using depth views, make sure we update our near and far there... if (depth_views != nullptr) { for (uint32_t i = 0; i < view_count; i++) { - depth_views[i].nearZ = p_z_near; - depth_views[i].farZ = p_z_far; + // As we are using reverse-Z these need to be flipped. + depth_views[i].nearZ = p_z_far; + depth_views[i].farZ = p_z_near; } } diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index 8635240655..616fb18d53 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -204,6 +204,10 @@ Ref<Skin> MeshInstance3D::get_skin() const { return skin; } +Ref<SkinReference> MeshInstance3D::get_skin_reference() const { + return skin_ref; +} + void MeshInstance3D::set_skeleton_path(const NodePath &p_skeleton) { skeleton_path = p_skeleton; if (!is_inside_tree()) { @@ -518,6 +522,7 @@ void MeshInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_skeleton_path"), &MeshInstance3D::get_skeleton_path); ClassDB::bind_method(D_METHOD("set_skin", "skin"), &MeshInstance3D::set_skin); ClassDB::bind_method(D_METHOD("get_skin"), &MeshInstance3D::get_skin); + ClassDB::bind_method(D_METHOD("get_skin_reference"), &MeshInstance3D::get_skin_reference); ClassDB::bind_method(D_METHOD("get_surface_override_material_count"), &MeshInstance3D::get_surface_override_material_count); ClassDB::bind_method(D_METHOD("set_surface_override_material", "surface", "material"), &MeshInstance3D::set_surface_override_material); diff --git a/scene/3d/mesh_instance_3d.h b/scene/3d/mesh_instance_3d.h index add6bfe15e..d6ae1291d3 100644 --- a/scene/3d/mesh_instance_3d.h +++ b/scene/3d/mesh_instance_3d.h @@ -75,6 +75,8 @@ public: void set_skeleton_path(const NodePath &p_skeleton); NodePath get_skeleton_path(); + Ref<SkinReference> get_skin_reference() const; + int get_blend_shape_count() const; int find_blend_shape_by_name(const StringName &p_name); float get_blend_shape_value(int p_blend_shape) const; diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 1baf71dd07..f8bbedde09 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -835,10 +835,13 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o // Draw dropcap. int dc_lines = l.text_buf->get_dropcap_lines(); float h_off = l.text_buf->get_dropcap_size().x; - if (l.dc_ol_size > 0) { - l.text_buf->draw_dropcap_outline(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_ol_size, l.dc_ol_color); + bool skip_dc = (trim_chars && l.char_offset > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs)); + if (!skip_dc) { + if (l.dc_ol_size > 0) { + l.text_buf->draw_dropcap_outline(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_ol_size, l.dc_ol_color); + } + l.text_buf->draw_dropcap(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_color); } - l.text_buf->draw_dropcap(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_color); int line_count = 0; Size2 ctrl_size = get_size(); @@ -894,7 +897,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } break; } - if (!prefix.is_empty() && line == 0) { + bool skip_prefix = (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING && l.char_offset == visible_characters) || (trim_chars && l.char_offset > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs)); + if (!prefix.is_empty() && line == 0 && !skip_prefix) { Ref<Font> font = theme_cache.normal_font; int font_size = theme_cache.normal_font_size; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index beb2583b61..5c5049759f 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -29,7 +29,6 @@ /**************************************************************************/ #include "node.h" -#include "node.compat.inc" #include "core/config/project_settings.h" #include "core/core_string_names.h" @@ -3006,7 +3005,7 @@ static void find_owned_by(Node *p_by, Node *p_node, List<Node *> *p_owned) { } } -void Node::replace_by(Node *p_node, bool p_keep_groups, bool p_keep_children) { +void Node::replace_by(Node *p_node, bool p_keep_groups) { ERR_THREAD_GUARD ERR_FAIL_NULL(p_node); ERR_FAIL_COND(p_node->data.parent); @@ -3027,13 +3026,13 @@ void Node::replace_by(Node *p_node, bool p_keep_groups, bool p_keep_children) { _replace_connections_target(p_node); if (data.owner) { - if (p_keep_children) { - for (int i = 0; i < get_child_count(); i++) { - find_owned_by(data.owner, get_child(i), &owned_by_owner); - } + for (int i = 0; i < get_child_count(); i++) { + find_owned_by(data.owner, get_child(i), &owned_by_owner); } + _clean_up_owner(); } + Node *parent = data.parent; int index_in_parent = get_index(false); @@ -3045,33 +3044,31 @@ void Node::replace_by(Node *p_node, bool p_keep_groups, bool p_keep_children) { emit_signal(SNAME("replacing_by"), p_node); - if (p_keep_children) { - while (get_child_count()) { - Node *child = get_child(0); - remove_child(child); - if (!child->is_owned_by_parent()) { - // add the custom children to the p_node - Node *child_owner = child->get_owner() == this ? p_node : child->get_owner(); - child->set_owner(nullptr); - p_node->add_child(child); - child->set_owner(child_owner); - } + while (get_child_count()) { + Node *child = get_child(0); + remove_child(child); + if (!child->is_owned_by_parent()) { + // add the custom children to the p_node + Node *child_owner = child->get_owner() == this ? p_node : child->get_owner(); + child->set_owner(nullptr); + p_node->add_child(child); + child->set_owner(child_owner); } + } - for (Node *E : owned) { - if (E->data.owner != p_node) { - E->set_owner(p_node); - } + p_node->set_owner(owner); + for (Node *E : owned) { + if (E->data.owner != p_node) { + E->set_owner(p_node); } + } - for (Node *E : owned_by_owner) { - if (E->data.owner != owner) { - E->set_owner(owner); - } + for (Node *E : owned_by_owner) { + if (E->data.owner != owner) { + E->set_owner(owner); } } - p_node->set_owner(owner); p_node->set_scene_file_path(get_scene_file_path()); } @@ -3598,7 +3595,7 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("create_tween"), &Node::create_tween); ClassDB::bind_method(D_METHOD("duplicate", "flags"), &Node::duplicate, DEFVAL(DUPLICATE_USE_INSTANTIATION | DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS)); - ClassDB::bind_method(D_METHOD("replace_by", "node", "keep_groups", "keep_children"), &Node::replace_by, DEFVAL(false), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("replace_by", "node", "keep_groups"), &Node::replace_by, DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_scene_instance_load_placeholder", "load_placeholder"), &Node::set_scene_instance_load_placeholder); ClassDB::bind_method(D_METHOD("get_scene_instance_load_placeholder"), &Node::get_scene_instance_load_placeholder); diff --git a/scene/main/node.h b/scene/main/node.h index 99def10338..f49eeec9cd 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -310,11 +310,6 @@ private: Variant _call_thread_safe_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); protected: -#ifndef DISABLE_DEPRECATED - void _replace_by_bind_compat_89992(Node *p_node, bool p_keep_data = false); - static void _bind_compatibility_methods(); -#endif // DISABLE_DEPRECATED - void _block() { data.blocked++; } void _unblock() { data.blocked--; } @@ -634,7 +629,7 @@ public: return binds; } - void replace_by(Node *p_node, bool p_keep_groups = false, bool p_keep_children = true); + void replace_by(Node *p_node, bool p_keep_data = false); void set_process_mode(ProcessMode p_mode); ProcessMode get_process_mode() const; diff --git a/servers/rendering/dummy/storage/light_storage.h b/servers/rendering/dummy/storage/light_storage.h index a76305cdaa..0a9602b603 100644 --- a/servers/rendering/dummy/storage/light_storage.h +++ b/servers/rendering/dummy/storage/light_storage.h @@ -91,6 +91,7 @@ public: void light_instance_set_aabb(RID p_light_instance, const AABB &p_aabb) override {} void light_instance_set_shadow_transform(RID p_light_instance, const Projection &p_projection, const Transform3D &p_transform, float p_far, float p_split, int p_pass, float p_shadow_texel_size, float p_bias_scale = 1.0, float p_range_begin = 0, const Vector2 &p_uv_scale = Vector2()) override {} void light_instance_mark_visible(RID p_light_instance) override {} + virtual bool light_instance_is_shadow_visible_at_position(RID p_light_instance, const Vector3 &p_position) const override { return false; } /* PROBE API */ virtual RID reflection_probe_allocate() override { return RID(); } diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.h b/servers/rendering/renderer_rd/storage_rd/light_storage.h index b3d6bf5254..f152cc5dae 100644 --- a/servers/rendering/renderer_rd/storage_rd/light_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/light_storage.h @@ -590,6 +590,29 @@ public: virtual void light_instance_set_shadow_transform(RID p_light_instance, const Projection &p_projection, const Transform3D &p_transform, float p_far, float p_split, int p_pass, float p_shadow_texel_size, float p_bias_scale = 1.0, float p_range_begin = 0, const Vector2 &p_uv_scale = Vector2()) override; virtual void light_instance_mark_visible(RID p_light_instance) override; + virtual bool light_instance_is_shadow_visible_at_position(RID p_light_instance, const Vector3 &p_position) const override { + const LightInstance *light_instance = light_instance_owner.get_or_null(p_light_instance); + ERR_FAIL_NULL_V(light_instance, false); + const Light *light = light_owner.get_or_null(light_instance->light); + ERR_FAIL_NULL_V(light, false); + + if (!light->shadow) { + return false; + } + + if (!light->distance_fade) { + return true; + } + + real_t distance = p_position.distance_to(light_instance->transform.origin); + + if (distance > light->distance_fade_shadow + light->distance_fade_length) { + return false; + } + + return true; + } + _FORCE_INLINE_ RID light_instance_get_base_light(RID p_light_instance) { LightInstance *li = light_instance_owner.get_or_null(p_light_instance); return li->light; diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp index c5d74d395f..b7934cb3de 100644 --- a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp +++ b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp @@ -130,9 +130,10 @@ void RenderSceneBuffersRD::cleanup() { named_textures.clear(); // Clear weight_buffer / blur textures. - for (const WeightBuffers &weight_buffer : weight_buffers) { + for (WeightBuffers &weight_buffer : weight_buffers) { if (weight_buffer.weight.is_valid()) { RD::get_singleton()->free(weight_buffer.weight); + weight_buffer.weight = RID(); } } } diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp index b33de9d6f4..96c0479ac3 100644 --- a/servers/rendering/renderer_scene_cull.cpp +++ b/servers/rendering/renderer_scene_cull.cpp @@ -1029,7 +1029,6 @@ inline bool is_geometry_instance(RenderingServer::InstanceType p_type) { void RendererSceneCull::instance_set_custom_aabb(RID p_instance, AABB p_aabb) { Instance *instance = instance_owner.get_or_null(p_instance); ERR_FAIL_NULL(instance); - ERR_FAIL_COND(!is_geometry_instance(instance->base_type)); if (p_aabb != AABB()) { // Set custom AABB @@ -3029,6 +3028,7 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c light_culler->prepare_camera(p_camera_data->main_transform, p_camera_data->main_projection); Scenario *scenario = scenario_owner.get_or_null(p_scenario); + Vector3 camera_position = p_camera_data->main_transform.origin; ERR_FAIL_COND(p_render_buffers.is_null()); @@ -3038,7 +3038,7 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c if (p_reflection_probe.is_null()) { //no rendering code here, this is only to set up what needs to be done, request regions, etc. - scene_render->sdfgi_update(p_render_buffers, p_environment, p_camera_data->main_transform.origin); //update conditions for SDFGI (whether its used or not) + scene_render->sdfgi_update(p_render_buffers, p_environment, camera_position); //update conditions for SDFGI (whether its used or not) } RENDER_TIMESTAMP("Update Visibility Dependencies"); @@ -3051,7 +3051,7 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c VisibilityCullData visibility_cull_data; visibility_cull_data.scenario = scenario; visibility_cull_data.viewport_mask = scenario->viewport_visibility_masks[p_viewport]; - visibility_cull_data.camera_position = p_camera_data->main_transform.origin; + visibility_cull_data.camera_position = camera_position; for (int i = scenario->instance_visibility.get_bin_count() - 1; i > 0; i--) { // We skip bin 0 visibility_cull_data.cull_offset = scenario->instance_visibility.get_bin_start(i); @@ -3220,16 +3220,20 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c } } - // Positional Shadowss + // Positional Shadows for (uint32_t i = 0; i < (uint32_t)scene_cull_result.lights.size(); i++) { Instance *ins = scene_cull_result.lights[i]; - if (!p_shadow_atlas.is_valid() || !RSG::light_storage->light_has_shadow(ins->base)) { + if (!p_shadow_atlas.is_valid()) { continue; } InstanceLightData *light = static_cast<InstanceLightData *>(ins->base_data); + if (!RSG::light_storage->light_instance_is_shadow_visible_at_position(light->instance, camera_position)) { + continue; + } + float coverage = 0.f; { //compute coverage diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index fef1a205d6..8fbad346a4 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -8361,7 +8361,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f } } #endif // DEBUG_ENABLED - if (String(shader_type_identifier) != "spatial") { + if (shader_type_identifier != StringName() && String(shader_type_identifier) != "spatial") { _set_error(vformat(RTR("Uniform instances are not yet implemented for '%s' shaders."), shader_type_identifier)); return ERR_PARSE_ERROR; } @@ -8848,7 +8848,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f _set_error(RTR("'hint_normal_roughness_texture' is only available when using the Forward+ backend.")); return ERR_PARSE_ERROR; } - if (String(shader_type_identifier) != "spatial") { + if (shader_type_identifier != StringName() && String(shader_type_identifier) != "spatial") { _set_error(vformat(RTR("'hint_normal_roughness_texture' is not supported in '%s' shaders."), shader_type_identifier)); return ERR_PARSE_ERROR; } @@ -8857,7 +8857,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f new_hint = ShaderNode::Uniform::HINT_DEPTH_TEXTURE; --texture_uniforms; --texture_binding; - if (String(shader_type_identifier) != "spatial") { + if (shader_type_identifier != StringName() && String(shader_type_identifier) != "spatial") { _set_error(vformat(RTR("'hint_depth_texture' is not supported in '%s' shaders."), shader_type_identifier)); return ERR_PARSE_ERROR; } diff --git a/servers/rendering/storage/light_storage.h b/servers/rendering/storage/light_storage.h index d439598f3d..6a0adfa596 100644 --- a/servers/rendering/storage/light_storage.h +++ b/servers/rendering/storage/light_storage.h @@ -98,6 +98,7 @@ public: virtual bool light_instances_can_render_shadow_cube() const { return true; } + virtual bool light_instance_is_shadow_visible_at_position(RID p_light, const Vector3 &p_position) const = 0; /* PROBE API */ diff --git a/scene/main/node.compat.inc b/tests/core/io/test_ip.h index 69ece1a40d..7b5583faa0 100644 --- a/scene/main/node.compat.inc +++ b/tests/core/io/test_ip.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* node.compat.inc */ +/* test_ip.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,14 +28,24 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef DISABLE_DEPRECATED +#ifndef TEST_IP_H +#define TEST_IP_H -void Node::_replace_by_bind_compat_89992(Node *p_node, bool p_keep_data) { - replace_by(p_node, p_keep_data, true); -} +#include "core/io/ip.h" + +#include "tests/test_macros.h" -void Node::_bind_compatibility_methods() { - ClassDB::bind_compatibility_method(D_METHOD("replace_by", "node", "keep_groups"), &Node::_replace_by_bind_compat_89992, DEFVAL(false)); +namespace TestIP { + +TEST_CASE("[IP] resolve_hostname") { + for (int x = 0; x < 1000; x++) { + IPAddress IPV4 = IP::get_singleton()->resolve_hostname("localhost", IP::TYPE_IPV4); + CHECK("127.0.0.1" == String(IPV4)); + IPAddress IPV6 = IP::get_singleton()->resolve_hostname("localhost", IP::TYPE_IPV6); + CHECK("0:0:0:0:0:0:0:1" == String(IPV6)); + } } -#endif +} // namespace TestIP + +#endif // TEST_IP_H diff --git a/tests/scene/test_camera_2d.h b/tests/scene/test_camera_2d.h new file mode 100644 index 0000000000..f03a4aed53 --- /dev/null +++ b/tests/scene/test_camera_2d.h @@ -0,0 +1,318 @@ +/**************************************************************************/ +/* test_camera_2d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_CAMERA_2D_H +#define TEST_CAMERA_2D_H + +#include "scene/2d/camera_2d.h" +#include "scene/main/viewport.h" +#include "scene/main/window.h" +#include "tests/test_macros.h" + +namespace TestCamera2D { + +TEST_CASE("[SceneTree][Camera2D] Getters and setters") { + Camera2D *test_camera = memnew(Camera2D); + + SUBCASE("AnchorMode") { + test_camera->set_anchor_mode(Camera2D::AnchorMode::ANCHOR_MODE_FIXED_TOP_LEFT); + CHECK(test_camera->get_anchor_mode() == Camera2D::AnchorMode::ANCHOR_MODE_FIXED_TOP_LEFT); + test_camera->set_anchor_mode(Camera2D::AnchorMode::ANCHOR_MODE_DRAG_CENTER); + CHECK(test_camera->get_anchor_mode() == Camera2D::AnchorMode::ANCHOR_MODE_DRAG_CENTER); + } + + SUBCASE("ProcessCallback") { + test_camera->set_process_callback(Camera2D::Camera2DProcessCallback::CAMERA2D_PROCESS_PHYSICS); + CHECK(test_camera->get_process_callback() == Camera2D::Camera2DProcessCallback::CAMERA2D_PROCESS_PHYSICS); + test_camera->set_process_callback(Camera2D::Camera2DProcessCallback::CAMERA2D_PROCESS_IDLE); + CHECK(test_camera->get_process_callback() == Camera2D::Camera2DProcessCallback::CAMERA2D_PROCESS_IDLE); + } + + SUBCASE("Drag") { + constexpr float drag_left_margin = 0.8f; + constexpr float drag_top_margin = 0.8f; + constexpr float drag_right_margin = 0.8f; + constexpr float drag_bottom_margin = 0.8f; + constexpr float drag_horizontal_offset1 = 0.5f; + constexpr float drag_horizontal_offset2 = -0.5f; + constexpr float drag_vertical_offset1 = 0.5f; + constexpr float drag_vertical_offset2 = -0.5f; + test_camera->set_drag_margin(SIDE_LEFT, drag_left_margin); + CHECK(test_camera->get_drag_margin(SIDE_LEFT) == drag_left_margin); + test_camera->set_drag_margin(SIDE_TOP, drag_top_margin); + CHECK(test_camera->get_drag_margin(SIDE_TOP) == drag_top_margin); + test_camera->set_drag_margin(SIDE_RIGHT, drag_right_margin); + CHECK(test_camera->get_drag_margin(SIDE_RIGHT) == drag_right_margin); + test_camera->set_drag_margin(SIDE_BOTTOM, drag_bottom_margin); + CHECK(test_camera->get_drag_margin(SIDE_BOTTOM) == drag_bottom_margin); + test_camera->set_drag_horizontal_enabled(true); + CHECK(test_camera->is_drag_horizontal_enabled()); + test_camera->set_drag_horizontal_enabled(false); + CHECK_FALSE(test_camera->is_drag_horizontal_enabled()); + test_camera->set_drag_horizontal_offset(drag_horizontal_offset1); + CHECK(test_camera->get_drag_horizontal_offset() == drag_horizontal_offset1); + test_camera->set_drag_horizontal_offset(drag_horizontal_offset2); + CHECK(test_camera->get_drag_horizontal_offset() == drag_horizontal_offset2); + test_camera->set_drag_vertical_enabled(true); + CHECK(test_camera->is_drag_vertical_enabled()); + test_camera->set_drag_vertical_enabled(false); + CHECK_FALSE(test_camera->is_drag_vertical_enabled()); + test_camera->set_drag_vertical_offset(drag_vertical_offset1); + CHECK(test_camera->get_drag_vertical_offset() == drag_vertical_offset1); + test_camera->set_drag_vertical_offset(drag_vertical_offset2); + CHECK(test_camera->get_drag_vertical_offset() == drag_vertical_offset2); + } + + SUBCASE("Drawing") { + test_camera->set_margin_drawing_enabled(true); + CHECK(test_camera->is_margin_drawing_enabled()); + test_camera->set_margin_drawing_enabled(false); + CHECK_FALSE(test_camera->is_margin_drawing_enabled()); + test_camera->set_limit_drawing_enabled(true); + CHECK(test_camera->is_limit_drawing_enabled()); + test_camera->set_limit_drawing_enabled(false); + CHECK_FALSE(test_camera->is_limit_drawing_enabled()); + test_camera->set_screen_drawing_enabled(true); + CHECK(test_camera->is_screen_drawing_enabled()); + test_camera->set_screen_drawing_enabled(false); + CHECK_FALSE(test_camera->is_screen_drawing_enabled()); + } + + SUBCASE("Enabled") { + test_camera->set_enabled(true); + CHECK(test_camera->is_enabled()); + test_camera->set_enabled(false); + CHECK_FALSE(test_camera->is_enabled()); + } + + SUBCASE("Rotation") { + constexpr float rotation_smoothing_speed = 20.0f; + test_camera->set_ignore_rotation(true); + CHECK(test_camera->is_ignoring_rotation()); + test_camera->set_ignore_rotation(false); + CHECK_FALSE(test_camera->is_ignoring_rotation()); + test_camera->set_rotation_smoothing_enabled(true); + CHECK(test_camera->is_rotation_smoothing_enabled()); + test_camera->set_rotation_smoothing_speed(rotation_smoothing_speed); + CHECK(test_camera->get_rotation_smoothing_speed() == rotation_smoothing_speed); + } + + SUBCASE("Zoom") { + const Vector2 zoom = Vector2(4, 4); + test_camera->set_zoom(zoom); + CHECK(test_camera->get_zoom() == zoom); + } + + SUBCASE("Offset") { + const Vector2 offset = Vector2(100, 100); + test_camera->set_offset(offset); + CHECK(test_camera->get_offset() == offset); + } + + SUBCASE("Limit") { + constexpr int limit_left = 100; + constexpr int limit_top = 100; + constexpr int limit_right = 100; + constexpr int limit_bottom = 100; + test_camera->set_limit_smoothing_enabled(true); + CHECK(test_camera->is_limit_smoothing_enabled()); + test_camera->set_limit_smoothing_enabled(false); + CHECK_FALSE(test_camera->is_limit_smoothing_enabled()); + test_camera->set_limit(SIDE_LEFT, limit_left); + CHECK(test_camera->get_limit(SIDE_LEFT) == limit_left); + test_camera->set_limit(SIDE_TOP, limit_top); + CHECK(test_camera->get_limit(SIDE_TOP) == limit_top); + test_camera->set_limit(SIDE_RIGHT, limit_right); + CHECK(test_camera->get_limit(SIDE_RIGHT) == limit_right); + test_camera->set_limit(SIDE_BOTTOM, limit_bottom); + CHECK(test_camera->get_limit(SIDE_BOTTOM) == limit_bottom); + } + + SUBCASE("Position") { + constexpr float smoothing_speed = 20.0f; + test_camera->set_position_smoothing_enabled(true); + CHECK(test_camera->is_position_smoothing_enabled()); + test_camera->set_position_smoothing_speed(smoothing_speed); + CHECK(test_camera->get_position_smoothing_speed() == smoothing_speed); + } + + memdelete(test_camera); +} + +TEST_CASE("[SceneTree][Camera2D] Camera positioning") { + SubViewport *mock_viewport = memnew(SubViewport); + Camera2D *test_camera = memnew(Camera2D); + + mock_viewport->set_size(Vector2(400, 200)); + SceneTree::get_singleton()->get_root()->add_child(mock_viewport); + mock_viewport->add_child(test_camera); + + SUBCASE("Anchor mode") { + test_camera->set_anchor_mode(Camera2D::ANCHOR_MODE_DRAG_CENTER); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 0))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + + test_camera->set_anchor_mode(Camera2D::ANCHOR_MODE_FIXED_TOP_LEFT); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(200, 100))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + } + + SUBCASE("Offset") { + test_camera->set_offset(Vector2(100, 100)); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(100, 100))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + + test_camera->set_offset(Vector2(-100, 300)); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(-100, 300))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + + test_camera->set_offset(Vector2(0, 0)); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 0))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + } + + SUBCASE("Limits") { + test_camera->set_limit(SIDE_LEFT, 100); + test_camera->set_limit(SIDE_TOP, 50); + + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(300, 150))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + + test_camera->set_limit(SIDE_LEFT, 0); + test_camera->set_limit(SIDE_TOP, 0); + + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(200, 100))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + } + + SUBCASE("Drag") { + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 0))); + + // horizontal + test_camera->set_drag_horizontal_enabled(true); + test_camera->set_drag_margin(SIDE_RIGHT, 0.5); + + test_camera->set_position(Vector2(100, 100)); + test_camera->force_update_scroll(); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 100))); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 100))); + test_camera->set_position(Vector2(101, 101)); + test_camera->force_update_scroll(); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(1, 101))); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(1, 101))); + + // test align + test_camera->set_position(Vector2(0, 0)); + test_camera->align(); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 0))); + + // vertical + test_camera->set_drag_vertical_enabled(true); + test_camera->set_drag_horizontal_enabled(false); + test_camera->set_drag_margin(SIDE_TOP, 0.3); + + test_camera->set_position(Vector2(200, -20)); + test_camera->force_update_scroll(); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(200, 0))); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(200, 0))); + test_camera->set_position(Vector2(250, -55)); + test_camera->force_update_scroll(); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(250, -25))); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(250, -25))); + } + + memdelete(test_camera); + memdelete(mock_viewport); +} + +TEST_CASE("[SceneTree][Camera2D] Transforms") { + SubViewport *mock_viewport = memnew(SubViewport); + Camera2D *test_camera = memnew(Camera2D); + + mock_viewport->set_size(Vector2(400, 200)); + SceneTree::get_singleton()->get_root()->add_child(mock_viewport); + mock_viewport->add_child(test_camera); + + SUBCASE("Default camera") { + Transform2D xform = mock_viewport->get_canvas_transform(); + // x,y are basis vectors, origin = screen center + Transform2D test_xform = Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + } + + SUBCASE("Zoom") { + test_camera->set_zoom(Vector2(0.5, 2)); + Transform2D xform = mock_viewport->get_canvas_transform(); + Transform2D test_xform = Transform2D(Vector2(0.5, 0), Vector2(0, 2), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + + test_camera->set_zoom(Vector2(10, 10)); + xform = mock_viewport->get_canvas_transform(); + test_xform = Transform2D(Vector2(10, 0), Vector2(0, 10), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + + test_camera->set_zoom(Vector2(1, 1)); + xform = mock_viewport->get_canvas_transform(); + test_xform = Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + } + + SUBCASE("Rotation") { + test_camera->set_rotation(Math_PI / 2); + Transform2D xform = mock_viewport->get_canvas_transform(); + Transform2D test_xform = Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + + test_camera->set_ignore_rotation(false); + xform = mock_viewport->get_canvas_transform(); + test_xform = Transform2D(Vector2(0, -1), Vector2(1, 0), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + + test_camera->set_rotation(-1 * Math_PI); + test_camera->force_update_scroll(); + xform = mock_viewport->get_canvas_transform(); + test_xform = Transform2D(Vector2(-1, 0), Vector2(0, -1), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + + test_camera->set_rotation(0); + test_camera->force_update_scroll(); + xform = mock_viewport->get_canvas_transform(); + test_xform = Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + } + + memdelete(test_camera); + memdelete(mock_viewport); +} + +} // namespace TestCamera2D + +#endif // TEST_CAMERA_2D_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 24eb84127b..4a389f4284 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -44,6 +44,7 @@ #include "tests/core/io/test_file_access.h" #include "tests/core/io/test_http_client.h" #include "tests/core/io/test_image.h" +#include "tests/core/io/test_ip.h" #include "tests/core/io/test_json.h" #include "tests/core/io/test_marshalls.h" #include "tests/core/io/test_pck_packer.h" @@ -101,6 +102,7 @@ #include "tests/scene/test_arraymesh.h" #include "tests/scene/test_audio_stream_wav.h" #include "tests/scene/test_bit_map.h" +#include "tests/scene/test_camera_2d.h" #include "tests/scene/test_code_edit.h" #include "tests/scene/test_color_picker.h" #include "tests/scene/test_control.h" |