diff options
54 files changed, 1127 insertions, 485 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index cb25dc9ebf..79fab50882 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -67,14 +67,6 @@ String ProjectSettings::get_resource_path() const { return resource_path; } -String ProjectSettings::get_safe_project_name() const { - String safe_name = OS::get_singleton()->get_safe_dir_name(get("application/config/name")); - if (safe_name.is_empty()) { - safe_name = "UnnamedProject"; - } - return safe_name; -} - String ProjectSettings::get_imported_files_path() const { return get_project_data_path().path_join("imported"); } @@ -930,10 +922,26 @@ Error ProjectSettings::_save_settings_text(const String &p_file, const RBMap<Str } Error ProjectSettings::_save_custom_bnd(const String &p_file) { // add other params as dictionary and array? - return save_custom(p_file); } +#ifdef TOOLS_ENABLED +bool _csproj_exists(String p_root_dir) { + Ref<DirAccess> dir = DirAccess::open(p_root_dir); + + dir->list_dir_begin(); + String file_name = dir->_get_next(); + while (file_name != "") { + if (!dir->current_is_dir() && file_name.get_extension() == "csproj") { + return true; + } + file_name = dir->_get_next(); + } + + return false; +} +#endif // TOOLS_ENABLED + Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_custom, const Vector<String> &p_custom_features, bool p_merge_with_current) { ERR_FAIL_COND_V_MSG(p_path.is_empty(), ERR_INVALID_PARAMETER, "Project settings save path cannot be empty."); @@ -952,7 +960,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust } } // Check for the existence of a csproj file. - if (FileAccess::exists(get_resource_path().path_join(get_safe_project_name() + ".csproj"))) { + if (_csproj_exists(get_resource_path())) { // If there is a csproj file, add the C# feature if it doesn't already exist. if (!project_features.has("C#")) { project_features.append("C#"); diff --git a/core/config/project_settings.h b/core/config/project_settings.h index a0249ef267..b89e6694b0 100644 --- a/core/config/project_settings.h +++ b/core/config/project_settings.h @@ -166,7 +166,6 @@ public: String get_project_data_dir_name() const; String get_project_data_path() const; String get_resource_path() const; - String get_safe_project_name() const; String get_imported_files_path() const; static ProjectSettings *get_singleton(); diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index 6010c2a2b4..e547b04d0b 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -201,7 +201,7 @@ bool InputEventWithModifiers::is_alt_pressed() const { } void InputEventWithModifiers::set_ctrl_pressed(bool p_enabled) { - ERR_FAIL_COND_MSG(command_or_control_autoremap, "Command/Control autoremaping is enabled, cannot set Control directly!"); + ERR_FAIL_COND_MSG(command_or_control_autoremap, "Command or Control autoremapping is enabled, cannot set Control directly!"); ctrl_pressed = p_enabled; emit_changed(); } @@ -211,7 +211,7 @@ bool InputEventWithModifiers::is_ctrl_pressed() const { } void InputEventWithModifiers::set_meta_pressed(bool p_enabled) { - ERR_FAIL_COND_MSG(command_or_control_autoremap, "Command/Control autoremaping is enabled, cannot set Meta directly!"); + ERR_FAIL_COND_MSG(command_or_control_autoremap, "Command or Control autoremapping is enabled, cannot set Meta directly!"); meta_pressed = p_enabled; emit_changed(); } diff --git a/core/object/object.cpp b/core/object/object.cpp index d6937539c7..c76188a2cd 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -1719,6 +1719,30 @@ uint32_t Object::get_edited_version() const { } #endif +StringName Object::get_class_name_for_extension(const GDExtension *p_library) const { + // Only return the class name per the extension if it matches the given p_library. + if (_extension && _extension->library == p_library) { + return _extension->class_name; + } + + // Extensions only have wrapper classes for classes exposed in ClassDB. + const StringName *class_name = _get_class_namev(); + if (ClassDB::is_class_exposed(*class_name)) { + return *class_name; + } + + // Find the nearest parent class that's exposed. + StringName parent_class = ClassDB::get_parent_class(*class_name); + while (parent_class != StringName()) { + if (ClassDB::is_class_exposed(parent_class)) { + return parent_class; + } + parent_class = ClassDB::get_parent_class(parent_class); + } + + return SNAME("Object"); +} + void Object::set_instance_binding(void *p_token, void *p_binding, const GDExtensionInstanceBindingCallbacks *p_callbacks) { // This is only meant to be used on creation by the binder. ERR_FAIL_COND(_instance_bindings != nullptr); diff --git a/core/object/object.h b/core/object/object.h index 6f626b0ed0..a3e9d025ea 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -800,17 +800,7 @@ public: return *_class_name_ptr; } - _FORCE_INLINE_ const StringName &get_class_name_for_extension(const GDExtension *p_library) const { - // Only return the class name per the extension if it matches the given p_library. - if (_extension && _extension->library == p_library) { - return _extension->class_name; - } - // Otherwise, return the name of the built-in class. - if (unlikely(!_class_name_ptr)) { - return *_get_class_namev(); - } - return *_class_name_ptr; - } + StringName get_class_name_for_extension(const GDExtension *p_library) const; /* IAPI */ diff --git a/doc/classes/NavigationMeshGenerator.xml b/doc/classes/NavigationMeshGenerator.xml index e10e23a5cf..f13d6b570a 100644 --- a/doc/classes/NavigationMeshGenerator.xml +++ b/doc/classes/NavigationMeshGenerator.xml @@ -14,12 +14,21 @@ <link title="Using NavigationMeshes">$DOCS_URL/tutorials/navigation/navigation_using_navigationmeshes.html</link> </tutorials> <methods> - <method name="bake"> + <method name="bake" is_deprecated="true"> <return type="void" /> <param index="0" name="navigation_mesh" type="NavigationMesh" /> <param index="1" name="root_node" type="Node" /> <description> - Bakes navigation data to the provided [param navigation_mesh] by parsing child nodes under the provided [param root_node] or a specific group of nodes for potential source geometry. The parse behavior can be controlled with the [member NavigationMesh.geometry_parsed_geometry_type] and [member NavigationMesh.geometry_source_geometry_mode] properties on the [NavigationMesh] resource. + The bake function is deprecated due to core threading changes. To upgrade existing code, first create a [NavigationMeshSourceGeometryData3D] resource. Use this resource with [method parse_source_geometry_data] to parse the SceneTree for nodes that should contribute to the navigation mesh baking. The SceneTree parsing needs to happen on the main thread. After the parsing is finished use the resource with [method bake_from_source_geometry_data] to bake a navigation mesh. + </description> + </method> + <method name="bake_from_source_geometry_data"> + <return type="void" /> + <param index="0" name="navigation_mesh" type="NavigationMesh" /> + <param index="1" name="source_geometry_data" type="NavigationMeshSourceGeometryData3D" /> + <param index="2" name="callback" type="Callable" /> + <description> + Bakes the provided [param navigation_mesh] with the data from the provided [param source_geometry_data]. After the process is finished the optional [param callback] will be called. </description> </method> <method name="clear"> @@ -29,5 +38,16 @@ Removes all polygons and vertices from the provided [param navigation_mesh] resource. </description> </method> + <method name="parse_source_geometry_data"> + <return type="void" /> + <param index="0" name="navigation_mesh" type="NavigationMesh" /> + <param index="1" name="source_geometry_data" type="NavigationMeshSourceGeometryData3D" /> + <param index="2" name="root_node" type="Node" /> + <param index="3" name="callback" type="Callable" /> + <description> + Parses the [SceneTree] for source geometry according to the properties of [param navigation_mesh]. Updates the provided [param source_geometry_data] resource with the resulting data. The resource can then be used to bake a navigation mesh with [method bake_from_source_geometry_data]. After the process is finished the optional [param callback] will be called. + [b]Note:[/b] This function needs to run on the main thread or with a deferred call as the SceneTree is not thread-safe. + </description> + </method> </methods> </class> diff --git a/doc/classes/NavigationMeshSourceGeometryData3D.xml b/doc/classes/NavigationMeshSourceGeometryData3D.xml new file mode 100644 index 0000000000..b929e82436 --- /dev/null +++ b/doc/classes/NavigationMeshSourceGeometryData3D.xml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="NavigationMeshSourceGeometryData3D" inherits="Resource" is_experimental="true" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Container for parsed source geometry data used in navigation mesh baking. + </brief_description> + <description> + Container for parsed source geometry data used in navigation mesh baking. + </description> + <tutorials> + </tutorials> + <methods> + <method name="add_faces"> + <return type="void" /> + <param index="0" name="faces" type="PackedVector3Array" /> + <param index="1" name="xform" type="Transform3D" /> + <description> + Adds an array of vertex positions to the geometry data for navigation mesh baking to form triangulated faces. For each face the array must have three vertex positions in clockwise winding order. Since [NavigationMesh] resource have no transform all vertex positions need to be offset by the node's transform using the [code]xform[/code] parameter. + </description> + </method> + <method name="add_mesh"> + <return type="void" /> + <param index="0" name="mesh" type="Mesh" /> + <param index="1" name="xform" type="Transform3D" /> + <description> + Adds the geometry data of a [Mesh] resource to the navigation mesh baking data. The mesh must have valid triangulated mesh data to be considered. Since [NavigationMesh] resource have no transform all vertex positions need to be offset by the node's transform using the [code]xform[/code] parameter. + </description> + </method> + <method name="add_mesh_array"> + <return type="void" /> + <param index="0" name="mesh_array" type="Array" /> + <param index="1" name="xform" type="Transform3D" /> + <description> + Adds an [Array] the size of [constant Mesh.ARRAY_MAX] and with vertices at index [constant Mesh.ARRAY_VERTEX] and indices at index [constant Mesh.ARRAY_INDEX] to the navigation mesh baking data. The array must have valid triangulated mesh data to be considered. Since [NavigationMesh] resource have no transform all vertex positions need to be offset by the node's transform using the [code]xform[/code] parameter. + </description> + </method> + <method name="clear"> + <return type="void" /> + <description> + Clears the internal data. + </description> + </method> + <method name="get_indices" qualifiers="const"> + <return type="PackedInt32Array" /> + <description> + Returns the parsed source geometry data indices array. + </description> + </method> + <method name="get_vertices" qualifiers="const"> + <return type="PackedFloat32Array" /> + <description> + Returns the parsed source geometry data vertices array. + </description> + </method> + <method name="has_data"> + <return type="bool" /> + <description> + Returns [b]true[/b] when parsed source geometry data exists. + </description> + </method> + <method name="set_indices"> + <return type="void" /> + <param index="0" name="indices" type="PackedInt32Array" /> + <description> + Sets the parsed source geometry data indices. The indices need to be matched with appropriated vertices. + [b]Warning:[/b] Inappropriate data can crash the baking process of the involved third-party libraries. + </description> + </method> + <method name="set_vertices"> + <return type="void" /> + <param index="0" name="vertices" type="PackedFloat32Array" /> + <description> + Sets the parsed source geometry data vertices. The vertices need to be matched with appropriated indices. + [b]Warning:[/b] Inappropriate data can crash the baking process of the involved third-party libraries. + </description> + </method> + </methods> +</class> diff --git a/doc/classes/NavigationServer3D.xml b/doc/classes/NavigationServer3D.xml index ac9646da4a..4c759beac0 100644 --- a/doc/classes/NavigationServer3D.xml +++ b/doc/classes/NavigationServer3D.xml @@ -192,6 +192,15 @@ Replaces the internal velocity in the collision avoidance simulation with [param velocity] for the specified [param agent]. When an agent is teleported to a new position this function should be used in the same frame. If called frequently this function can get agents stuck. </description> </method> + <method name="bake_from_source_geometry_data"> + <return type="void" /> + <param index="0" name="navigation_mesh" type="NavigationMesh" /> + <param index="1" name="source_geometry_data" type="NavigationMeshSourceGeometryData3D" /> + <param index="2" name="callback" type="Callable" /> + <description> + Bakes the provided [param navigation_mesh] with the data from the provided [param source_geometry_data]. After the process is finished the optional [param callback] will be called. + </description> + </method> <method name="free_rid"> <return type="void" /> <param index="0" name="rid" type="RID" /> @@ -637,6 +646,17 @@ Sets the outline vertices for the obstacle. If the vertices are winded in clockwise order agents will be pushed in by the obstacle, else they will be pushed out. </description> </method> + <method name="parse_source_geometry_data"> + <return type="void" /> + <param index="0" name="navigation_mesh" type="NavigationMesh" /> + <param index="1" name="source_geometry_data" type="NavigationMeshSourceGeometryData3D" /> + <param index="2" name="root_node" type="Node" /> + <param index="3" name="callback" type="Callable" /> + <description> + Parses the [SceneTree] for source geometry according to the properties of [param navigation_mesh]. Updates the provided [param source_geometry_data] resource with the resulting data. The resource can then be used to bake a navigation mesh with [method bake_from_source_geometry_data]. After the process is finished the optional [param callback] will be called. + [b]Note:[/b] This function needs to run on the main thread or with a deferred call as the SceneTree is not thread-safe. + </description> + </method> <method name="query_path" qualifiers="const"> <return type="void" /> <param index="0" name="parameters" type="NavigationPathQueryParameters3D" /> diff --git a/doc/classes/PackedColorArray.xml b/doc/classes/PackedColorArray.xml index 663050e567..eb5a0edfd6 100644 --- a/doc/classes/PackedColorArray.xml +++ b/doc/classes/PackedColorArray.xml @@ -27,6 +27,10 @@ <param index="0" name="from" type="Array" /> <description> Constructs a new [PackedColorArray]. Optionally, you can pass in a generic [Array] that will be converted. + [b]Note:[/b] When initializing a [PackedColorArray] with elements, it must be initialized with an [Array] of [Color] values: + [codeblock] + var array = PackedColorArray([Color(0.1, 0.2, 0.3), Color(0.4, 0.5, 0.6)]) + [/codeblock] </description> </constructor> </constructors> diff --git a/doc/classes/PackedVector2Array.xml b/doc/classes/PackedVector2Array.xml index 0f74e0d2e5..a5a5d2f2df 100644 --- a/doc/classes/PackedVector2Array.xml +++ b/doc/classes/PackedVector2Array.xml @@ -28,6 +28,10 @@ <param index="0" name="from" type="Array" /> <description> Constructs a new [PackedVector2Array]. Optionally, you can pass in a generic [Array] that will be converted. + [b]Note:[/b] When initializing a [PackedVector2Array] with elements, it must be initialized with an [Array] of [Vector2] values: + [codeblock] + var array = PackedVector2Array([Vector2(12, 34), Vector2(56, 78)]) + [/codeblock] </description> </constructor> </constructors> diff --git a/doc/classes/PackedVector3Array.xml b/doc/classes/PackedVector3Array.xml index 9d0fdaa717..2557549703 100644 --- a/doc/classes/PackedVector3Array.xml +++ b/doc/classes/PackedVector3Array.xml @@ -27,6 +27,10 @@ <param index="0" name="from" type="Array" /> <description> Constructs a new [PackedVector3Array]. Optionally, you can pass in a generic [Array] that will be converted. + [b]Note:[/b] When initializing a [PackedVector3Array] with elements, it must be initialized with an [Array] of [Vector3] values: + [codeblock] + var array = PackedVector3Array([Vector3(12, 34, 56), Vector3(78, 90, 12)]) + [/codeblock] </description> </constructor> </constructors> diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp index f65056951d..3feed2b109 100644 --- a/drivers/vulkan/vulkan_context.cpp +++ b/drivers/vulkan/vulkan_context.cpp @@ -2276,6 +2276,7 @@ Error VulkanContext::prepare_buffers() { // presentation engine will still present the image correctly. print_verbose("Vulkan: Early suboptimal swapchain, recreating."); _update_swap_chain(w); + break; } else if (err != VK_SUCCESS) { ERR_BREAK_MSG(err != VK_SUCCESS, "Vulkan: Did not create swapchain successfully. Error code: " + String(string_VkResult(err))); } else { diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp index 9e22a0ead6..4232eacd76 100644 --- a/editor/editor_plugin.cpp +++ b/editor/editor_plugin.cpp @@ -62,7 +62,14 @@ void EditorPlugin::remove_custom_type(const String &p_type) { } void EditorPlugin::add_autoload_singleton(const String &p_name, const String &p_path) { - EditorNode::get_singleton()->get_project_settings()->get_autoload_settings()->autoload_add(p_name, p_path); + if (p_path.begins_with("res://")) { + EditorNode::get_singleton()->get_project_settings()->get_autoload_settings()->autoload_add(p_name, p_path); + } else { + const Ref<Script> plugin_script = static_cast<Ref<Script>>(get_script()); + ERR_FAIL_COND(plugin_script.is_null()); + const String script_base_path = plugin_script->get_path().get_base_dir(); + EditorNode::get_singleton()->get_project_settings()->get_autoload_settings()->autoload_add(p_name, script_base_path.path_join(p_path)); + } } void EditorPlugin::remove_autoload_singleton(const String &p_name) { diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index 76e09769cc..520cf42687 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -656,15 +656,13 @@ bool SceneTreeEditor::_update_filter(TreeItem *p_parent, bool p_scroll_to_select } } - if (keep_for_children) { - if (keep) { - p_parent->clear_custom_color(0); - p_parent->set_selectable(0, true); - } else { - p_parent->set_custom_color(0, get_theme_color(SNAME("disabled_font_color"), SNAME("Editor"))); - p_parent->set_selectable(0, false); - p_parent->deselect(0); - } + if (keep) { + p_parent->clear_custom_color(0); + p_parent->set_selectable(0, true); + } else if (keep_for_children) { + p_parent->set_custom_color(0, get_theme_color(SNAME("disabled_font_color"), SNAME("Editor"))); + p_parent->set_selectable(0, false); + p_parent->deselect(0); } if (editor_selection) { @@ -964,62 +962,74 @@ void SceneTreeEditor::set_selected(Node *p_node, bool p_emit_selected) { } } -void SceneTreeEditor::_rename_node(ObjectID p_node, const String &p_name) { - Object *o = ObjectDB::get_instance(p_node); - ERR_FAIL_COND(!o); - Node *n = Object::cast_to<Node>(o); - ERR_FAIL_COND(!n); - TreeItem *item = _find(tree->get_root(), n->get_path()); +void SceneTreeEditor::_rename_node(Node *p_node, const String &p_name) { + TreeItem *item = _find(tree->get_root(), p_node->get_path()); ERR_FAIL_COND(!item); + String new_name = p_name.validate_node_name(); - n->set_name(p_name); - item->set_metadata(0, n->get_path()); - item->set_text(0, p_name); -} - -void SceneTreeEditor::_renamed() { - TreeItem *which = tree->get_edited(); - - ERR_FAIL_COND(!which); - NodePath np = which->get_metadata(0); - Node *n = get_node(np); - ERR_FAIL_COND(!n); - - String raw_new_name = which->get_text(0); - if (raw_new_name.strip_edges().is_empty()) { - // If name is empty, fallback to class name. - if (GLOBAL_GET("editor/naming/node_name_casing").operator int() != NAME_CASING_PASCAL_CASE) { - raw_new_name = Node::adjust_name_casing(n->get_class()); - } else { - raw_new_name = n->get_class(); - } - } - - String new_name = raw_new_name.validate_node_name(); - - if (new_name != raw_new_name) { + if (new_name != p_name) { error->set_text(TTR("Invalid node name, the following characters are not allowed:") + "\n" + String::get_invalid_node_name_characters()); error->popup_centered(); if (new_name.is_empty()) { - which->set_text(0, n->get_name()); + item->set_text(0, p_node->get_name()); return; } - which->set_text(0, new_name); + item->set_text(0, new_name); } - if (new_name == n->get_name()) { - if (which->get_text(0).is_empty()) { - which->set_text(0, new_name); + if (new_name == p_node->get_name()) { + if (item->get_text(0).is_empty()) { + item->set_text(0, new_name); } return; } - // Trim leading/trailing whitespace to prevent node names from containing accidental whitespace, which would make it more difficult to get the node via `get_node()`. new_name = new_name.strip_edges(); + if (!is_scene_tree_dock) { + p_node->set_name(new_name); + item->set_metadata(0, p_node->get_path()); + emit_signal(SNAME("node_renamed")); + } else { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action("Rename Node", UndoRedo::MERGE_DISABLE, p_node); + + emit_signal(SNAME("node_prerename"), p_node, new_name); + + undo_redo->add_undo_method(p_node, "set_name", p_node->get_name()); + undo_redo->add_undo_method(item, "set_metadata", 0, p_node->get_path()); + undo_redo->add_undo_method(item, "set_text", 0, p_node->get_name()); + + p_node->set_name(p_name); + undo_redo->add_do_method(p_node, "set_name", new_name); + undo_redo->add_do_method(item, "set_metadata", 0, p_node->get_path()); + undo_redo->add_do_method(item, "set_text", 0, new_name); + + undo_redo->commit_action(); + } +} + +void SceneTreeEditor::_renamed() { + TreeItem *which = tree->get_edited(); + + ERR_FAIL_COND(!which); + NodePath np = which->get_metadata(0); + Node *n = get_node(np); + ERR_FAIL_COND(!n); + + String new_name = which->get_text(0); + if (new_name.strip_edges().is_empty()) { + // If name is empty, fallback to class name. + if (GLOBAL_GET("editor/naming/node_name_casing").operator int() != NAME_CASING_PASCAL_CASE) { + new_name = Node::adjust_name_casing(n->get_class()); + } else { + new_name = n->get_class(); + } + } + if (n->is_unique_name_in_owner() && get_tree()->get_edited_scene_root()->get_node_or_null("%" + new_name) != nullptr) { error->set_text(TTR("Another node already uses this unique name in the scene.")); error->popup_centered(); @@ -1027,18 +1037,7 @@ void SceneTreeEditor::_renamed() { return; } - if (!is_scene_tree_dock) { - n->set_name(new_name); - which->set_metadata(0, n->get_path()); - emit_signal(SNAME("node_renamed")); - } else { - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Rename Node"), UndoRedo::MERGE_DISABLE, n); - emit_signal(SNAME("node_prerename"), n, new_name); - undo_redo->add_do_method(this, "_rename_node", n->get_instance_id(), new_name); - undo_redo->add_undo_method(this, "_rename_node", n->get_instance_id(), n->get_name()); - undo_redo->commit_action(); - } + _rename_node(n, new_name); } Node *SceneTreeEditor::get_selected() { diff --git a/editor/gui/scene_tree_editor.h b/editor/gui/scene_tree_editor.h index 20cc62d675..8878256009 100644 --- a/editor/gui/scene_tree_editor.h +++ b/editor/gui/scene_tree_editor.h @@ -85,7 +85,7 @@ class SceneTreeEditor : public Control { void _notification(int p_what); void _selected_changed(); void _deselect_items(); - void _rename_node(ObjectID p_node, const String &p_name); + void _rename_node(Node *p_node, const String &p_name); void _cell_collapsed(Object *p_obj); diff --git a/editor/rename_dialog.cpp b/editor/rename_dialog.cpp index f42f34e110..39affdd9f8 100644 --- a/editor/rename_dialog.cpp +++ b/editor/rename_dialog.cpp @@ -588,7 +588,7 @@ void RenameDialog::rename() { if (!to_rename.is_empty()) { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Batch Rename")); + undo_redo->create_action(TTR("Batch Rename"), UndoRedo::MERGE_DISABLE, root_node, true); // Make sure to iterate reversed so that child nodes will find parents. for (int i = to_rename.size() - 1; i >= 0; --i) { @@ -600,9 +600,7 @@ void RenameDialog::rename() { continue; } - scene_tree_editor->emit_signal(SNAME("node_prerename"), n, new_name); - undo_redo->add_do_method(scene_tree_editor, "_rename_node", n->get_instance_id(), new_name); - undo_redo->add_undo_method(scene_tree_editor, "_rename_node", n->get_instance_id(), n->get_name()); + scene_tree_editor->call("_rename_node", n, new_name); } undo_redo->commit_action(); diff --git a/misc/extension_api_validation/4.0-stable.expected b/misc/extension_api_validation/4.0-stable.expected index 444560eb53..963c997aa0 100644 --- a/misc/extension_api_validation/4.0-stable.expected +++ b/misc/extension_api_validation/4.0-stable.expected @@ -6,6 +6,10 @@ should instead be used to justify these changes and describe how users should wo ======================================================================================================================== +GH-78237 +-------- +Validate extension JSON: Error: Field 'classes/WebRTCPeerConnectionExtension/methods/_create_data_channel/return_value': type changed value in new API, from "Object" to "WebRTCDataChannel". + GH-77757 -------- Validate extension JSON: Error: Field 'classes/Viewport/methods/gui_get_focus_owner': is_const changed value in new API, from false to true. diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 13fd115c43..8251de2956 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -3130,7 +3130,8 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } #ifdef DEBUG_ENABLED - if (p_is_root && return_type.kind != GDScriptParser::DataType::UNRESOLVED && return_type.builtin_type != Variant::NIL) { + if (p_is_root && return_type.kind != GDScriptParser::DataType::UNRESOLVED && return_type.builtin_type != Variant::NIL && + !(p_call->is_super && p_call->function_name == GDScriptLanguage::get_singleton()->strings._init)) { parser->push_warning(p_call, GDScriptWarning::RETURN_VALUE_DISCARDED, p_call->function_name); } @@ -4718,7 +4719,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo } if (p_is_constructor) { - function_name = "_init"; + function_name = GDScriptLanguage::get_singleton()->strings._init; r_static = true; } diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.gd b/modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.gd new file mode 100644 index 0000000000..c447003619 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.gd @@ -0,0 +1,10 @@ +class TestOne: + func _init(): + pass + +class TestTwo extends TestOne: + func _init(): + super() + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.out b/modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 05595e7e45..28331288d3 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -62,6 +62,7 @@ #include "signal_awaiter_utils.h" #include "utils/macros.h" #include "utils/naming_utils.h" +#include "utils/path_utils.h" #include "utils/string_utils.h" #define CACHED_STRING_NAME(m_var) (CSharpLanguage::get_singleton()->get_string_names().m_var) @@ -740,11 +741,7 @@ bool CSharpLanguage::is_assembly_reloading_needed() { return false; // Already up to date } } else { - String assembly_name = GLOBAL_GET("dotnet/project/assembly_name"); - - if (assembly_name.is_empty()) { - assembly_name = ProjectSettings::get_singleton()->get_safe_project_name(); - } + String assembly_name = path::get_csharp_project_name(); assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir() .path_join(assembly_name + ".dll"); diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs index 7c5502814f..d86a77d222 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs @@ -57,27 +57,5 @@ namespace GodotTools.Core path.StartsWith("\\", StringComparison.Ordinal) || path.StartsWith(_driveRoot, StringComparison.Ordinal); } - - public static string ToSafeDirName(this string dirName, bool allowDirSeparator = false) - { - var invalidChars = new List<string> { ":", "*", "?", "\"", "<", ">", "|" }; - - if (allowDirSeparator) - { - // Directory separators are allowed, but disallow ".." to avoid going up the filesystem - invalidChars.Add(".."); - } - else - { - invalidChars.Add("/"); - } - - string safeDirName = dirName.Replace("\\", "/").Trim(); - - foreach (string invalidChar in invalidChars) - safeDirName = safeDirName.Replace(invalidChar, "-"); - - return safeDirName; - } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index 27c2676dd0..ea2d14958b 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -151,7 +151,7 @@ namespace GodotTools.Export string ridOS = DetermineRuntimeIdentifierOS(platform); string ridArch = DetermineRuntimeIdentifierArch(arch); string runtimeIdentifier = $"{ridOS}-{ridArch}"; - string projectDataDirName = $"{DetermineDataDirNameForProject()}_{arch}"; + string projectDataDirName = $"data_{GodotSharpDirs.CSharpProjectName}_{arch}"; if (platform == OS.Platforms.MacOS) { projectDataDirName = Path.Combine("Contents", "Resources", projectDataDirName); @@ -258,12 +258,5 @@ namespace GodotTools.Export platform = null; return false; } - - private static string DetermineDataDirNameForProject() - { - string appName = (string)ProjectSettings.GetSetting("application/config/name"); - string appNameSafe = appName.ToSafeDirName(); - return $"data_{appNameSafe}"; - } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs index fb68fcbae6..55b413453d 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs @@ -48,21 +48,23 @@ namespace GodotTools.Internals } } - public static void DetermineProjectLocation() + + public static string CSharpProjectName { - static string DetermineProjectName() + get { - string projectAssemblyName = (string)ProjectSettings.GetSetting("application/config/name"); - projectAssemblyName = projectAssemblyName.ToSafeDirName(); - if (string.IsNullOrEmpty(projectAssemblyName)) - projectAssemblyName = "UnnamedProject"; - return projectAssemblyName; + Internal.godot_icall_GodotSharpDirs_CSharpProjectName(out godot_string dest); + using (dest) + return Marshaling.ConvertStringToManaged(dest); } + } + public static void DetermineProjectLocation() + { _projectAssemblyName = (string)ProjectSettings.GetSetting("dotnet/project/assembly_name"); if (string.IsNullOrEmpty(_projectAssemblyName)) { - _projectAssemblyName = DetermineProjectName(); + _projectAssemblyName = CSharpProjectName; ProjectSettings.SetSetting("dotnet/project/assembly_name", _projectAssemblyName); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index fd810996f7..3ea11750b7 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -101,6 +101,8 @@ namespace GodotTools.Internals public static partial void godot_icall_GodotSharpDirs_DataEditorToolsDir(out godot_string r_dest); + public static partial void godot_icall_GodotSharpDirs_CSharpProjectName(out godot_string r_dest); + public static partial void godot_icall_EditorProgress_Create(in godot_string task, in godot_string label, int amount, bool canCancel); diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 34b9974d10..527543bb5e 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -49,6 +49,7 @@ #include "../csharp_script.h" #include "../godotsharp_dirs.h" #include "../utils/macos_utils.h" +#include "../utils/path_utils.h" #include "code_completion.h" #include "../interop_types.h" @@ -81,6 +82,10 @@ void godot_icall_GodotSharpDirs_DataEditorToolsDir(godot_string *r_dest) { #endif } +void godot_icall_GodotSharpDirs_CSharpProjectName(godot_string *r_dest) { + memnew_placement(r_dest, String(path::get_csharp_project_name())); +} + void godot_icall_EditorProgress_Create(const godot_string *p_task, const godot_string *p_label, int32_t p_amount, bool p_can_cancel) { String task = *reinterpret_cast<const String *>(p_task); String label = *reinterpret_cast<const String *>(p_label); @@ -231,6 +236,7 @@ static const void *unmanaged_callbacks[]{ (void *)godot_icall_GodotSharpDirs_MonoUserDir, (void *)godot_icall_GodotSharpDirs_BuildLogsDirs, (void *)godot_icall_GodotSharpDirs_DataEditorToolsDir, + (void *)godot_icall_GodotSharpDirs_CSharpProjectName, (void *)godot_icall_EditorProgress_Create, (void *)godot_icall_EditorProgress_Dispose, (void *)godot_icall_EditorProgress_Step, diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index bff7d04b55..9b487992b5 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -44,6 +44,7 @@ #endif #include "mono_gd/gd_mono.h" +#include "utils/path_utils.h" namespace GodotSharpDirs { @@ -139,8 +140,7 @@ private: api_assemblies_dir = api_assemblies_base_dir.path_join(GDMono::get_expected_api_build_config()); #else // TOOLS_ENABLED String arch = Engine::get_singleton()->get_architecture_name(); - String appname = GLOBAL_GET("application/config/name"); - String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); + String appname_safe = path::get_csharp_project_name(); String data_dir_root = exe_dir.path_join("data_" + appname_safe + "_" + arch); if (!DirAccess::exists(data_dir_root)) { data_dir_root = exe_dir.path_join("data_Godot_" + arch); diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 97bf49271a..0aef45c128 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -293,20 +293,10 @@ godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime return godot_plugins_initialize; } #else -static String get_assembly_name() { - String assembly_name = GLOBAL_GET("dotnet/project/assembly_name"); - - if (assembly_name.is_empty()) { - assembly_name = ProjectSettings::get_singleton()->get_safe_project_name(); - } - - return assembly_name; -} - godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) { godot_plugins_initialize_fn godot_plugins_initialize = nullptr; - String assembly_name = get_assembly_name(); + String assembly_name = path::get_csharp_project_name(); HostFxrCharString assembly_path = str_to_hostfxr(GodotSharpDirs::get_api_assemblies_dir() .path_join(assembly_name + ".dll")); @@ -331,7 +321,7 @@ godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime } godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle) { - String assembly_name = get_assembly_name(); + String assembly_name = path::get_csharp_project_name(); #if defined(WINDOWS_ENABLED) String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".dll"); @@ -476,11 +466,7 @@ void GDMono::_init_godot_api_hashes() { #ifdef TOOLS_ENABLED bool GDMono::_load_project_assembly() { - String assembly_name = GLOBAL_GET("dotnet/project/assembly_name"); - - if (assembly_name.is_empty()) { - assembly_name = ProjectSettings::get_singleton()->get_safe_project_name(); - } + String assembly_name = path::get_csharp_project_name(); String assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir() .path_join(assembly_name + ".dll"); diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp index b5a3816ed7..7b3f212734 100644 --- a/modules/mono/utils/path_utils.cpp +++ b/modules/mono/utils/path_utils.cpp @@ -231,4 +231,27 @@ String relative_to(const String &p_path, const String &p_relative_to) { return relative_to_impl(path_abs_norm, relative_to_abs_norm); } + +String get_csharp_project_name() { + String name = ProjectSettings::get_singleton()->get_setting_with_override("dotnet/project/assembly_name"); + if (name.is_empty()) { + name = ProjectSettings::get_singleton()->get_setting_with_override("application/config/name"); + Vector<String> invalid_chars = Vector<String>({ // + // Windows reserved filename chars. + ":", "*", "?", "\"", "<", ">", "|", + // Directory separators. + "/", "\\", + // Other chars that have been found to break assembly loading. + ";", "'", "=", "," }); + name = name.strip_edges(); + for (int i = 0; i < invalid_chars.size(); i++) { + name = name.replace(invalid_chars[i], "-"); + } + } + if (name.is_empty()) { + name = "UnnamedProject"; + } + return name; +} + } // namespace path diff --git a/modules/mono/utils/path_utils.h b/modules/mono/utils/path_utils.h index 25d130fc58..fc043c3f15 100644 --- a/modules/mono/utils/path_utils.h +++ b/modules/mono/utils/path_utils.h @@ -31,7 +31,6 @@ #ifndef MONO_PATH_UTILS_H #define MONO_PATH_UTILS_H -#include "core/string/string_builder.h" #include "core/string/ustring.h" namespace path { @@ -58,6 +57,8 @@ String abspath(const String &p_path); String realpath(const String &p_path); String relative_to(const String &p_path, const String &p_relative_to); + +String get_csharp_project_name(); } // namespace path #endif // MONO_PATH_UTILS_H diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp index dd2c539c95..bf6a75cce8 100644 --- a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp +++ b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp @@ -41,6 +41,7 @@ #include "scene/gui/button.h" #include "scene/gui/dialogs.h" #include "scene/gui/label.h" +#include "scene/resources/navigation_mesh_source_geometry_data_3d.h" void NavigationMeshEditor::_node_removed(Node *p_node) { if (p_node == node) { @@ -98,7 +99,10 @@ void NavigationMeshEditor::_bake_pressed() { } NavigationMeshGenerator::get_singleton()->clear(node->get_navigation_mesh()); - NavigationMeshGenerator::get_singleton()->bake(node->get_navigation_mesh(), node); + Ref<NavigationMeshSourceGeometryData3D> source_geometry_data; + source_geometry_data.instantiate(); + NavigationMeshGenerator::get_singleton()->parse_source_geometry_data(node->get_navigation_mesh(), source_geometry_data, node); + NavigationMeshGenerator::get_singleton()->bake_from_source_geometry_data(node->get_navigation_mesh(), source_geometry_data); node->update_gizmos(); } diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp index 5baae478c2..3783c001f1 100644 --- a/modules/navigation/godot_navigation_server.cpp +++ b/modules/navigation/godot_navigation_server.cpp @@ -465,7 +465,10 @@ void GodotNavigationServer::region_bake_navigation_mesh(Ref<NavigationMesh> p_na #ifndef _3D_DISABLED NavigationMeshGenerator::get_singleton()->clear(p_navigation_mesh); - NavigationMeshGenerator::get_singleton()->bake(p_navigation_mesh, p_root_node); + Ref<NavigationMeshSourceGeometryData3D> source_geometry_data; + source_geometry_data.instantiate(); + NavigationMeshGenerator::get_singleton()->parse_source_geometry_data(p_navigation_mesh, source_geometry_data, p_root_node); + NavigationMeshGenerator::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, source_geometry_data); #endif } @@ -925,6 +928,18 @@ COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers) { obstacle->set_avoidance_layers(p_layers); } +void GodotNavigationServer::parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) { +#ifndef _3D_DISABLED + NavigationMeshGenerator::get_singleton()->parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node, p_callback); +#endif +} + +void GodotNavigationServer::bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) { +#ifndef _3D_DISABLED + NavigationMeshGenerator::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback); +#endif +} + COMMAND_1(free, RID, p_object) { if (map_owner.owns(p_object)) { NavMap *map = map_owner.get_or_null(p_object); diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h index 1f2096b61a..7bf5bab477 100644 --- a/modules/navigation/godot_navigation_server.h +++ b/modules/navigation/godot_navigation_server.h @@ -214,6 +214,9 @@ public: virtual void obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) override; COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers); + virtual void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override; + virtual void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override; + COMMAND_1(free, RID, p_object); virtual void set_active(bool p_active) override; diff --git a/modules/navigation/navigation_mesh_generator.cpp b/modules/navigation/navigation_mesh_generator.cpp index fe63d67aba..88965cd439 100644 --- a/modules/navigation/navigation_mesh_generator.cpp +++ b/modules/navigation/navigation_mesh_generator.cpp @@ -43,6 +43,7 @@ #include "scene/resources/convex_polygon_shape_3d.h" #include "scene/resources/cylinder_shape_3d.h" #include "scene/resources/height_map_shape_3d.h" +#include "scene/resources/navigation_mesh_source_geometry_data_3d.h" #include "scene/resources/primitive_meshes.h" #include "scene/resources/shape_3d.h" #include "scene/resources/sphere_shape_3d.h" @@ -466,52 +467,102 @@ void NavigationMeshGenerator::_parse_geometry(const Transform3D &p_navmesh_trans } } -void NavigationMeshGenerator::_convert_detail_mesh_to_native_navigation_mesh(const rcPolyMeshDetail *p_detail_mesh, Ref<NavigationMesh> p_navigation_mesh) { - Vector<Vector3> nav_vertices; +NavigationMeshGenerator *NavigationMeshGenerator::get_singleton() { + return singleton; +} - for (int i = 0; i < p_detail_mesh->nverts; i++) { - const float *v = &p_detail_mesh->verts[i * 3]; - nav_vertices.push_back(Vector3(v[0], v[1], v[2])); +NavigationMeshGenerator::NavigationMeshGenerator() { + singleton = this; +} + +NavigationMeshGenerator::~NavigationMeshGenerator() { +} + +void NavigationMeshGenerator::bake(const Ref<NavigationMesh> &p_navigation_mesh, Node *p_root_node) { + WARN_PRINT_ONCE("NavigationMeshGenerator::bake() is deprecated due to core threading changes. To upgrade existing code, first create a NavigationMeshSourceGeometryData3D resource. Use this resource with method parse_source_geometry_data() to parse the SceneTree for nodes that should contribute to the navigation mesh baking. The SceneTree parsing needs to happen on the main thread. After the parsing is finished use the resource with method bake_from_source_geometry_data() to bake a navigation mesh.."); +} + +void NavigationMeshGenerator::clear(Ref<NavigationMesh> p_navigation_mesh) { + if (p_navigation_mesh.is_valid()) { + p_navigation_mesh->clear_polygons(); + p_navigation_mesh->set_vertices(Vector<Vector3>()); } - p_navigation_mesh->set_vertices(nav_vertices); +} - for (int i = 0; i < p_detail_mesh->nmeshes; i++) { - const unsigned int *m = &p_detail_mesh->meshes[i * 4]; - const unsigned int bverts = m[0]; - const unsigned int btris = m[2]; - const unsigned int ntris = m[3]; - const unsigned char *tris = &p_detail_mesh->tris[btris * 4]; - for (unsigned int j = 0; j < ntris; j++) { - Vector<int> nav_indices; - nav_indices.resize(3); - // Polygon order in recast is opposite than godot's - nav_indices.write[0] = ((int)(bverts + tris[j * 4 + 0])); - nav_indices.write[1] = ((int)(bverts + tris[j * 4 + 2])); - nav_indices.write[2] = ((int)(bverts + tris[j * 4 + 1])); - p_navigation_mesh->add_polygon(nav_indices); +void NavigationMeshGenerator::parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) { + ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred()."); + ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh."); + ERR_FAIL_COND_MSG(p_root_node == nullptr, "No parsing root node specified."); + ERR_FAIL_COND_MSG(!p_root_node->is_inside_tree(), "The root node needs to be inside the SceneTree."); + + Vector<float> vertices; + Vector<int> indices; + + List<Node *> parse_nodes; + + if (p_navigation_mesh->get_source_geometry_mode() == NavigationMesh::SOURCE_GEOMETRY_ROOT_NODE_CHILDREN) { + parse_nodes.push_back(p_root_node); + } else { + p_root_node->get_tree()->get_nodes_in_group(p_navigation_mesh->get_source_group_name(), &parse_nodes); + } + + Transform3D navmesh_xform = Transform3D(); + if (Object::cast_to<Node3D>(p_root_node)) { + navmesh_xform = Object::cast_to<Node3D>(p_root_node)->get_global_transform().affine_inverse(); + } + for (Node *E : parse_nodes) { + NavigationMesh::ParsedGeometryType geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + uint32_t collision_mask = p_navigation_mesh->get_collision_mask(); + bool recurse_children = p_navigation_mesh->get_source_geometry_mode() != NavigationMesh::SOURCE_GEOMETRY_GROUPS_EXPLICIT; + _parse_geometry(navmesh_xform, E, vertices, indices, geometry_type, collision_mask, recurse_children); + } + + p_source_geometry_data->set_vertices(vertices); + p_source_geometry_data->set_indices(indices); + + if (p_callback.is_valid()) { + Callable::CallError ce; + Variant result; + p_callback.callp(nullptr, 0, result, ce); + if (ce.error == Callable::CallError::CALL_OK) { + // } } } -void NavigationMeshGenerator::_build_recast_navigation_mesh( - Ref<NavigationMesh> p_navigation_mesh, -#ifdef TOOLS_ENABLED - EditorProgress *ep, -#endif - rcHeightfield *hf, - rcCompactHeightfield *chf, - rcContourSet *cset, - rcPolyMesh *poly_mesh, - rcPolyMeshDetail *detail_mesh, - Vector<float> &vertices, - Vector<int> &indices) { - rcContext ctx; +void NavigationMeshGenerator::bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) { + ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh."); + ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData3D."); + ERR_FAIL_COND_MSG(!p_source_geometry_data->has_data(), "NavigationMeshSourceGeometryData3D is empty. Parse source geometry first."); -#ifdef TOOLS_ENABLED - if (ep) { - ep->step(TTR("Setting up Configuration..."), 1); + generator_mutex.lock(); + if (baking_navmeshes.has(p_navigation_mesh)) { + generator_mutex.unlock(); + ERR_FAIL_MSG("NavigationMesh is already baking. Wait for current bake to finish."); + } else { + baking_navmeshes.insert(p_navigation_mesh); + generator_mutex.unlock(); } -#endif + +#ifndef _3D_DISABLED + const Vector<float> vertices = p_source_geometry_data->get_vertices(); + const Vector<int> indices = p_source_geometry_data->get_indices(); + + if (vertices.size() < 3 || indices.size() < 3) { + return; + } + + rcHeightfield *hf = nullptr; + rcCompactHeightfield *chf = nullptr; + rcContourSet *cset = nullptr; + rcPolyMesh *poly_mesh = nullptr; + rcPolyMeshDetail *detail_mesh = nullptr; + rcContext ctx; + + // added to keep track of steps, no functionality right now + String bake_state = ""; + + bake_state = "Setting up Configuration..."; // step #1 const float *verts = vertices.ptr(); const int nverts = vertices.size() / 3; @@ -581,11 +632,7 @@ void NavigationMeshGenerator::_build_recast_navigation_mesh( cfg.bmax[2] = cfg.bmin[2] + baking_aabb.size[2]; } -#ifdef TOOLS_ENABLED - if (ep) { - ep->step(TTR("Calculating grid size..."), 2); - } -#endif + bake_state = "Calculating grid size..."; // step #2 rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height); // ~30000000 seems to be around sweetspot where Editor baking breaks @@ -596,21 +643,13 @@ void NavigationMeshGenerator::_build_recast_navigation_mesh( "\nIt is advised to increase Cell Size and/or Cell Height in the NavMesh Resource bake settings or reduce the size / scale of the source geometry."); } -#ifdef TOOLS_ENABLED - if (ep) { - ep->step(TTR("Creating heightfield..."), 3); - } -#endif + bake_state = "Creating heightfield..."; // step #3 hf = rcAllocHeightfield(); ERR_FAIL_COND(!hf); ERR_FAIL_COND(!rcCreateHeightfield(&ctx, *hf, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch)); -#ifdef TOOLS_ENABLED - if (ep) { - ep->step(TTR("Marking walkable triangles..."), 4); - } -#endif + bake_state = "Marking walkable triangles..."; // step #4 { Vector<unsigned char> tri_areas; tri_areas.resize(ntris); @@ -633,11 +672,7 @@ void NavigationMeshGenerator::_build_recast_navigation_mesh( rcFilterWalkableLowHeightSpans(&ctx, cfg.walkableHeight, *hf); } -#ifdef TOOLS_ENABLED - if (ep) { - ep->step(TTR("Constructing compact heightfield..."), 5); - } -#endif + bake_state = "Constructing compact heightfield..."; // step #5 chf = rcAllocCompactHeightfield(); @@ -647,19 +682,11 @@ void NavigationMeshGenerator::_build_recast_navigation_mesh( rcFreeHeightField(hf); hf = nullptr; -#ifdef TOOLS_ENABLED - if (ep) { - ep->step(TTR("Eroding walkable area..."), 6); - } -#endif + bake_state = "Eroding walkable area..."; // step #6 ERR_FAIL_COND(!rcErodeWalkableArea(&ctx, cfg.walkableRadius, *chf)); -#ifdef TOOLS_ENABLED - if (ep) { - ep->step(TTR("Partitioning..."), 7); - } -#endif + bake_state = "Partitioning..."; // step #7 if (p_navigation_mesh->get_sample_partition_type() == NavigationMesh::SAMPLE_PARTITION_WATERSHED) { ERR_FAIL_COND(!rcBuildDistanceField(&ctx, *chf)); @@ -670,22 +697,14 @@ void NavigationMeshGenerator::_build_recast_navigation_mesh( ERR_FAIL_COND(!rcBuildLayerRegions(&ctx, *chf, 0, cfg.minRegionArea)); } -#ifdef TOOLS_ENABLED - if (ep) { - ep->step(TTR("Creating contours..."), 8); - } -#endif + bake_state = "Creating contours..."; // step #8 cset = rcAllocContourSet(); ERR_FAIL_COND(!cset); ERR_FAIL_COND(!rcBuildContours(&ctx, *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cset)); -#ifdef TOOLS_ENABLED - if (ep) { - ep->step(TTR("Creating polymesh..."), 9); - } -#endif + bake_state = "Creating polymesh..."; // step #9 poly_mesh = rcAllocPolyMesh(); ERR_FAIL_COND(!poly_mesh); @@ -700,128 +719,63 @@ void NavigationMeshGenerator::_build_recast_navigation_mesh( rcFreeContourSet(cset); cset = nullptr; -#ifdef TOOLS_ENABLED - if (ep) { - ep->step(TTR("Converting to native navigation mesh..."), 10); - } -#endif - - _convert_detail_mesh_to_native_navigation_mesh(detail_mesh, p_navigation_mesh); - - rcFreePolyMesh(poly_mesh); - poly_mesh = nullptr; - rcFreePolyMeshDetail(detail_mesh); - detail_mesh = nullptr; -} - -NavigationMeshGenerator *NavigationMeshGenerator::get_singleton() { - return singleton; -} - -NavigationMeshGenerator::NavigationMeshGenerator() { - singleton = this; -} - -NavigationMeshGenerator::~NavigationMeshGenerator() { -} - -void NavigationMeshGenerator::bake(Ref<NavigationMesh> p_navigation_mesh, Node *p_root_node) { - ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh."); - -#ifdef TOOLS_ENABLED - EditorProgress *ep(nullptr); - // FIXME -#endif -#if 0 - // After discussion on devchat disabled EditorProgress for now as it is not thread-safe and uses hacks and Main::iteration() for steps. - // EditorProgress randomly crashes the Engine when the bake function is used with a thread e.g. inside Editor with a tool script and procedural navigation - // This was not a problem in older versions as previously Godot was unable to (re)bake NavigationMesh at runtime. - // If EditorProgress is fixed and made thread-safe this should be enabled again. - if (Engine::get_singleton()->is_editor_hint()) { - ep = memnew(EditorProgress("bake", TTR("Navigation Mesh Generator Setup:"), 11)); - } - - if (ep) { - ep->step(TTR("Parsing Geometry..."), 0); - } -#endif + bake_state = "Converting to native navigation mesh..."; // step #10 - Vector<float> vertices; - Vector<int> indices; - - List<Node *> parse_nodes; + Vector<Vector3> nav_vertices; - if (p_navigation_mesh->get_source_geometry_mode() == NavigationMesh::SOURCE_GEOMETRY_ROOT_NODE_CHILDREN) { - parse_nodes.push_back(p_root_node); - } else { - p_root_node->get_tree()->get_nodes_in_group(p_navigation_mesh->get_source_group_name(), &parse_nodes); + for (int i = 0; i < detail_mesh->nverts; i++) { + const float *v = &detail_mesh->verts[i * 3]; + nav_vertices.push_back(Vector3(v[0], v[1], v[2])); } + p_navigation_mesh->set_vertices(nav_vertices); - Transform3D navmesh_xform = Object::cast_to<Node3D>(p_root_node)->get_global_transform().affine_inverse(); - for (Node *E : parse_nodes) { - NavigationMesh::ParsedGeometryType geometry_type = p_navigation_mesh->get_parsed_geometry_type(); - uint32_t collision_mask = p_navigation_mesh->get_collision_mask(); - bool recurse_children = p_navigation_mesh->get_source_geometry_mode() != NavigationMesh::SOURCE_GEOMETRY_GROUPS_EXPLICIT; - _parse_geometry(navmesh_xform, E, vertices, indices, geometry_type, collision_mask, recurse_children); + for (int i = 0; i < detail_mesh->nmeshes; i++) { + const unsigned int *detail_mesh_m = &detail_mesh->meshes[i * 4]; + const unsigned int detail_mesh_bverts = detail_mesh_m[0]; + const unsigned int detail_mesh_m_btris = detail_mesh_m[2]; + const unsigned int detail_mesh_ntris = detail_mesh_m[3]; + const unsigned char *detail_mesh_tris = &detail_mesh->tris[detail_mesh_m_btris * 4]; + for (unsigned int j = 0; j < detail_mesh_ntris; j++) { + Vector<int> nav_indices; + nav_indices.resize(3); + // Polygon order in recast is opposite than godot's + nav_indices.write[0] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 0])); + nav_indices.write[1] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 2])); + nav_indices.write[2] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 1])); + p_navigation_mesh->add_polygon(nav_indices); + } } - if (vertices.size() > 0 && indices.size() > 0) { - rcHeightfield *hf = nullptr; - rcCompactHeightfield *chf = nullptr; - rcContourSet *cset = nullptr; - rcPolyMesh *poly_mesh = nullptr; - rcPolyMeshDetail *detail_mesh = nullptr; - - _build_recast_navigation_mesh( - p_navigation_mesh, -#ifdef TOOLS_ENABLED - ep, -#endif - hf, - chf, - cset, - poly_mesh, - detail_mesh, - vertices, - indices); - - rcFreeHeightField(hf); - hf = nullptr; + bake_state = "Cleanup..."; // step #11 - rcFreeCompactHeightfield(chf); - chf = nullptr; - - rcFreeContourSet(cset); - cset = nullptr; - - rcFreePolyMesh(poly_mesh); - poly_mesh = nullptr; + rcFreePolyMesh(poly_mesh); + poly_mesh = nullptr; + rcFreePolyMeshDetail(detail_mesh); + detail_mesh = nullptr; - rcFreePolyMeshDetail(detail_mesh); - detail_mesh = nullptr; - } + bake_state = "Baking finished."; // step #12 +#endif // _3D_DISABLED -#ifdef TOOLS_ENABLED - if (ep) { - ep->step(TTR("Done!"), 11); - } + generator_mutex.lock(); + baking_navmeshes.erase(p_navigation_mesh); + generator_mutex.unlock(); - if (ep) { - memdelete(ep); - } -#endif -} - -void NavigationMeshGenerator::clear(Ref<NavigationMesh> p_navigation_mesh) { - if (p_navigation_mesh.is_valid()) { - p_navigation_mesh->clear_polygons(); - p_navigation_mesh->set_vertices(Vector<Vector3>()); + if (p_callback.is_valid()) { + Callable::CallError ce; + Variant result; + p_callback.callp(nullptr, 0, result, ce); + if (ce.error == Callable::CallError::CALL_OK) { + // + } } } void NavigationMeshGenerator::_bind_methods() { ClassDB::bind_method(D_METHOD("bake", "navigation_mesh", "root_node"), &NavigationMeshGenerator::bake); ClassDB::bind_method(D_METHOD("clear", "navigation_mesh"), &NavigationMeshGenerator::clear); + + ClassDB::bind_method(D_METHOD("parse_source_geometry_data", "navigation_mesh", "source_geometry_data", "root_node", "callback"), &NavigationMeshGenerator::parse_source_geometry_data, DEFVAL(Callable())); + ClassDB::bind_method(D_METHOD("bake_from_source_geometry_data", "navigation_mesh", "source_geometry_data", "callback"), &NavigationMeshGenerator::bake_from_source_geometry_data, DEFVAL(Callable())); } #endif diff --git a/modules/navigation/navigation_mesh_generator.h b/modules/navigation/navigation_mesh_generator.h index 4399c422e2..4bf2b64f44 100644 --- a/modules/navigation/navigation_mesh_generator.h +++ b/modules/navigation/navigation_mesh_generator.h @@ -34,18 +34,20 @@ #ifndef _3D_DISABLED #include "scene/3d/navigation_region_3d.h" +#include "scene/resources/navigation_mesh.h" #include <Recast.h> -#ifdef TOOLS_ENABLED -struct EditorProgress; -#endif +class NavigationMeshSourceGeometryData3D; class NavigationMeshGenerator : public Object { GDCLASS(NavigationMeshGenerator, Object); + Mutex generator_mutex; static NavigationMeshGenerator *singleton; + HashSet<Ref<NavigationMesh>> baking_navmeshes; + protected: static void _bind_methods(); @@ -55,28 +57,17 @@ protected: static void _add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices); static void _parse_geometry(const Transform3D &p_navmesh_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children); - static void _convert_detail_mesh_to_native_navigation_mesh(const rcPolyMeshDetail *p_detail_mesh, Ref<NavigationMesh> p_navigation_mesh); - static void _build_recast_navigation_mesh( - Ref<NavigationMesh> p_navigation_mesh, -#ifdef TOOLS_ENABLED - EditorProgress *ep, -#endif - rcHeightfield *hf, - rcCompactHeightfield *chf, - rcContourSet *cset, - rcPolyMesh *poly_mesh, - rcPolyMeshDetail *detail_mesh, - Vector<float> &vertices, - Vector<int> &indices); - public: static NavigationMeshGenerator *get_singleton(); NavigationMeshGenerator(); ~NavigationMeshGenerator(); - void bake(Ref<NavigationMesh> p_navigation_mesh, Node *p_root_node); + void bake(const Ref<NavigationMesh> &p_navigation_mesh, Node *p_root_node); void clear(Ref<NavigationMesh> p_navigation_mesh); + + void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()); + void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()); }; #endif diff --git a/modules/webrtc/doc_classes/WebRTCPeerConnectionExtension.xml b/modules/webrtc/doc_classes/WebRTCPeerConnectionExtension.xml index 535a20d67e..dbe6033c4d 100644 --- a/modules/webrtc/doc_classes/WebRTCPeerConnectionExtension.xml +++ b/modules/webrtc/doc_classes/WebRTCPeerConnectionExtension.xml @@ -21,7 +21,7 @@ </description> </method> <method name="_create_data_channel" qualifiers="virtual"> - <return type="Object" /> + <return type="WebRTCDataChannel" /> <param index="0" name="p_label" type="String" /> <param index="1" name="p_config" type="Dictionary" /> <description> diff --git a/modules/webrtc/webrtc_peer_connection_extension.cpp b/modules/webrtc/webrtc_peer_connection_extension.cpp index 15e6bac3b0..248b0cf610 100644 --- a/modules/webrtc/webrtc_peer_connection_extension.cpp +++ b/modules/webrtc/webrtc_peer_connection_extension.cpp @@ -43,14 +43,3 @@ void WebRTCPeerConnectionExtension::_bind_methods() { GDVIRTUAL_BIND(_poll); GDVIRTUAL_BIND(_close); } - -Ref<WebRTCDataChannel> WebRTCPeerConnectionExtension::create_data_channel(String p_label, Dictionary p_options) { - Object *ret = nullptr; - if (GDVIRTUAL_CALL(_create_data_channel, p_label, p_options, ret)) { - WebRTCDataChannel *ch = Object::cast_to<WebRTCDataChannel>(ret); - ERR_FAIL_COND_V_MSG(ret && !ch, nullptr, "Returned object must be an instance of WebRTCDataChannel."); - return ch; - } - WARN_PRINT_ONCE("WebRTCPeerConnectionExtension::_create_data_channel is unimplemented!"); - return nullptr; -} diff --git a/modules/webrtc/webrtc_peer_connection_extension.h b/modules/webrtc/webrtc_peer_connection_extension.h index a2a5c773f0..f3339f1eb4 100644 --- a/modules/webrtc/webrtc_peer_connection_extension.h +++ b/modules/webrtc/webrtc_peer_connection_extension.h @@ -45,17 +45,12 @@ protected: static void _bind_methods(); public: - // FIXME Can't be directly exposed due to issues in exchanging Ref(s) between godot and extensions. - // See godot-cpp GH-652 . - virtual Ref<WebRTCDataChannel> create_data_channel(String p_label, Dictionary p_options = Dictionary()) override; - GDVIRTUAL2R(Object *, _create_data_channel, String, Dictionary); - // EXBIND2R(Ref<WebRTCDataChannel>, create_data_channel, String, Dictionary); - /** GDExtension **/ EXBIND0RC(ConnectionState, get_connection_state); EXBIND0RC(GatheringState, get_gathering_state); EXBIND0RC(SignalingState, get_signaling_state); EXBIND1R(Error, initialize, Dictionary); + EXBIND2R(Ref<WebRTCDataChannel>, create_data_channel, String, Dictionary); EXBIND0R(Error, create_offer); EXBIND2R(Error, set_remote_description, String, String); EXBIND2R(Error, set_local_description, String, String); diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index c14fb29353..693e03e1d4 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -35,7 +35,6 @@ #include "scene/2d/navigation_obstacle_2d.h" #include "scene/resources/world_2d.h" #include "servers/navigation_server_2d.h" -#include "servers/navigation_server_3d.h" void NavigationRegion2D::set_enabled(bool p_enabled) { if (enabled == p_enabled) { @@ -50,14 +49,12 @@ void NavigationRegion2D::set_enabled(bool p_enabled) { if (!enabled) { NavigationServer2D::get_singleton()->region_set_map(region, RID()); - NavigationServer2D::get_singleton()->disconnect("map_changed", callable_mp(this, &NavigationRegion2D::_map_changed)); } else { NavigationServer2D::get_singleton()->region_set_map(region, get_world_2d()->get_navigation_map()); - NavigationServer2D::get_singleton()->connect("map_changed", callable_mp(this, &NavigationRegion2D::_map_changed)); } #ifdef DEBUG_ENABLED - if (Engine::get_singleton()->is_editor_hint() || NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { + if (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_navigation_enabled()) { queue_redraw(); } #endif // DEBUG_ENABLED @@ -166,7 +163,6 @@ void NavigationRegion2D::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { if (enabled) { NavigationServer2D::get_singleton()->region_set_map(region, get_world_2d()->get_navigation_map()); - NavigationServer2D::get_singleton()->connect("map_changed", callable_mp(this, &NavigationRegion2D::_map_changed)); for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { if (constrain_avoidance_obstacles[i].is_valid()) { NavigationServer2D::get_singleton()->obstacle_set_map(constrain_avoidance_obstacles[i], get_world_2d()->get_navigation_map()); @@ -184,9 +180,6 @@ void NavigationRegion2D::_notification(int p_what) { case NOTIFICATION_EXIT_TREE: { NavigationServer2D::get_singleton()->region_set_map(region, RID()); - if (enabled) { - NavigationServer2D::get_singleton()->disconnect("map_changed", callable_mp(this, &NavigationRegion2D::_map_changed)); - } for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { if (constrain_avoidance_obstacles[i].is_valid()) { NavigationServer2D::get_singleton()->obstacle_set_map(constrain_avoidance_obstacles[i], RID()); @@ -215,78 +208,8 @@ void NavigationRegion2D::_notification(int p_what) { case NOTIFICATION_DRAW: { #ifdef DEBUG_ENABLED if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled()) && navigation_polygon.is_valid()) { - Vector<Vector2> navigation_polygon_vertices = navigation_polygon->get_vertices(); - if (navigation_polygon_vertices.size() < 3) { - return; - } - - const NavigationServer2D *ns2d = NavigationServer2D::get_singleton(); - - bool enabled_geometry_face_random_color = ns2d->get_debug_navigation_enable_geometry_face_random_color(); - bool enabled_edge_lines = ns2d->get_debug_navigation_enable_edge_lines(); - bool enable_edge_connections = use_edge_connections && ns2d->get_debug_navigation_enable_edge_connections() && ns2d->map_get_use_edge_connections(get_world_2d()->get_navigation_map()); - - Color debug_face_color = ns2d->get_debug_navigation_geometry_face_color(); - Color debug_edge_color = ns2d->get_debug_navigation_geometry_edge_color(); - Color debug_edge_connection_color = ns2d->get_debug_navigation_edge_connection_color(); - - if (!enabled) { - debug_face_color = ns2d->get_debug_navigation_geometry_face_disabled_color(); - debug_edge_color = ns2d->get_debug_navigation_geometry_edge_disabled_color(); - } - - RandomPCG rand; - - for (int i = 0; i < navigation_polygon->get_polygon_count(); i++) { - // An array of vertices for this polygon. - Vector<int> polygon = navigation_polygon->get_polygon(i); - Vector<Vector2> debug_polygon_vertices; - debug_polygon_vertices.resize(polygon.size()); - for (int j = 0; j < polygon.size(); j++) { - ERR_FAIL_INDEX(polygon[j], navigation_polygon_vertices.size()); - debug_polygon_vertices.write[j] = navigation_polygon_vertices[polygon[j]]; - } - - // Generate the polygon color, slightly randomly modified from the settings one. - Color random_variation_color = debug_face_color; - if (enabled_geometry_face_random_color) { - random_variation_color.set_hsv( - debug_face_color.get_h() + rand.random(-1.0, 1.0) * 0.1, - debug_face_color.get_s(), - debug_face_color.get_v() + rand.random(-1.0, 1.0) * 0.2); - } - random_variation_color.a = debug_face_color.a; - - Vector<Color> debug_face_colors; - debug_face_colors.push_back(random_variation_color); - RS::get_singleton()->canvas_item_add_polygon(get_canvas_item(), debug_polygon_vertices, debug_face_colors); - - if (enabled_edge_lines) { - Vector<Color> debug_edge_colors; - debug_edge_colors.push_back(debug_edge_color); - debug_polygon_vertices.push_back(debug_polygon_vertices[0]); // Add first again for closing polyline. - RS::get_singleton()->canvas_item_add_polyline(get_canvas_item(), debug_polygon_vertices, debug_edge_colors); - } - } - - if (enable_edge_connections) { - // Draw the region edge connections. - Transform2D xform = get_global_transform(); - real_t radius = ns2d->map_get_edge_connection_margin(get_world_2d()->get_navigation_map()) / 2.0; - for (int i = 0; i < ns2d->region_get_connections_count(region); i++) { - // Two main points - Vector2 a = ns2d->region_get_connection_pathway_start(region, i); - a = xform.affine_inverse().xform(a); - Vector2 b = ns2d->region_get_connection_pathway_end(region, i); - b = xform.affine_inverse().xform(b); - draw_line(a, b, debug_edge_connection_color); - - // Draw a circle to illustrate the margins. - real_t angle = a.angle_to_point(b); - draw_arc(a, radius, angle + Math_PI / 2.0, angle - Math_PI / 2.0 + Math_TAU, 10, debug_edge_connection_color); - draw_arc(b, radius, angle - Math_PI / 2.0, angle + Math_PI / 2.0, 10, debug_edge_connection_color); - } - } + _update_debug_mesh(); + _update_debug_edge_connections_mesh(); } #endif // DEBUG_ENABLED } break; @@ -327,13 +250,13 @@ void NavigationRegion2D::_navigation_polygon_changed() { _update_avoidance_constrain(); } -void NavigationRegion2D::_map_changed(RID p_map) { #ifdef DEBUG_ENABLED +void NavigationRegion2D::_navigation_map_changed(RID p_map) { if (is_inside_tree() && get_world_2d()->get_navigation_map() == p_map) { queue_redraw(); } -#endif // DEBUG_ENABLED } +#endif // DEBUG_ENABLED PackedStringArray NavigationRegion2D::get_configuration_warnings() const { PackedStringArray warnings = Node2D::get_configuration_warnings(); @@ -419,8 +342,8 @@ NavigationRegion2D::NavigationRegion2D() { NavigationServer2D::get_singleton()->region_set_travel_cost(region, get_travel_cost()); #ifdef DEBUG_ENABLED - NavigationServer3D::get_singleton()->connect("map_changed", callable_mp(this, &NavigationRegion2D::_map_changed)); - NavigationServer3D::get_singleton()->connect("navigation_debug_changed", callable_mp(this, &NavigationRegion2D::_map_changed)); + NavigationServer2D::get_singleton()->connect(SNAME("map_changed"), callable_mp(this, &NavigationRegion2D::_navigation_map_changed)); + NavigationServer2D::get_singleton()->connect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationRegion2D::_navigation_map_changed)); #endif // DEBUG_ENABLED } @@ -436,8 +359,8 @@ NavigationRegion2D::~NavigationRegion2D() { constrain_avoidance_obstacles.clear(); #ifdef DEBUG_ENABLED - NavigationServer3D::get_singleton()->disconnect("map_changed", callable_mp(this, &NavigationRegion2D::_map_changed)); - NavigationServer3D::get_singleton()->disconnect("navigation_debug_changed", callable_mp(this, &NavigationRegion2D::_map_changed)); + NavigationServer2D::get_singleton()->disconnect(SNAME("map_changed"), callable_mp(this, &NavigationRegion2D::_navigation_map_changed)); + NavigationServer2D::get_singleton()->disconnect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationRegion2D::_navigation_map_changed)); #endif // DEBUG_ENABLED } @@ -548,3 +471,86 @@ bool NavigationRegion2D::get_avoidance_layer_value(int p_layer_number) const { ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Avoidance layer number must be between 1 and 32 inclusive."); return get_avoidance_layers() & (1 << (p_layer_number - 1)); } + +#ifdef DEBUG_ENABLED +void NavigationRegion2D::_update_debug_mesh() { + Vector<Vector2> navigation_polygon_vertices = navigation_polygon->get_vertices(); + if (navigation_polygon_vertices.size() < 3) { + return; + } + + const NavigationServer2D *ns2d = NavigationServer2D::get_singleton(); + + bool enabled_geometry_face_random_color = ns2d->get_debug_navigation_enable_geometry_face_random_color(); + bool enabled_edge_lines = ns2d->get_debug_navigation_enable_edge_lines(); + + Color debug_face_color = ns2d->get_debug_navigation_geometry_face_color(); + Color debug_edge_color = ns2d->get_debug_navigation_geometry_edge_color(); + + if (!enabled) { + debug_face_color = ns2d->get_debug_navigation_geometry_face_disabled_color(); + debug_edge_color = ns2d->get_debug_navigation_geometry_edge_disabled_color(); + } + + RandomPCG rand; + + for (int i = 0; i < navigation_polygon->get_polygon_count(); i++) { + // An array of vertices for this polygon. + Vector<int> polygon = navigation_polygon->get_polygon(i); + Vector<Vector2> debug_polygon_vertices; + debug_polygon_vertices.resize(polygon.size()); + for (int j = 0; j < polygon.size(); j++) { + ERR_FAIL_INDEX(polygon[j], navigation_polygon_vertices.size()); + debug_polygon_vertices.write[j] = navigation_polygon_vertices[polygon[j]]; + } + + // Generate the polygon color, slightly randomly modified from the settings one. + Color random_variation_color = debug_face_color; + if (enabled_geometry_face_random_color) { + random_variation_color.set_hsv( + debug_face_color.get_h() + rand.random(-1.0, 1.0) * 0.1, + debug_face_color.get_s(), + debug_face_color.get_v() + rand.random(-1.0, 1.0) * 0.2); + } + random_variation_color.a = debug_face_color.a; + + Vector<Color> debug_face_colors; + debug_face_colors.push_back(random_variation_color); + RS::get_singleton()->canvas_item_add_polygon(get_canvas_item(), debug_polygon_vertices, debug_face_colors); + + if (enabled_edge_lines) { + Vector<Color> debug_edge_colors; + debug_edge_colors.push_back(debug_edge_color); + debug_polygon_vertices.push_back(debug_polygon_vertices[0]); // Add first again for closing polyline. + RS::get_singleton()->canvas_item_add_polyline(get_canvas_item(), debug_polygon_vertices, debug_edge_colors); + } + } +} +#endif // DEBUG_ENABLED + +#ifdef DEBUG_ENABLED +void NavigationRegion2D::_update_debug_edge_connections_mesh() { + const NavigationServer2D *ns2d = NavigationServer2D::get_singleton(); + bool enable_edge_connections = use_edge_connections && ns2d->get_debug_navigation_enable_edge_connections() && ns2d->map_get_use_edge_connections(get_world_2d()->get_navigation_map()); + + if (enable_edge_connections) { + Color debug_edge_connection_color = ns2d->get_debug_navigation_edge_connection_color(); + // Draw the region edge connections. + Transform2D xform = get_global_transform(); + real_t radius = ns2d->map_get_edge_connection_margin(get_world_2d()->get_navigation_map()) / 2.0; + for (int i = 0; i < ns2d->region_get_connections_count(region); i++) { + // Two main points + Vector2 a = ns2d->region_get_connection_pathway_start(region, i); + a = xform.affine_inverse().xform(a); + Vector2 b = ns2d->region_get_connection_pathway_end(region, i); + b = xform.affine_inverse().xform(b); + draw_line(a, b, debug_edge_connection_color); + + // Draw a circle to illustrate the margins. + real_t angle = a.angle_to_point(b); + draw_arc(a, radius, angle + Math_PI / 2.0, angle - Math_PI / 2.0 + Math_TAU, 10, debug_edge_connection_color); + draw_arc(b, radius, angle - Math_PI / 2.0, angle + Math_PI / 2.0, 10, debug_edge_connection_color); + } + } +} +#endif // DEBUG_ENABLED diff --git a/scene/2d/navigation_region_2d.h b/scene/2d/navigation_region_2d.h index 39de0a0004..642f1663cd 100644 --- a/scene/2d/navigation_region_2d.h +++ b/scene/2d/navigation_region_2d.h @@ -52,7 +52,13 @@ class NavigationRegion2D : public Node2D { Transform2D current_global_transform; void _navigation_polygon_changed(); - void _map_changed(RID p_RID); + +#ifdef DEBUG_ENABLED +private: + void _update_debug_mesh(); + void _update_debug_edge_connections_mesh(); + void _navigation_map_changed(RID p_map); +#endif // DEBUG_ENABLED protected: void _notification(int p_what); diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp index 165d44436c..4c2f56b7b3 100644 --- a/scene/3d/navigation_region_3d.cpp +++ b/scene/3d/navigation_region_3d.cpp @@ -30,7 +30,8 @@ #include "navigation_region_3d.h" -#include "mesh_instance_3d.h" +#include "core/core_string_names.h" +#include "scene/resources/navigation_mesh_source_geometry_data_3d.h" #include "servers/navigation_server_3d.h" void NavigationRegion3D::set_enabled(bool p_enabled) { @@ -179,12 +180,10 @@ void NavigationRegion3D::_notification(int p_what) { _update_debug_mesh(); } #endif // DEBUG_ENABLED - } break; case NOTIFICATION_TRANSFORM_CHANGED: { set_physics_process_internal(true); - } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { @@ -224,13 +223,13 @@ void NavigationRegion3D::set_navigation_mesh(const Ref<NavigationMesh> &p_naviga } if (navigation_mesh.is_valid()) { - navigation_mesh->disconnect("changed", callable_mp(this, &NavigationRegion3D::_navigation_changed)); + navigation_mesh->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NavigationRegion3D::_navigation_mesh_changed)); } navigation_mesh = p_navigation_mesh; if (navigation_mesh.is_valid()) { - navigation_mesh->connect("changed", callable_mp(this, &NavigationRegion3D::_navigation_changed)); + navigation_mesh->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NavigationRegion3D::_navigation_mesh_changed)); } NavigationServer3D::get_singleton()->region_set_navigation_mesh(region, p_navigation_mesh); @@ -263,6 +262,7 @@ Ref<NavigationMesh> NavigationRegion3D::get_navigation_mesh() const { struct BakeThreadsArgs { NavigationRegion3D *nav_region = nullptr; + Ref<NavigationMeshSourceGeometryData3D> source_geometry_data; }; void _bake_navigation_mesh(void *p_user_data) { @@ -270,8 +270,9 @@ void _bake_navigation_mesh(void *p_user_data) { if (args->nav_region->get_navigation_mesh().is_valid()) { Ref<NavigationMesh> nav_mesh = args->nav_region->get_navigation_mesh()->duplicate(); + Ref<NavigationMeshSourceGeometryData3D> source_geometry_data = args->source_geometry_data; - NavigationServer3D::get_singleton()->region_bake_navigation_mesh(nav_mesh, args->nav_region); + NavigationServer3D::get_singleton()->bake_from_source_geometry_data(nav_mesh, source_geometry_data); args->nav_region->call_deferred(SNAME("_bake_finished"), nav_mesh); memdelete(args); } else { @@ -282,10 +283,18 @@ void _bake_navigation_mesh(void *p_user_data) { } void NavigationRegion3D::bake_navigation_mesh(bool p_on_thread) { + ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred()."); + ERR_FAIL_COND_MSG(!navigation_mesh.is_valid(), "Baking the navigation mesh requires a valid `NavigationMesh` resource."); ERR_FAIL_COND_MSG(bake_thread.is_started(), "Unable to start another bake request. The navigation mesh bake thread is already baking a navigation mesh."); + Ref<NavigationMeshSourceGeometryData3D> source_geometry_data; + source_geometry_data.instantiate(); + + NavigationServer3D::get_singleton()->parse_source_geometry_data(navigation_mesh, source_geometry_data, this); + BakeThreadsArgs *args = memnew(BakeThreadsArgs); args->nav_region = this; + args->source_geometry_data = source_geometry_data; if (p_on_thread) { bake_thread.start(_bake_navigation_mesh, args); @@ -371,7 +380,7 @@ bool NavigationRegion3D::_get(const StringName &p_name, Variant &r_ret) const { } #endif // DISABLE_DEPRECATED -void NavigationRegion3D::_navigation_changed() { +void NavigationRegion3D::_navigation_mesh_changed() { update_gizmos(); update_configuration_warnings(); @@ -397,9 +406,9 @@ NavigationRegion3D::NavigationRegion3D() { NavigationServer3D::get_singleton()->region_set_travel_cost(region, get_travel_cost()); #ifdef DEBUG_ENABLED - NavigationServer3D::get_singleton()->connect("map_changed", callable_mp(this, &NavigationRegion3D::_navigation_map_changed)); - NavigationServer3D::get_singleton()->connect("navigation_debug_changed", callable_mp(this, &NavigationRegion3D::_update_debug_mesh)); - NavigationServer3D::get_singleton()->connect("navigation_debug_changed", callable_mp(this, &NavigationRegion3D::_update_debug_edge_connections_mesh)); + NavigationServer3D::get_singleton()->connect(SNAME("map_changed"), callable_mp(this, &NavigationRegion3D::_navigation_map_changed)); + NavigationServer3D::get_singleton()->connect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationRegion3D::_update_debug_mesh)); + NavigationServer3D::get_singleton()->connect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationRegion3D::_update_debug_edge_connections_mesh)); #endif // DEBUG_ENABLED } @@ -409,15 +418,15 @@ NavigationRegion3D::~NavigationRegion3D() { } if (navigation_mesh.is_valid()) { - navigation_mesh->disconnect("changed", callable_mp(this, &NavigationRegion3D::_navigation_changed)); + navigation_mesh->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NavigationRegion3D::_navigation_mesh_changed)); } ERR_FAIL_NULL(NavigationServer3D::get_singleton()); NavigationServer3D::get_singleton()->free(region); #ifdef DEBUG_ENABLED - NavigationServer3D::get_singleton()->disconnect("map_changed", callable_mp(this, &NavigationRegion3D::_navigation_map_changed)); - NavigationServer3D::get_singleton()->disconnect("navigation_debug_changed", callable_mp(this, &NavigationRegion3D::_update_debug_mesh)); - NavigationServer3D::get_singleton()->disconnect("navigation_debug_changed", callable_mp(this, &NavigationRegion3D::_update_debug_edge_connections_mesh)); + NavigationServer3D::get_singleton()->disconnect(SNAME("map_changed"), callable_mp(this, &NavigationRegion3D::_navigation_map_changed)); + NavigationServer3D::get_singleton()->disconnect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationRegion3D::_update_debug_mesh)); + NavigationServer3D::get_singleton()->disconnect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationRegion3D::_update_debug_edge_connections_mesh)); ERR_FAIL_NULL(RenderingServer::get_singleton()); if (debug_instance.is_valid()) { diff --git a/scene/3d/navigation_region_3d.h b/scene/3d/navigation_region_3d.h index fd6deebd63..84b57d064f 100644 --- a/scene/3d/navigation_region_3d.h +++ b/scene/3d/navigation_region_3d.h @@ -50,7 +50,7 @@ class NavigationRegion3D : public Node3D { Thread bake_thread; - void _navigation_changed(); + void _navigation_mesh_changed(); #ifdef DEBUG_ENABLED RID debug_instance; diff --git a/scene/3d/vehicle_body_3d.cpp b/scene/3d/vehicle_body_3d.cpp index d0c186fb29..e23703a6dc 100644 --- a/scene/3d/vehicle_body_3d.cpp +++ b/scene/3d/vehicle_body_3d.cpp @@ -274,9 +274,9 @@ void VehicleWheel3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_steering"), &VehicleWheel3D::get_steering); ADD_GROUP("Per-Wheel Motion", ""); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "engine_force", PROPERTY_HINT_RANGE, U"-1024,1024.0,0.01,or_greater,suffix:kg\u22C5m/s\u00B2 (N)"), "set_engine_force", "get_engine_force"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "brake", PROPERTY_HINT_RANGE, U"0.0,1.0,0.01,suffix:kg\u22C5m/s\u00B2 (N)"), "set_brake", "get_brake"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "steering", PROPERTY_HINT_RANGE, "-180,180.0,0.01,radians"), "set_steering", "get_steering"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "engine_force", PROPERTY_HINT_RANGE, U"-1024,1024,0.01,or_less,or_greater,suffix:kg\u22C5m/s\u00B2 (N)"), "set_engine_force", "get_engine_force"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "brake", PROPERTY_HINT_RANGE, U"-128,128,0.01,or_less,or_greater,suffix:kg\u22C5m/s\u00B2 (N)"), "set_brake", "get_brake"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "steering", PROPERTY_HINT_RANGE, "-180,180,0.01,radians"), "set_steering", "get_steering"); ADD_GROUP("VehicleBody3D Motion", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_as_traction"), "set_use_as_traction", "is_used_as_traction"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_as_steering"), "set_use_as_steering", "is_used_as_steering"); @@ -918,9 +918,9 @@ void VehicleBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_steering"), &VehicleBody3D::get_steering); ADD_GROUP("Motion", ""); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "engine_force", PROPERTY_HINT_RANGE, U"-1024,1024.0,0.01,or_greater,suffix:kg\u22C5m/s\u00B2 (N)"), "set_engine_force", "get_engine_force"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "brake", PROPERTY_HINT_RANGE, U"0.0,1.0,0.01,suffix:kg\u22C5m/s\u00B2 (N)"), "set_brake", "get_brake"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "steering", PROPERTY_HINT_RANGE, "-180,180.0,0.01,radians"), "set_steering", "get_steering"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "engine_force", PROPERTY_HINT_RANGE, U"-1024,1024,0.01,or_less,or_greater,suffix:kg\u22C5m/s\u00B2 (N)"), "set_engine_force", "get_engine_force"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "brake", PROPERTY_HINT_RANGE, U"-128,128,0.01,or_less,or_greater,suffix:kg\u22C5m/s\u00B2 (N)"), "set_brake", "get_brake"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "steering", PROPERTY_HINT_RANGE, "-180,180,0.01,radians"), "set_steering", "get_steering"); } VehicleBody3D::VehicleBody3D() { diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index a4fe24dfd9..9706fb1a5d 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -165,6 +165,7 @@ #include "scene/resources/mesh_data_tool.h" #include "scene/resources/multimesh.h" #include "scene/resources/navigation_mesh.h" +#include "scene/resources/navigation_mesh_source_geometry_data_3d.h" #include "scene/resources/navigation_polygon.h" #include "scene/resources/packed_scene.h" #include "scene/resources/particle_process_material.h" @@ -930,6 +931,7 @@ void register_scene_types() { GDREGISTER_CLASS(PathFollow2D); GDREGISTER_CLASS(NavigationMesh); + GDREGISTER_CLASS(NavigationMeshSourceGeometryData3D); GDREGISTER_CLASS(NavigationPolygon); GDREGISTER_CLASS(NavigationRegion2D); GDREGISTER_CLASS(NavigationAgent2D); diff --git a/scene/resources/navigation_mesh_source_geometry_data_3d.cpp b/scene/resources/navigation_mesh_source_geometry_data_3d.cpp new file mode 100644 index 0000000000..0201fb70b2 --- /dev/null +++ b/scene/resources/navigation_mesh_source_geometry_data_3d.cpp @@ -0,0 +1,181 @@ +/**************************************************************************/ +/* navigation_mesh_source_geometry_data_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "navigation_mesh_source_geometry_data_3d.h" + +void NavigationMeshSourceGeometryData3D::set_vertices(const Vector<float> &p_vertices) { + vertices = p_vertices; +} + +void NavigationMeshSourceGeometryData3D::set_indices(const Vector<int> &p_indices) { + indices = p_indices; +} + +void NavigationMeshSourceGeometryData3D::clear() { + vertices.clear(); + indices.clear(); +} + +void NavigationMeshSourceGeometryData3D::_add_vertex(const Vector3 &p_vec3) { + vertices.push_back(p_vec3.x); + vertices.push_back(p_vec3.y); + vertices.push_back(p_vec3.z); +} + +void NavigationMeshSourceGeometryData3D::_add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform) { + int current_vertex_count; + for (int i = 0; i < p_mesh->get_surface_count(); i++) { + current_vertex_count = vertices.size() / 3; + + if (p_mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { + continue; + } + + int index_count = 0; + if (p_mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { + index_count = p_mesh->surface_get_array_index_len(i); + } else { + index_count = p_mesh->surface_get_array_len(i); + } + + ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0)); + + int face_count = index_count / 3; + + Array a = p_mesh->surface_get_arrays(i); + + Vector<Vector3> mesh_vertices = a[Mesh::ARRAY_VERTEX]; + const Vector3 *vr = mesh_vertices.ptr(); + + if (p_mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { + Vector<int> mesh_indices = a[Mesh::ARRAY_INDEX]; + const int *ir = mesh_indices.ptr(); + + for (int j = 0; j < mesh_vertices.size(); j++) { + _add_vertex(p_xform.xform(vr[j])); + } + + for (int j = 0; j < face_count; j++) { + // CCW + indices.push_back(current_vertex_count + (ir[j * 3 + 0])); + indices.push_back(current_vertex_count + (ir[j * 3 + 2])); + indices.push_back(current_vertex_count + (ir[j * 3 + 1])); + } + } else { + face_count = mesh_vertices.size() / 3; + for (int j = 0; j < face_count; j++) { + _add_vertex(p_xform.xform(vr[j * 3 + 0])); + _add_vertex(p_xform.xform(vr[j * 3 + 2])); + _add_vertex(p_xform.xform(vr[j * 3 + 1])); + + indices.push_back(current_vertex_count + (j * 3 + 0)); + indices.push_back(current_vertex_count + (j * 3 + 1)); + indices.push_back(current_vertex_count + (j * 3 + 2)); + } + } + } +} + +void NavigationMeshSourceGeometryData3D::_add_mesh_array(const Array &p_mesh_array, const Transform3D &p_xform) { + Vector<Vector3> mesh_vertices = p_mesh_array[Mesh::ARRAY_VERTEX]; + const Vector3 *vr = mesh_vertices.ptr(); + + Vector<int> mesh_indices = p_mesh_array[Mesh::ARRAY_INDEX]; + const int *ir = mesh_indices.ptr(); + + const int face_count = mesh_indices.size() / 3; + const int current_vertex_count = vertices.size() / 3; + + for (int j = 0; j < mesh_vertices.size(); j++) { + _add_vertex(p_xform.xform(vr[j])); + } + + for (int j = 0; j < face_count; j++) { + // CCW + indices.push_back(current_vertex_count + (ir[j * 3 + 0])); + indices.push_back(current_vertex_count + (ir[j * 3 + 2])); + indices.push_back(current_vertex_count + (ir[j * 3 + 1])); + } +} + +void NavigationMeshSourceGeometryData3D::_add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform) { + int face_count = p_faces.size() / 3; + int current_vertex_count = vertices.size() / 3; + + for (int j = 0; j < face_count; j++) { + _add_vertex(p_xform.xform(p_faces[j * 3 + 0])); + _add_vertex(p_xform.xform(p_faces[j * 3 + 1])); + _add_vertex(p_xform.xform(p_faces[j * 3 + 2])); + + indices.push_back(current_vertex_count + (j * 3 + 0)); + indices.push_back(current_vertex_count + (j * 3 + 2)); + indices.push_back(current_vertex_count + (j * 3 + 1)); + } +} + +void NavigationMeshSourceGeometryData3D::add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform) { + ERR_FAIL_COND(!p_mesh.is_valid()); + _add_mesh(p_mesh, root_node_transform * p_xform); +} + +void NavigationMeshSourceGeometryData3D::add_mesh_array(const Array &p_mesh_array, const Transform3D &p_xform) { + ERR_FAIL_COND(p_mesh_array.size() != Mesh::ARRAY_MAX); + _add_mesh_array(p_mesh_array, root_node_transform * p_xform); +} + +void NavigationMeshSourceGeometryData3D::add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform) { + ERR_FAIL_COND(p_faces.size() % 3 != 0); + _add_faces(p_faces, root_node_transform * p_xform); +} + +void NavigationMeshSourceGeometryData3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &NavigationMeshSourceGeometryData3D::set_vertices); + ClassDB::bind_method(D_METHOD("get_vertices"), &NavigationMeshSourceGeometryData3D::get_vertices); + + ClassDB::bind_method(D_METHOD("set_indices", "indices"), &NavigationMeshSourceGeometryData3D::set_indices); + ClassDB::bind_method(D_METHOD("get_indices"), &NavigationMeshSourceGeometryData3D::get_indices); + + ClassDB::bind_method(D_METHOD("clear"), &NavigationMeshSourceGeometryData3D::clear); + ClassDB::bind_method(D_METHOD("has_data"), &NavigationMeshSourceGeometryData3D::has_data); + + ClassDB::bind_method(D_METHOD("add_mesh", "mesh", "xform"), &NavigationMeshSourceGeometryData3D::add_mesh); + ClassDB::bind_method(D_METHOD("add_mesh_array", "mesh_array", "xform"), &NavigationMeshSourceGeometryData3D::add_mesh_array); + ClassDB::bind_method(D_METHOD("add_faces", "faces", "xform"), &NavigationMeshSourceGeometryData3D::add_faces); + + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "indices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_indices", "get_indices"); +} + +NavigationMeshSourceGeometryData3D::NavigationMeshSourceGeometryData3D() { +} + +NavigationMeshSourceGeometryData3D::~NavigationMeshSourceGeometryData3D() { + clear(); +} diff --git a/scene/resources/navigation_mesh_source_geometry_data_3d.h b/scene/resources/navigation_mesh_source_geometry_data_3d.h new file mode 100644 index 0000000000..ec8bddd4dd --- /dev/null +++ b/scene/resources/navigation_mesh_source_geometry_data_3d.h @@ -0,0 +1,75 @@ +/**************************************************************************/ +/* navigation_mesh_source_geometry_data_3d.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 NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_3D_H +#define NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_3D_H + +#include "scene/3d/visual_instance_3d.h" + +class NavigationMeshSourceGeometryData3D : public Resource { + GDCLASS(NavigationMeshSourceGeometryData3D, Resource); + + Vector<float> vertices; + Vector<int> indices; + +protected: + static void _bind_methods(); + +private: + void _add_vertex(const Vector3 &p_vec3); + void _add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform); + void _add_mesh_array(const Array &p_array, const Transform3D &p_xform); + void _add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform); + +public: + // kept root node transform here on the geometry data + // if we add this transform to all exposed functions we need to break comp on all functions later + // when navmesh changes from global transform to relative to navregion + // but if it stays here we can just remove it and change the internal functions only + Transform3D root_node_transform; + + void set_vertices(const Vector<float> &p_vertices); + const Vector<float> &get_vertices() const { return vertices; } + + void set_indices(const Vector<int> &p_indices); + const Vector<int> &get_indices() const { return indices; } + + bool has_data() { return vertices.size() && indices.size(); }; + void clear(); + + void add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform); + void add_mesh_array(const Array &p_mesh_array, const Transform3D &p_xform); + void add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform); + + NavigationMeshSourceGeometryData3D(); + ~NavigationMeshSourceGeometryData3D(); +}; + +#endif // NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_3D_H diff --git a/servers/navigation_server_3d.cpp b/servers/navigation_server_3d.cpp index 45b85a616e..2536ce719d 100644 --- a/servers/navigation_server_3d.cpp +++ b/servers/navigation_server_3d.cpp @@ -143,6 +143,9 @@ void NavigationServer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("obstacle_set_vertices", "obstacle", "vertices"), &NavigationServer3D::obstacle_set_vertices); ClassDB::bind_method(D_METHOD("obstacle_set_avoidance_layers", "obstacle", "layers"), &NavigationServer3D::obstacle_set_avoidance_layers); + ClassDB::bind_method(D_METHOD("parse_source_geometry_data", "navigation_mesh", "source_geometry_data", "root_node", "callback"), &NavigationServer3D::parse_source_geometry_data); + ClassDB::bind_method(D_METHOD("bake_from_source_geometry_data", "navigation_mesh", "source_geometry_data", "callback"), &NavigationServer3D::bake_from_source_geometry_data); + ClassDB::bind_method(D_METHOD("free_rid", "rid"), &NavigationServer3D::free); ClassDB::bind_method(D_METHOD("set_active", "active"), &NavigationServer3D::set_active); diff --git a/servers/navigation_server_3d.h b/servers/navigation_server_3d.h index da3741ed8b..a524f0f595 100644 --- a/servers/navigation_server_3d.h +++ b/servers/navigation_server_3d.h @@ -35,6 +35,7 @@ #include "core/templates/rid.h" #include "scene/3d/navigation_region_3d.h" +#include "scene/resources/navigation_mesh_source_geometry_data_3d.h" #include "servers/navigation/navigation_path_query_parameters_3d.h" #include "servers/navigation/navigation_path_query_result_3d.h" @@ -291,6 +292,9 @@ public: virtual NavigationUtilities::PathQueryResult _query_path(const NavigationUtilities::PathQueryParameters &p_parameters) const = 0; + virtual void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) = 0; + virtual void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) = 0; + NavigationServer3D(); ~NavigationServer3D() override; diff --git a/servers/navigation_server_3d_dummy.h b/servers/navigation_server_3d_dummy.h index d2f11ccfa0..a0d97503ba 100644 --- a/servers/navigation_server_3d_dummy.h +++ b/servers/navigation_server_3d_dummy.h @@ -135,6 +135,8 @@ public: void obstacle_set_position(RID p_obstacle, Vector3 p_position) override {} void obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) override {} void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers) override {} + void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override {} + void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override {} void free(RID p_object) override {} void set_active(bool p_active) override {} void process(real_t delta_time) override {} diff --git a/tests/scene/test_navigation_obstacle_2d.h b/tests/scene/test_navigation_obstacle_2d.h new file mode 100644 index 0000000000..97d28e0a48 --- /dev/null +++ b/tests/scene/test_navigation_obstacle_2d.h @@ -0,0 +1,69 @@ +/**************************************************************************/ +/* test_navigation_obstacle_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_NAVIGATION_OBSTACLE_2D_H +#define TEST_NAVIGATION_OBSTACLE_2D_H + +#include "scene/2d/navigation_obstacle_2d.h" +#include "scene/main/window.h" + +#include "tests/test_macros.h" + +namespace TestNavigationObstacle2D { + +TEST_SUITE("[Navigation]") { + TEST_CASE("[SceneTree][NavigationObstacle2D] New obstacle should have valid RID") { + NavigationObstacle2D *obstacle_node = memnew(NavigationObstacle2D); + CHECK(obstacle_node->get_rid().is_valid()); + memdelete(obstacle_node); + } + + TEST_CASE("[SceneTree][NavigationObstacle2D] New obstacle should attach to default map") { + Node2D *node_2d = memnew(Node2D); + SceneTree::get_singleton()->get_root()->add_child(node_2d); + + NavigationObstacle2D *obstacle_node = memnew(NavigationObstacle2D); + // obstacle should not be attached to any map when outside of tree + CHECK_FALSE(obstacle_node->get_navigation_map().is_valid()); + + SUBCASE("Obstacle should attach to default map when it enters the tree") { + node_2d->add_child(obstacle_node); + CHECK(obstacle_node->get_navigation_map().is_valid()); + CHECK(obstacle_node->get_navigation_map() == node_2d->get_world_2d()->get_navigation_map()); + } + + memdelete(obstacle_node); + memdelete(node_2d); + } +} + +} //namespace TestNavigationObstacle2D + +#endif // TEST_NAVIGATION_OBSTACLE_2D_H diff --git a/tests/scene/test_navigation_obstacle_3d.h b/tests/scene/test_navigation_obstacle_3d.h new file mode 100644 index 0000000000..8769f4fb64 --- /dev/null +++ b/tests/scene/test_navigation_obstacle_3d.h @@ -0,0 +1,69 @@ +/**************************************************************************/ +/* test_navigation_obstacle_3d.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_NAVIGATION_OBSTACLE_3D_H +#define TEST_NAVIGATION_OBSTACLE_3D_H + +#include "scene/3d/navigation_obstacle_3d.h" +#include "scene/main/window.h" + +#include "tests/test_macros.h" + +namespace TestNavigationObstacle3D { + +TEST_SUITE("[Navigation]") { + TEST_CASE("[SceneTree][NavigationObstacle3D] New obstacle should have valid RID") { + NavigationObstacle3D *obstacle_node = memnew(NavigationObstacle3D); + CHECK(obstacle_node->get_rid().is_valid()); + memdelete(obstacle_node); + } + + TEST_CASE("[SceneTree][NavigationObstacle3D] New obstacle should attach to default map") { + Node3D *node_3d = memnew(Node3D); + SceneTree::get_singleton()->get_root()->add_child(node_3d); + + NavigationObstacle3D *obstacle_node = memnew(NavigationObstacle3D); + // obstacle should not be attached to any map when outside of tree + CHECK_FALSE(obstacle_node->get_navigation_map().is_valid()); + + SUBCASE("Obstacle should attach to default map when it enters the tree") { + node_3d->add_child(obstacle_node); + CHECK(obstacle_node->get_navigation_map().is_valid()); + CHECK(obstacle_node->get_navigation_map() == node_3d->get_world_3d()->get_navigation_map()); + } + + memdelete(obstacle_node); + memdelete(node_3d); + } +} + +} //namespace TestNavigationObstacle3D + +#endif // TEST_NAVIGATION_OBSTACLE_3D_H diff --git a/tests/scene/test_navigation_region_2d.h b/tests/scene/test_navigation_region_2d.h new file mode 100644 index 0000000000..4574893c8d --- /dev/null +++ b/tests/scene/test_navigation_region_2d.h @@ -0,0 +1,51 @@ +/**************************************************************************/ +/* test_navigation_region_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_NAVIGATION_REGION_2D_H +#define TEST_NAVIGATION_REGION_2D_H + +#include "scene/2d/navigation_region_2d.h" +#include "scene/main/window.h" + +#include "tests/test_macros.h" + +namespace TestNavigationRegion2D { + +TEST_SUITE("[Navigation]") { + TEST_CASE("[SceneTree][NavigationRegion2D] New region should have valid RID") { + NavigationRegion2D *region_node = memnew(NavigationRegion2D); + CHECK(region_node->get_region_rid().is_valid()); + memdelete(region_node); + } +} + +} //namespace TestNavigationRegion2D + +#endif // TEST_NAVIGATION_REGION_2D_H diff --git a/tests/scene/test_navigation_region_3d.h b/tests/scene/test_navigation_region_3d.h new file mode 100644 index 0000000000..4c5ae34615 --- /dev/null +++ b/tests/scene/test_navigation_region_3d.h @@ -0,0 +1,51 @@ +/**************************************************************************/ +/* test_navigation_region_3d.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_NAVIGATION_REGION_3D_H +#define TEST_NAVIGATION_REGION_3D_H + +#include "scene/3d/navigation_region_3d.h" +#include "scene/main/window.h" + +#include "tests/test_macros.h" + +namespace TestNavigationRegion3D { + +TEST_SUITE("[Navigation]") { + TEST_CASE("[SceneTree][NavigationRegion3D] New region should have valid RID") { + NavigationRegion3D *region_node = memnew(NavigationRegion3D); + CHECK(region_node->get_region_rid().is_valid()); + memdelete(region_node); + } +} + +} //namespace TestNavigationRegion3D + +#endif // TEST_NAVIGATION_REGION_3D_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index ef071f4115..d0f124bd05 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -98,6 +98,10 @@ #include "tests/scene/test_gradient.h" #include "tests/scene/test_navigation_agent_2d.h" #include "tests/scene/test_navigation_agent_3d.h" +#include "tests/scene/test_navigation_obstacle_2d.h" +#include "tests/scene/test_navigation_obstacle_3d.h" +#include "tests/scene/test_navigation_region_2d.h" +#include "tests/scene/test_navigation_region_3d.h" #include "tests/scene/test_node.h" #include "tests/scene/test_path_2d.h" #include "tests/scene/test_path_3d.h" |