diff options
Diffstat (limited to 'modules')
55 files changed, 1639 insertions, 449 deletions
diff --git a/modules/astcenc/SCsub b/modules/astcenc/SCsub index 0f04f2bc28..691c74b4a7 100644 --- a/modules/astcenc/SCsub +++ b/modules/astcenc/SCsub @@ -29,7 +29,6 @@ thirdparty_sources = [ "astcenc_partition_tables.cpp", "astcenc_percentile_tables.cpp", "astcenc_pick_best_endpoint_format.cpp", - "astcenc_platform_isa_detection.cpp", "astcenc_quantization.cpp", "astcenc_symbolic_physical.cpp", "astcenc_weight_align.cpp", diff --git a/modules/csg/csg.cpp b/modules/csg/csg.cpp index 0bd8e5d9ad..878664bb9c 100644 --- a/modules/csg/csg.cpp +++ b/modules/csg/csg.cpp @@ -467,7 +467,7 @@ void CSGBrushOperation::merge_brushes(Operation p_operation, const CSGBrush &p_b // Use a limit to speed up bvh and limit the depth. #define BVH_LIMIT 8 -int CSGBrushOperation::MeshMerge::_create_bvh(FaceBVH *facebvhptr, FaceBVH **facebvhptrptr, int p_from, int p_size, int p_depth, int &r_max_depth, int &r_max_alloc) { +int CSGBrushOperation::MeshMerge::_create_bvh(FaceBVH *r_facebvhptr, FaceBVH **r_facebvhptrptr, int p_from, int p_size, int p_depth, int &r_max_depth, int &r_max_alloc) { if (p_depth > r_max_depth) { r_max_depth = p_depth; } @@ -478,15 +478,15 @@ int CSGBrushOperation::MeshMerge::_create_bvh(FaceBVH *facebvhptr, FaceBVH **fac if (p_size <= BVH_LIMIT) { for (int i = 0; i < p_size - 1; i++) { - facebvhptrptr[p_from + i]->next = facebvhptrptr[p_from + i + 1] - facebvhptr; + r_facebvhptrptr[p_from + i]->next = r_facebvhptrptr[p_from + i + 1] - r_facebvhptr; } - return facebvhptrptr[p_from] - facebvhptr; + return r_facebvhptrptr[p_from] - r_facebvhptr; } AABB aabb; - aabb = facebvhptrptr[p_from]->aabb; + aabb = r_facebvhptrptr[p_from]->aabb; for (int i = 1; i < p_size; i++) { - aabb.merge_with(facebvhptrptr[p_from + i]->aabb); + aabb.merge_with(r_facebvhptrptr[p_from + i]->aabb); } int li = aabb.get_longest_axis_index(); @@ -494,28 +494,28 @@ int CSGBrushOperation::MeshMerge::_create_bvh(FaceBVH *facebvhptr, FaceBVH **fac switch (li) { case Vector3::AXIS_X: { SortArray<FaceBVH *, FaceBVHCmpX> sort_x; - sort_x.nth_element(0, p_size, p_size / 2, &facebvhptrptr[p_from]); + sort_x.nth_element(0, p_size, p_size / 2, &r_facebvhptrptr[p_from]); //sort_x.sort(&p_bb[p_from],p_size); } break; case Vector3::AXIS_Y: { SortArray<FaceBVH *, FaceBVHCmpY> sort_y; - sort_y.nth_element(0, p_size, p_size / 2, &facebvhptrptr[p_from]); + sort_y.nth_element(0, p_size, p_size / 2, &r_facebvhptrptr[p_from]); //sort_y.sort(&p_bb[p_from],p_size); } break; case Vector3::AXIS_Z: { SortArray<FaceBVH *, FaceBVHCmpZ> sort_z; - sort_z.nth_element(0, p_size, p_size / 2, &facebvhptrptr[p_from]); + sort_z.nth_element(0, p_size, p_size / 2, &r_facebvhptrptr[p_from]); //sort_z.sort(&p_bb[p_from],p_size); } break; } - int left = _create_bvh(facebvhptr, facebvhptrptr, p_from, p_size / 2, p_depth + 1, r_max_depth, r_max_alloc); - int right = _create_bvh(facebvhptr, facebvhptrptr, p_from + p_size / 2, p_size - p_size / 2, p_depth + 1, r_max_depth, r_max_alloc); + int left = _create_bvh(r_facebvhptr, r_facebvhptrptr, p_from, p_size / 2, p_depth + 1, r_max_depth, r_max_alloc); + int right = _create_bvh(r_facebvhptr, r_facebvhptrptr, p_from + p_size / 2, p_size - p_size / 2, p_depth + 1, r_max_depth, r_max_alloc); int index = r_max_alloc++; - FaceBVH *_new = &facebvhptr[index]; + FaceBVH *_new = &r_facebvhptr[index]; _new->aabb = aabb; _new->center = aabb.get_center(); _new->face = -1; @@ -535,13 +535,13 @@ void CSGBrushOperation::MeshMerge::_add_distance(List<IntersectionDistance> &r_i return; } } - IntersectionDistance IntersectionDistance; - IntersectionDistance.is_conormal = p_is_conormal; - IntersectionDistance.distance_squared = p_distance_squared; - intersections.push_back(IntersectionDistance); + IntersectionDistance distance; + distance.is_conormal = p_is_conormal; + distance.distance_squared = p_distance_squared; + intersections.push_back(distance); } -bool CSGBrushOperation::MeshMerge::_bvh_inside(FaceBVH *facebvhptr, int p_max_depth, int p_bvh_first, int p_face_idx) const { +bool CSGBrushOperation::MeshMerge::_bvh_inside(FaceBVH *r_facebvhptr, int p_max_depth, int p_bvh_first, int p_face_idx) const { Face face = faces[p_face_idx]; Vector3 face_points[3] = { points[face.points[0]], @@ -575,7 +575,7 @@ bool CSGBrushOperation::MeshMerge::_bvh_inside(FaceBVH *facebvhptr, int p_max_de while (true) { uint32_t node = stack[level] & NODE_IDX_MASK; - const FaceBVH *current_facebvhptr = &(facebvhptr[node]); + const FaceBVH *current_facebvhptr = &(r_facebvhptr[node]); bool done = false; switch (stack[level] >> VISITED_BIT_SHIFT) { @@ -651,7 +651,7 @@ bool CSGBrushOperation::MeshMerge::_bvh_inside(FaceBVH *facebvhptr, int p_max_de } if (current_facebvhptr->next != -1) { - current_facebvhptr = &facebvhptr[current_facebvhptr->next]; + current_facebvhptr = &r_facebvhptr[current_facebvhptr->next]; } else { current_facebvhptr = nullptr; } diff --git a/modules/csg/csg.h b/modules/csg/csg.h index 473a23e39f..2a0831e1ce 100644 --- a/modules/csg/csg.h +++ b/modules/csg/csg.h @@ -155,8 +155,8 @@ struct CSGBrushOperation { float vertex_snap = 0.0; inline void _add_distance(List<IntersectionDistance> &r_intersectionsA, List<IntersectionDistance> &r_intersectionsB, bool p_from_B, real_t p_distance, bool p_is_conormal) const; - inline bool _bvh_inside(FaceBVH *facebvhptr, int p_max_depth, int p_bvh_first, int p_face_idx) const; - inline int _create_bvh(FaceBVH *facebvhptr, FaceBVH **facebvhptrptr, int p_from, int p_size, int p_depth, int &r_max_depth, int &r_max_alloc); + inline bool _bvh_inside(FaceBVH *r_facebvhptr, int p_max_depth, int p_bvh_first, int p_face_idx) const; + inline int _create_bvh(FaceBVH *r_facebvhptr, FaceBVH **r_facebvhptrptr, int p_from, int p_size, int p_depth, int &r_max_depth, int &r_max_alloc); void add_face(const Vector3 p_points[3], const Vector2 p_uvs[3], bool p_smooth, bool p_invert, const Ref<Material> &p_material, bool p_from_b); void mark_inside_faces(); diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index ba0d49c993..c8fe39dab0 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -476,6 +476,43 @@ void CSGShape3D::_update_collision_faces() { } root_collision_shape->set_faces(physics_faces); + + if (_is_debug_collision_shape_visible()) { + _update_debug_collision_shape(); + } + } +} + +bool CSGShape3D::_is_debug_collision_shape_visible() { + return is_inside_tree() && (get_tree()->is_debugging_collisions_hint() || Engine::get_singleton()->is_editor_hint()); +} + +void CSGShape3D::_update_debug_collision_shape() { + // NOTE: This is called only for the root shape with collision, when root_collision_shape is valid. + + ERR_FAIL_NULL(RenderingServer::get_singleton()); + + if (root_collision_debug_instance.is_null()) { + root_collision_debug_instance = RS::get_singleton()->instance_create(); + } + + Ref<Mesh> debug_mesh = root_collision_shape->get_debug_mesh(); + RS::get_singleton()->instance_set_scenario(root_collision_debug_instance, get_world_3d()->get_scenario()); + RS::get_singleton()->instance_set_base(root_collision_debug_instance, debug_mesh->get_rid()); + RS::get_singleton()->instance_set_transform(root_collision_debug_instance, get_global_transform()); +} + +void CSGShape3D::_clear_debug_collision_shape() { + if (root_collision_debug_instance.is_valid()) { + RS::get_singleton()->free(root_collision_debug_instance); + root_collision_debug_instance = RID(); + } +} + +void CSGShape3D::_on_transform_changed() { + if (root_collision_debug_instance.is_valid() && !debug_shape_old_transform.is_equal_approx(get_global_transform())) { + debug_shape_old_transform = get_global_transform(); + RS::get_singleton()->instance_set_transform(root_collision_debug_instance, debug_shape_old_transform); } } @@ -558,6 +595,7 @@ void CSGShape3D::_notification(int p_what) { set_collision_layer(collision_layer); set_collision_mask(collision_mask); set_collision_priority(collision_priority); + debug_shape_old_transform = get_global_transform(); _make_dirty(); } } break; @@ -567,6 +605,7 @@ void CSGShape3D::_notification(int p_what) { PhysicsServer3D::get_singleton()->free(root_collision_instance); root_collision_instance = RID(); root_collision_shape.unref(); + _clear_debug_collision_shape(); } } break; @@ -574,6 +613,7 @@ void CSGShape3D::_notification(int p_what) { if (use_collision && is_root_shape() && root_collision_instance.is_valid()) { PhysicsServer3D::get_singleton()->body_set_state(root_collision_instance, PhysicsServer3D::BODY_STATE_TRANSFORM, get_global_transform()); } + _on_transform_changed(); } break; } } diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index c244107bfb..d0e4d0c8cd 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -68,6 +68,8 @@ private: real_t collision_priority = 1.0; Ref<ConcavePolygonShape3D> root_collision_shape; RID root_collision_instance; + RID root_collision_debug_instance; + Transform3D debug_shape_old_transform; bool calculate_tangents = true; @@ -107,6 +109,10 @@ private: void _update_shape(); void _update_collision_faces(); + bool _is_debug_collision_shape_visible(); + void _update_debug_collision_shape(); + void _clear_debug_collision_shape(); + void _on_transform_changed(); protected: void _notification(int p_what); diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index d8f12f7232..e3f5502391 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -139,7 +139,7 @@ print(is_instance_of(a, MyClass)) print(is_instance_of(a, MyClass.InnerClass)) [/codeblock] - [b]Note:[/b] If [param value] and/or [param type] are freed objects (see [method @GlobalScope.is_instance_valid]), or [param type] is not one of the above options, this method will raise an runtime error. + [b]Note:[/b] If [param value] and/or [param type] are freed objects (see [method @GlobalScope.is_instance_valid]), or [param type] is not one of the above options, this method will raise a runtime error. See also [method @GlobalScope.typeof], [method type_exists], [method Array.is_same_typed] (and other [Array] methods). </description> </method> @@ -170,6 +170,7 @@ [b]Important:[/b] The path must be absolute. A relative path will always return [code]null[/code]. This function is a simplified version of [method ResourceLoader.load], which can be used for more advanced scenarios. [b]Note:[/b] Files have to be imported into the engine first to load them using this function. If you want to load [Image]s at run-time, you may use [method Image.load]. If you want to import audio files, you can use the snippet described in [member AudioStreamMP3.data]. + [b]Note:[/b] If [member ProjectSettings.editor/export/convert_text_resources_to_binary] is [code]true[/code], [method @GDScript.load] will not be able to read converted files in an exported project. If you rely on run-time loading of files present within the PCK, set [member ProjectSettings.editor/export/convert_text_resources_to_binary] to [code]false[/code]. </description> </method> <method name="preload"> @@ -227,8 +228,8 @@ To iterate over an [Array] backwards, use: [codeblock] var array = [3, 6, 9] - for i in range(array.size(), 0, -1): - print(array[i - 1]) + for i in range(array.size() - 1, -1, -1): + print(array[i]) [/codeblock] Output: [codeblock] @@ -457,6 +458,16 @@ [/codeblock] </description> </annotation> + <annotation name="@export_flags_avoidance"> + <return type="void" /> + <description> + Export an integer property as a bit flag field for navigation avoidance layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/avoidance/layer_1]. + See also [constant PROPERTY_HINT_LAYERS_AVOIDANCE]. + [codeblock] + @export_flags_avoidance var avoidance_layers: int + [/codeblock] + </description> + </annotation> <annotation name="@export_global_dir"> <return type="void" /> <description> diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp index 451af996ec..ce64d79747 100644 --- a/modules/gdscript/editor/gdscript_docgen.cpp +++ b/modules/gdscript/editor/gdscript_docgen.cpp @@ -239,6 +239,7 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c DocData::ConstantDoc const_doc; const_doc.name = val.identifier->name; const_doc.value = String(Variant(val.value)); + const_doc.is_value_valid = true; const_doc.description = val.doc_description; const_doc.enumeration = name; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index c8cdac3702..9092ae2969 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -1336,10 +1336,11 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co push_error(vformat(R"(Getter with type "%s" cannot be used along with setter of type "%s".)", getter_function->datatype.to_string(), setter_function->parameters[0]->datatype.to_string()), member.variable); } } + } + #ifdef DEBUG_ENABLED - parser->ignored_warnings = previously_ignored_warnings; + parser->ignored_warnings = previously_ignored_warnings; #endif // DEBUG_ENABLED - } } } diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index f1ac234d28..829567d734 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -3098,12 +3098,7 @@ String GDScriptLanguage::_get_indentation() const { if (use_space_indentation) { int indent_size = EDITOR_GET("text_editor/behavior/indent/size"); - - String space_indent = ""; - for (int i = 0; i < indent_size; i++) { - space_indent += " "; - } - return space_indent; + return String(" ").repeat(indent_size); } } #endif @@ -3150,12 +3145,7 @@ void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_t } if (i >= p_from_line) { - l = ""; - for (int j = 0; j < indent_stack.size(); j++) { - l += indent; - } - l += st; - + l = indent.repeat(indent_stack.size()) + st; } else if (i > p_to_line) { break; } diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index d3529154cf..3bce258072 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -104,6 +104,7 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); + register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>); // Export grouping annotations. register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>); register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray("")); diff --git a/modules/gdscript/language_server/godot_lsp.h b/modules/gdscript/language_server/godot_lsp.h index 5b5327bdb7..b9a54cf818 100644 --- a/modules/gdscript/language_server/godot_lsp.h +++ b/modules/gdscript/language_server/godot_lsp.h @@ -1005,7 +1005,9 @@ struct CompletionItem { if (commitCharacters.size()) { dict["commitCharacters"] = commitCharacters; } - dict["command"] = command.to_json(); + if (!command.command.is_empty()) { + dict["command"] = command.to_json(); + } } return dict; } diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.gd new file mode 100644 index 0000000000..5ca8ceffdd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.gd @@ -0,0 +1,10 @@ +# GH-72135 + +var _a +@warning_ignore("unused_private_class_variable") +var _b +@warning_ignore("unused_private_class_variable") var _c +var _d + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.out b/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.out new file mode 100644 index 0000000000..fd88d23950 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.out @@ -0,0 +1,9 @@ +GDTEST_OK +>> WARNING +>> Line: 3 +>> UNUSED_PRIVATE_CLASS_VARIABLE +>> The class variable "_a" is declared but never used in the script. +>> WARNING +>> Line: 7 +>> UNUSED_PRIVATE_CLASS_VARIABLE +>> The class variable "_d" is declared but never used in the script. diff --git a/modules/gltf/doc_classes/GLTFAccessor.xml b/modules/gltf/doc_classes/GLTFAccessor.xml index a6517a7fde..e4e4b79d6e 100644 --- a/modules/gltf/doc_classes/GLTFAccessor.xml +++ b/modules/gltf/doc_classes/GLTFAccessor.xml @@ -7,7 +7,7 @@ <tutorials> </tutorials> <members> - <member name="buffer_view" type="int" setter="set_buffer_view" getter="get_buffer_view" default="0"> + <member name="buffer_view" type="int" setter="set_buffer_view" getter="get_buffer_view" default="-1"> </member> <member name="byte_offset" type="int" setter="set_byte_offset" getter="get_byte_offset" default="0"> </member> diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index 2efaaa7d4d..91271da331 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -466,8 +466,8 @@ bool EditorFileSystemImportFormatSupportQueryBlend::query() { } bool found = false; - for (const String &path : mdfind_paths) { - found = _autodetect_path(path.path_join("Contents/MacOS")); + for (const String &found_path : mdfind_paths) { + found = _autodetect_path(found_path.path_join("Contents/MacOS")); if (found) { break; } diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 4a07183b0d..251aa6375f 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -7190,9 +7190,9 @@ PackedByteArray GLTFDocument::_serialize_glb_buffer(Ref<GLTFState> p_state, Erro const int32_t header_size = 12; const int32_t chunk_header_size = 8; - for (int32_t pad_i = 0; pad_i < (chunk_header_size + json.utf8().length()) % 4; pad_i++) { - json += " "; - } + int32_t padding = (chunk_header_size + json.utf8().length()) % 4; + json += String(" ").repeat(padding); + CharString cs = json.utf8(); const uint32_t text_chunk_length = cs.length(); diff --git a/modules/gltf/structures/gltf_accessor.h b/modules/gltf/structures/gltf_accessor.h index 5b4afc79c4..1a5a910048 100644 --- a/modules/gltf/structures/gltf_accessor.h +++ b/modules/gltf/structures/gltf_accessor.h @@ -39,7 +39,7 @@ struct GLTFAccessor : public Resource { friend class GLTFDocument; private: - GLTFBufferViewIndex buffer_view = 0; + GLTFBufferViewIndex buffer_view = -1; int byte_offset = 0; int component_type = 0; bool normalized = false; diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index db8c645558..c77fa98be2 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -907,7 +907,7 @@ void GridMap::_notification(int p_what) { #ifdef DEBUG_ENABLED case NOTIFICATION_ENTER_TREE: { - if (bake_navigation && NavigationServer3D::get_singleton()->get_debug_enabled()) { + if (bake_navigation && NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { _update_navigation_debug_edge_connections(); } } break; @@ -1352,7 +1352,7 @@ void GridMap::_update_octant_navigation_debug_edge_connections_mesh(const Octant ERR_FAIL_COND(!octant_map.has(p_key)); Octant &g = *octant_map[p_key]; - if (!NavigationServer3D::get_singleton()->get_debug_enabled()) { + if (!NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { if (g.navigation_debug_edge_connections_instance.is_valid()) { RS::get_singleton()->instance_set_visible(g.navigation_debug_edge_connections_instance, false); } diff --git a/modules/mbedtls/SCsub b/modules/mbedtls/SCsub index 9133fdef35..7c1204d2b7 100644 --- a/modules/mbedtls/SCsub +++ b/modules/mbedtls/SCsub @@ -100,10 +100,14 @@ if env["builtin_mbedtls"]: thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] env_mbed_tls.Prepend(CPPPATH=["#thirdparty/mbedtls/include/"]) + env_mbed_tls.Append( + CPPDEFINES=[("MBEDTLS_CONFIG_FILE", '\\"thirdparty/mbedtls/include/godot_module_mbedtls_config.h\\"')] + ) env_thirdparty = env_mbed_tls.Clone() env_thirdparty.disable_warnings() env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) + env_thirdparty.Depends(thirdparty_obj, "#thirdparty/mbedtls/include/godot_module_mbedtls_config.h") env.modules_sources += thirdparty_obj diff --git a/modules/mbedtls/crypto_mbedtls.cpp b/modules/mbedtls/crypto_mbedtls.cpp index 5b52af3068..68b213d79e 100644 --- a/modules/mbedtls/crypto_mbedtls.cpp +++ b/modules/mbedtls/crypto_mbedtls.cpp @@ -36,12 +36,14 @@ #include "core/config/project_settings.h" #include "core/io/certs_compressed.gen.h" #include "core/io/compression.h" +#include "core/os/os.h" #ifdef TOOLS_ENABLED #include "editor/editor_settings.h" #endif #define PEM_BEGIN_CRT "-----BEGIN CERTIFICATE-----\n" #define PEM_END_CRT "-----END CERTIFICATE-----\n" +#define PEM_MIN_SIZE 54 #include <mbedtls/debug.h> #include <mbedtls/md.h> @@ -181,6 +183,35 @@ Error X509CertificateMbedTLS::save(String p_path) { return OK; } +String X509CertificateMbedTLS::save_to_string() { + String buffer; + mbedtls_x509_crt *crt = &cert; + while (crt) { + unsigned char w[4096]; + size_t wrote = 0; + int ret = mbedtls_pem_write_buffer(PEM_BEGIN_CRT, PEM_END_CRT, cert.raw.p, cert.raw.len, w, sizeof(w), &wrote); + ERR_FAIL_COND_V_MSG(ret != 0 || wrote == 0, String(), "Error saving the certificate."); + + buffer += String((char *)w, wrote); + crt = crt->next; + } + if (buffer.length() <= PEM_MIN_SIZE) { + // When the returned value of variable 'buffer' would consist of no Base-64 data, return an empty String instead. + return String(); + } + return buffer; +} + +Error X509CertificateMbedTLS::load_from_string(const String &p_string_key) { + ERR_FAIL_COND_V_MSG(locks, ERR_ALREADY_IN_USE, "Certificate is in use"); + CharString cs = p_string_key.utf8(); + + int ret = mbedtls_x509_crt_parse(&cert, (const unsigned char *)cs.get_data(), cs.size()); + ERR_FAIL_COND_V_MSG(ret, FAILED, "Error parsing some certificates: " + itos(ret)); + + return OK; +} + bool HMACContextMbedTLS::is_md_type_allowed(mbedtls_md_type_t p_md_type) { switch (p_md_type) { case MBEDTLS_MD_SHA1: @@ -307,20 +338,26 @@ void CryptoMbedTLS::load_default_certificates(String p_path) { if (!p_path.is_empty()) { // Use certs defined in project settings. default_certs->load(p_path); - } + } else { + // Try to use system certs otherwise. + String system_certs = OS::get_singleton()->get_system_ca_certificates(); + if (!system_certs.is_empty()) { + CharString cs = system_certs.utf8(); + default_certs->load_from_memory((const uint8_t *)cs.get_data(), cs.size()); + print_verbose("Loaded system CA certificates"); + } #ifdef BUILTIN_CERTS_ENABLED - else { - // Use builtin certs only if user did not override it in project settings. - PackedByteArray out; - out.resize(_certs_uncompressed_size + 1); - Compression::decompress(out.ptrw(), _certs_uncompressed_size, _certs_compressed, _certs_compressed_size, Compression::MODE_DEFLATE); - out.write[_certs_uncompressed_size] = 0; // Make sure it ends with string terminator -#ifdef DEBUG_ENABLED - print_verbose("Loaded builtin certs"); + else { + // Use builtin certs if there are no system certs. + PackedByteArray certs; + certs.resize(_certs_uncompressed_size + 1); + Compression::decompress(certs.ptrw(), _certs_uncompressed_size, _certs_compressed, _certs_compressed_size, Compression::MODE_DEFLATE); + certs.write[_certs_uncompressed_size] = 0; // Make sure it ends with string terminator + default_certs->load_from_memory(certs.ptr(), certs.size()); + print_verbose("Loaded builtin CA certificates"); + } #endif - default_certs->load_from_memory(out.ptr(), out.size()); } -#endif } Ref<CryptoKey> CryptoMbedTLS::generate_rsa(int p_bytes) { diff --git a/modules/mbedtls/crypto_mbedtls.h b/modules/mbedtls/crypto_mbedtls.h index 7422ebad3e..0168e1f663 100644 --- a/modules/mbedtls/crypto_mbedtls.h +++ b/modules/mbedtls/crypto_mbedtls.h @@ -85,6 +85,8 @@ public: virtual Error load(String p_path); virtual Error load_from_memory(const uint8_t *p_buffer, int p_len); virtual Error save(String p_path); + virtual String save_to_string(); + virtual Error load_from_string(const String &p_string_key); X509CertificateMbedTLS() { mbedtls_x509_crt_init(&cert); diff --git a/modules/mbedtls/packet_peer_mbed_dtls.cpp b/modules/mbedtls/packet_peer_mbed_dtls.cpp index e8eb32f88d..ed1a97cc2c 100644 --- a/modules/mbedtls/packet_peer_mbed_dtls.cpp +++ b/modules/mbedtls/packet_peer_mbed_dtls.cpp @@ -29,7 +29,6 @@ /**************************************************************************/ #include "packet_peer_mbed_dtls.h" -#include "mbedtls/platform_util.h" #include "core/io/file_access.h" #include "core/io/stream_peer_tls.h" diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index a77b1d83ad..0d6436594e 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -522,12 +522,7 @@ String CSharpLanguage::_get_indentation() const { if (use_space_indentation) { int indent_size = EDITOR_GET("text_editor/behavior/indent/size"); - - String space_indent = ""; - for (int i = 0; i < indent_size; i++) { - space_indent += " "; - } - return space_indent; + return String(" ").repeat(indent_size); } } #endif diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs index b30c8c240e..834beaa131 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs @@ -86,7 +86,8 @@ namespace Godot.SourceGenerators NodeType = 34, HideQuaternionEdit = 35, Password = 36, - Max = 37 + LayersAvoidance = 37, + Max = 38 } [Flags] diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 8504fb2ac6..6b52c8eaaf 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -128,7 +128,7 @@ void BindingsGenerator::TypeInterface::postsetup_enum_type(BindingsGenerator::Ty { // The expected types for parameters and return value in ptrcall are 'int64_t' or 'uint64_t'. r_enum_itype.c_in = "%5%0 %1_in = %1;\n"; - r_enum_itype.c_out = "%5return (%0)%1;\n"; + r_enum_itype.c_out = "%5return (%0)(%1);\n"; r_enum_itype.c_type = "long"; r_enum_itype.c_arg_in = "&%s_in"; } @@ -1893,7 +1893,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte // Assume the index parameter is an enum const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type); CRASH_COND(idx_arg_type == nullptr); - p_output.append("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index)); + p_output.append("(" + idx_arg_type->proxy_name + ")(" + itos(p_iprop.index) + ")"); } else { p_output.append(itos(p_iprop.index)); } @@ -1911,7 +1911,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte // Assume the index parameter is an enum const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type); CRASH_COND(idx_arg_type == nullptr); - p_output.append("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index) + ", "); + p_output.append("(" + idx_arg_type->proxy_name + ")(" + itos(p_iprop.index) + "), "); } else { p_output.append(itos(p_iprop.index) + ", "); } @@ -3286,7 +3286,7 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar break; case Variant::INT: if (r_iarg.type.cname != name_cache.type_int) { - r_iarg.default_argument = "(%s)" + r_iarg.default_argument; + r_iarg.default_argument = "(%s)(" + r_iarg.default_argument + ")"; } break; case Variant::FLOAT: @@ -3508,7 +3508,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype = TypeInterface::create_value_type(String(m_name)); \ if (itype.name != "long" && itype.name != "ulong") { \ itype.c_in = "%5%0 %1_in = %1;\n"; \ - itype.c_out = "%5return (%0)%1;\n"; \ + itype.c_out = "%5return (%0)(%1);\n"; \ itype.c_type = "long"; \ itype.c_arg_in = "&%s_in"; \ } else { \ diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index ca963cbf4f..36f5d8e2ab 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -613,6 +613,43 @@ namespace Godot } /// <summary> + /// Creates a <see cref="Basis"/> with a rotation such that the forward + /// axis (-Z) points towards the <paramref name="target"/> position. + /// The up axis (+Y) points as close to the <paramref name="up"/> vector + /// as possible while staying perpendicular to the forward axis. + /// The resulting Basis is orthonormalized. + /// The <paramref name="target"/> and <paramref name="up"/> vectors + /// cannot be zero, and cannot be parallel to each other. + /// </summary> + /// <param name="target">The position to look at.</param> + /// <param name="up">The relative up direction.</param> + /// <returns>The resulting basis matrix.</returns> + public static Basis LookingAt(Vector3 target, Vector3 up) + { +#if DEBUG + if (target.IsZeroApprox()) + { + throw new ArgumentException("The vector can't be zero.", nameof(target)); + } + if (up.IsZeroApprox()) + { + throw new ArgumentException("The vector can't be zero.", nameof(up)); + } +#endif + Vector3 column2 = -target.Normalized(); + Vector3 column0 = up.Cross(column2); +#if DEBUG + if (column0.IsZeroApprox()) + { + throw new ArgumentException("The target vector and up vector can't be parallel to each other."); + } +#endif + column0.Normalize(); + Vector3 column1 = column2.Cross(column0); + return new Basis(column0, column1, column2); + } + + /// <summary> /// Returns the orthonormalized version of the basis matrix (useful to /// call occasionally to avoid rounding errors for orthogonal matrices). /// This performs a Gram-Schmidt orthonormalization on the basis of the matrix. diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs index b34e95c04d..1e2aaa299f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs @@ -164,14 +164,14 @@ namespace Godot } /// <summary> - /// Returns a copy of the transform rotated such that its - /// -Z axis (forward) points towards the <paramref name="target"/> position. - /// - /// The transform will first be rotated around the given <paramref name="up"/> vector, - /// and then fully aligned to the <paramref name="target"/> by a further rotation around - /// an axis perpendicular to both the <paramref name="target"/> and <paramref name="up"/> vectors. - /// - /// Operations take place in global space. + /// Returns a copy of the transform rotated such that the forward axis (-Z) + /// points towards the <paramref name="target"/> position. + /// The up axis (+Y) points as close to the <paramref name="up"/> vector + /// as possible while staying perpendicular to the forward axis. + /// The resulting transform is orthonormalized. + /// The existing rotation, scale, and skew information from the original transform is discarded. + /// The <paramref name="target"/> and <paramref name="up"/> vectors cannot be zero, + /// cannot be parallel to each other, and are defined in global/parent space. /// </summary> /// <param name="target">The object to look at.</param> /// <param name="up">The relative up direction.</param> @@ -249,24 +249,7 @@ namespace Godot private void SetLookAt(Vector3 eye, Vector3 target, Vector3 up) { - // Make rotation matrix - // Z vector - Vector3 column2 = eye - target; - - column2.Normalize(); - - Vector3 column1 = up; - - Vector3 column0 = column1.Cross(column2); - - // Recompute Y = Z cross X - column1 = column2.Cross(column0); - - column0.Normalize(); - column1.Normalize(); - - Basis = new Basis(column0, column1, column2); - + Basis = Basis.LookingAt(target - eye, up); Origin = eye; } diff --git a/modules/navigation/SCsub b/modules/navigation/SCsub index a9277657f4..46bcb0fba4 100644 --- a/modules/navigation/SCsub +++ b/modules/navigation/SCsub @@ -33,12 +33,14 @@ if env["builtin_recastnavigation"]: env_thirdparty.disable_warnings() env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) -# RVO Thirdparty source files -if env["builtin_rvo2"]: - thirdparty_dir = "#thirdparty/rvo2/" +# RVO 2D Thirdparty source files +if env["builtin_rvo2_2d"]: + thirdparty_dir = "#thirdparty/rvo2/rvo2_2d/" thirdparty_sources = [ - "Agent.cpp", - "KdTree.cpp", + "Agent2d.cpp", + "Obstacle2d.cpp", + "KdTree2d.cpp", + "RVOSimulator2d.cpp", ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] @@ -48,9 +50,24 @@ if env["builtin_rvo2"]: env_thirdparty.disable_warnings() env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) +# RVO 3D Thirdparty source files +if env["builtin_rvo2_3d"]: + thirdparty_dir = "#thirdparty/rvo2/rvo2_3d/" + thirdparty_sources = [ + "Agent3d.cpp", + "KdTree3d.cpp", + "RVOSimulator3d.cpp", + ] + thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] -env.modules_sources += thirdparty_obj + env_navigation.Prepend(CPPPATH=[thirdparty_dir]) + env_thirdparty = env_navigation.Clone() + env_thirdparty.disable_warnings() + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) + + +env.modules_sources += thirdparty_obj # Godot source files diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp index ee9687cf3d..b0b52a6732 100644 --- a/modules/navigation/godot_navigation_server.cpp +++ b/modules/navigation/godot_navigation_server.cpp @@ -166,6 +166,20 @@ real_t GodotNavigationServer::map_get_cell_size(RID p_map) const { return map->get_cell_size(); } +COMMAND_2(map_set_use_edge_connections, RID, p_map, bool, p_enabled) { + NavMap *map = map_owner.get_or_null(p_map); + ERR_FAIL_COND(map == nullptr); + + map->set_use_edge_connections(p_enabled); +} + +bool GodotNavigationServer::map_get_use_edge_connections(RID p_map) const { + NavMap *map = map_owner.get_or_null(p_map); + ERR_FAIL_COND_V(map == nullptr, false); + + return map->get_use_edge_connections(); +} + COMMAND_2(map_set_edge_connection_margin, RID, p_map, real_t, p_connection_margin) { NavMap *map = map_owner.get_or_null(p_map); ERR_FAIL_COND(map == nullptr); @@ -271,6 +285,18 @@ TypedArray<RID> GodotNavigationServer::map_get_agents(RID p_map) const { return agents_rids; } +TypedArray<RID> GodotNavigationServer::map_get_obstacles(RID p_map) const { + TypedArray<RID> obstacles_rids; + const NavMap *map = map_owner.get_or_null(p_map); + ERR_FAIL_COND_V(map == nullptr, obstacles_rids); + const LocalVector<NavObstacle *> obstacles = map->get_obstacles(); + obstacles_rids.resize(obstacles.size()); + for (uint32_t i = 0; i < obstacles.size(); i++) { + obstacles_rids[i] = obstacles[i]->get_self(); + } + return obstacles_rids; +} + RID GodotNavigationServer::region_get_map(RID p_region) const { NavRegion *region = region_owner.get_or_null(p_region); ERR_FAIL_COND_V(region == nullptr, RID()); @@ -300,6 +326,20 @@ RID GodotNavigationServer::region_create() { return rid; } +COMMAND_2(region_set_use_edge_connections, RID, p_region, bool, p_enabled) { + NavRegion *region = region_owner.get_or_null(p_region); + ERR_FAIL_COND(region == nullptr); + + region->set_use_edge_connections(p_enabled); +} + +bool GodotNavigationServer::region_get_use_edge_connections(RID p_region) const { + NavRegion *region = region_owner.get_or_null(p_region); + ERR_FAIL_COND_V(region == nullptr, false); + + return region->get_use_edge_connections(); +} + COMMAND_2(region_set_map, RID, p_region, RID, p_map) { NavRegion *region = region_owner.get_or_null(p_region); ERR_FAIL_COND(region == nullptr); @@ -584,6 +624,34 @@ RID GodotNavigationServer::agent_create() { return rid; } +COMMAND_2(agent_set_avoidance_enabled, RID, p_agent, bool, p_enabled) { + NavAgent *agent = agent_owner.get_or_null(p_agent); + ERR_FAIL_COND(agent == nullptr); + + agent->set_avoidance_enabled(p_enabled); +} + +bool GodotNavigationServer::agent_get_avoidance_enabled(RID p_agent) const { + NavAgent *agent = agent_owner.get_or_null(p_agent); + ERR_FAIL_COND_V(agent == nullptr, false); + + return agent->is_avoidance_enabled(); +} + +COMMAND_2(agent_set_use_3d_avoidance, RID, p_agent, bool, p_enabled) { + NavAgent *agent = agent_owner.get_or_null(p_agent); + ERR_FAIL_COND(agent == nullptr); + + agent->set_use_3d_avoidance(p_enabled); +} + +bool GodotNavigationServer::agent_get_use_3d_avoidance(RID p_agent) const { + NavAgent *agent = agent_owner.get_or_null(p_agent); + ERR_FAIL_COND_V(agent == nullptr, false); + + return agent->get_use_3d_avoidance(); +} + COMMAND_2(agent_set_map, RID, p_agent, RID, p_map) { NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); @@ -605,7 +673,7 @@ COMMAND_2(agent_set_map, RID, p_agent, RID, p_map) { agent->set_map(map); map->add_agent(agent); - if (agent->has_callback()) { + if (agent->has_avoidance_callback()) { map->set_agent_as_controlled(agent); } } @@ -615,63 +683,75 @@ COMMAND_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_distance) { NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->get_agent()->neighborDist_ = p_distance; + agent->set_neighbor_distance(p_distance); } COMMAND_2(agent_set_max_neighbors, RID, p_agent, int, p_count) { NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->get_agent()->maxNeighbors_ = p_count; + agent->set_max_neighbors(p_count); } -COMMAND_2(agent_set_time_horizon, RID, p_agent, real_t, p_time) { +COMMAND_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon) { + ERR_FAIL_COND_MSG(p_time_horizon < 0.0, "Time horizion must be positive."); NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->get_agent()->timeHorizon_ = p_time; + agent->set_time_horizon_agents(p_time_horizon); +} + +COMMAND_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon) { + ERR_FAIL_COND_MSG(p_time_horizon < 0.0, "Time horizion must be positive."); + NavAgent *agent = agent_owner.get_or_null(p_agent); + ERR_FAIL_COND(agent == nullptr); + + agent->set_time_horizon_obstacles(p_time_horizon); } COMMAND_2(agent_set_radius, RID, p_agent, real_t, p_radius) { + ERR_FAIL_COND_MSG(p_radius < 0.0, "Radius must be positive."); NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->get_agent()->radius_ = p_radius; + agent->set_radius(p_radius); } -COMMAND_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed) { +COMMAND_2(agent_set_height, RID, p_agent, real_t, p_height) { + ERR_FAIL_COND_MSG(p_height < 0.0, "Height must be positive."); NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->get_agent()->maxSpeed_ = p_max_speed; + agent->set_height(p_height); } -COMMAND_2(agent_set_velocity, RID, p_agent, Vector3, p_velocity) { +COMMAND_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed) { + ERR_FAIL_COND_MSG(p_max_speed < 0.0, "Max speed must be positive."); NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->get_agent()->velocity_ = RVO::Vector3(p_velocity.x, p_velocity.y, p_velocity.z); + agent->set_max_speed(p_max_speed); } -COMMAND_2(agent_set_target_velocity, RID, p_agent, Vector3, p_velocity) { +COMMAND_2(agent_set_velocity, RID, p_agent, Vector3, p_velocity) { NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->get_agent()->prefVelocity_ = RVO::Vector3(p_velocity.x, p_velocity.y, p_velocity.z); + agent->set_velocity(p_velocity); } -COMMAND_2(agent_set_position, RID, p_agent, Vector3, p_position) { +COMMAND_2(agent_set_velocity_forced, RID, p_agent, Vector3, p_velocity) { NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->get_agent()->position_ = RVO::Vector3(p_position.x, p_position.y, p_position.z); + agent->set_velocity_forced(p_velocity); } -COMMAND_2(agent_set_ignore_y, RID, p_agent, bool, p_ignore) { +COMMAND_2(agent_set_position, RID, p_agent, Vector3, p_position) { NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->get_agent()->ignore_y_ = p_ignore; + agent->set_position(p_position); } bool GodotNavigationServer::agent_is_map_changed(RID p_agent) const { @@ -681,11 +761,11 @@ bool GodotNavigationServer::agent_is_map_changed(RID p_agent) const { return agent->is_map_changed(); } -COMMAND_2(agent_set_callback, RID, p_agent, Callable, p_callback) { +COMMAND_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback) { NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->set_callback(p_callback); + agent->set_avoidance_callback(p_callback); if (agent->get_map()) { if (p_callback.is_valid()) { @@ -696,6 +776,91 @@ COMMAND_2(agent_set_callback, RID, p_agent, Callable, p_callback) { } } +COMMAND_2(agent_set_avoidance_layers, RID, p_agent, uint32_t, p_layers) { + NavAgent *agent = agent_owner.get_or_null(p_agent); + ERR_FAIL_COND(agent == nullptr); + agent->set_avoidance_layers(p_layers); +} + +COMMAND_2(agent_set_avoidance_mask, RID, p_agent, uint32_t, p_mask) { + NavAgent *agent = agent_owner.get_or_null(p_agent); + ERR_FAIL_COND(agent == nullptr); + agent->set_avoidance_mask(p_mask); +} + +COMMAND_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority) { + ERR_FAIL_COND_MSG(p_priority < 0.0, "Avoidance priority must be between 0.0 and 1.0 inclusive."); + ERR_FAIL_COND_MSG(p_priority > 1.0, "Avoidance priority must be between 0.0 and 1.0 inclusive."); + NavAgent *agent = agent_owner.get_or_null(p_agent); + ERR_FAIL_COND(agent == nullptr); + agent->set_avoidance_priority(p_priority); +} + +RID GodotNavigationServer::obstacle_create() { + GodotNavigationServer *mut_this = const_cast<GodotNavigationServer *>(this); + MutexLock lock(mut_this->operations_mutex); + RID rid = obstacle_owner.make_rid(); + NavObstacle *obstacle = obstacle_owner.get_or_null(rid); + obstacle->set_self(rid); + return rid; +} + +COMMAND_2(obstacle_set_map, RID, p_obstacle, RID, p_map) { + NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); + ERR_FAIL_COND(obstacle == nullptr); + + if (obstacle->get_map()) { + if (obstacle->get_map()->get_self() == p_map) { + return; // Pointless + } + + obstacle->get_map()->remove_obstacle(obstacle); + } + + obstacle->set_map(nullptr); + + if (p_map.is_valid()) { + NavMap *map = map_owner.get_or_null(p_map); + ERR_FAIL_COND(map == nullptr); + + obstacle->set_map(map); + map->add_obstacle(obstacle); + } +} + +RID GodotNavigationServer::obstacle_get_map(RID p_obstacle) const { + NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); + ERR_FAIL_COND_V(obstacle == nullptr, RID()); + if (obstacle->get_map()) { + return obstacle->get_map()->get_self(); + } + return RID(); +} + +COMMAND_2(obstacle_set_height, RID, p_obstacle, real_t, p_height) { + NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); + ERR_FAIL_COND(obstacle == nullptr); + obstacle->set_height(p_height); +} + +COMMAND_2(obstacle_set_position, RID, p_obstacle, Vector3, p_position) { + NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); + ERR_FAIL_COND(obstacle == nullptr); + obstacle->set_position(p_position); +} + +void GodotNavigationServer::obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) { + NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); + ERR_FAIL_COND(obstacle == nullptr); + obstacle->set_vertices(p_vertices); +} + +COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers) { + NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); + ERR_FAIL_COND(obstacle == nullptr); + obstacle->set_avoidance_layers(p_layers); +} + COMMAND_1(free, RID, p_object) { if (map_owner.owns(p_object)) { NavMap *map = map_owner.get_or_null(p_object); @@ -718,6 +883,12 @@ COMMAND_1(free, RID, p_object) { agent->set_map(nullptr); } + // Remove any assigned obstacles + for (NavObstacle *obstacle : map->get_obstacles()) { + map->remove_obstacle(obstacle); + obstacle->set_map(nullptr); + } + int map_index = active_maps.find(map); active_maps.remove_at(map_index); active_maps_update_id.remove_at(map_index); @@ -756,6 +927,17 @@ COMMAND_1(free, RID, p_object) { agent_owner.free(p_object); + } else if (obstacle_owner.owns(p_object)) { + NavObstacle *obstacle = obstacle_owner.get_or_null(p_object); + + // Removes this agent from the map if assigned + if (obstacle->get_map() != nullptr) { + obstacle->get_map()->remove_obstacle(obstacle); + obstacle->set_map(nullptr); + } + + obstacle_owner.free(p_object); + } else { ERR_PRINT("Attempted to free a NavigationServer RID that did not exist (or was already freed)."); } diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h index 0b113b77d4..5d68844a9b 100644 --- a/modules/navigation/godot_navigation_server.h +++ b/modules/navigation/godot_navigation_server.h @@ -39,6 +39,7 @@ #include "nav_agent.h" #include "nav_link.h" #include "nav_map.h" +#include "nav_obstacle.h" #include "nav_region.h" /// The commands are functions executed during the `sync` phase. @@ -72,6 +73,7 @@ class GodotNavigationServer : public NavigationServer3D { mutable RID_Owner<NavMap> map_owner; mutable RID_Owner<NavRegion> region_owner; mutable RID_Owner<NavAgent> agent_owner; + mutable RID_Owner<NavObstacle> obstacle_owner; bool active = true; LocalVector<NavMap *> active_maps; @@ -105,6 +107,9 @@ public: COMMAND_2(map_set_cell_size, RID, p_map, real_t, p_cell_size); virtual real_t map_get_cell_size(RID p_map) const override; + COMMAND_2(map_set_use_edge_connections, RID, p_map, bool, p_enabled); + virtual bool map_get_use_edge_connections(RID p_map) const override; + COMMAND_2(map_set_edge_connection_margin, RID, p_map, real_t, p_connection_margin); virtual real_t map_get_edge_connection_margin(RID p_map) const override; @@ -121,11 +126,15 @@ public: virtual TypedArray<RID> map_get_links(RID p_map) const override; virtual TypedArray<RID> map_get_regions(RID p_map) const override; virtual TypedArray<RID> map_get_agents(RID p_map) const override; + virtual TypedArray<RID> map_get_obstacles(RID p_map) const override; virtual void map_force_update(RID p_map) override; virtual RID region_create() override; + COMMAND_2(region_set_use_edge_connections, RID, p_region, bool, p_enabled); + virtual bool region_get_use_edge_connections(RID p_region) const override; + COMMAND_2(region_set_enter_cost, RID, p_region, real_t, p_enter_cost); virtual real_t region_get_enter_cost(RID p_region) const override; COMMAND_2(region_set_travel_cost, RID, p_region, real_t, p_travel_cost); @@ -166,19 +175,35 @@ public: virtual ObjectID link_get_owner_id(RID p_link) const override; virtual RID agent_create() override; + COMMAND_2(agent_set_avoidance_enabled, RID, p_agent, bool, p_enabled); + virtual bool agent_get_avoidance_enabled(RID p_agent) const override; + COMMAND_2(agent_set_use_3d_avoidance, RID, p_agent, bool, p_enabled); + virtual bool agent_get_use_3d_avoidance(RID p_agent) const override; COMMAND_2(agent_set_map, RID, p_agent, RID, p_map); virtual RID agent_get_map(RID p_agent) const override; COMMAND_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_distance); COMMAND_2(agent_set_max_neighbors, RID, p_agent, int, p_count); - COMMAND_2(agent_set_time_horizon, RID, p_agent, real_t, p_time); + COMMAND_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon); + COMMAND_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon); COMMAND_2(agent_set_radius, RID, p_agent, real_t, p_radius); + COMMAND_2(agent_set_height, RID, p_agent, real_t, p_height); COMMAND_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed); COMMAND_2(agent_set_velocity, RID, p_agent, Vector3, p_velocity); - COMMAND_2(agent_set_target_velocity, RID, p_agent, Vector3, p_velocity); + COMMAND_2(agent_set_velocity_forced, RID, p_agent, Vector3, p_velocity); COMMAND_2(agent_set_position, RID, p_agent, Vector3, p_position); - COMMAND_2(agent_set_ignore_y, RID, p_agent, bool, p_ignore); virtual bool agent_is_map_changed(RID p_agent) const override; - COMMAND_2(agent_set_callback, RID, p_agent, Callable, p_callback); + COMMAND_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback); + COMMAND_2(agent_set_avoidance_layers, RID, p_agent, uint32_t, p_layers); + COMMAND_2(agent_set_avoidance_mask, RID, p_agent, uint32_t, p_mask); + COMMAND_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority); + + virtual RID obstacle_create() override; + COMMAND_2(obstacle_set_map, RID, p_obstacle, RID, p_map); + virtual RID obstacle_get_map(RID p_obstacle) const override; + COMMAND_2(obstacle_set_position, RID, p_obstacle, Vector3, p_position); + COMMAND_2(obstacle_set_height, RID, p_obstacle, real_t, p_height); + 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); COMMAND_1(free, RID, p_object); diff --git a/modules/navigation/nav_agent.cpp b/modules/navigation/nav_agent.cpp index 293544c0a5..a0efe4c74c 100644 --- a/modules/navigation/nav_agent.cpp +++ b/modules/navigation/nav_agent.cpp @@ -32,8 +32,66 @@ #include "nav_map.h" +NavAgent::NavAgent() { +} + +void NavAgent::set_avoidance_enabled(bool p_enabled) { + avoidance_enabled = p_enabled; + _update_rvo_agent_properties(); +} + +void NavAgent::set_use_3d_avoidance(bool p_enabled) { + use_3d_avoidance = p_enabled; + _update_rvo_agent_properties(); +} + +void NavAgent::_update_rvo_agent_properties() { + if (use_3d_avoidance) { + rvo_agent_3d.neighborDist_ = neighbor_distance; + rvo_agent_3d.maxNeighbors_ = max_neighbors; + rvo_agent_3d.timeHorizon_ = time_horizon_agents; + rvo_agent_3d.timeHorizonObst_ = time_horizon_obstacles; + rvo_agent_3d.radius_ = radius; + rvo_agent_3d.maxSpeed_ = max_speed; + rvo_agent_3d.position_ = RVO3D::Vector3(position.x, position.y, position.z); + // Replacing the internal velocity directly causes major jitter / bugs due to unpredictable velocity jumps, left line here for testing. + //rvo_agent_3d.velocity_ = RVO3D::Vector3(velocity.x, velocity.y ,velocity.z); + rvo_agent_3d.prefVelocity_ = RVO3D::Vector3(velocity.x, velocity.y, velocity.z); + rvo_agent_3d.height_ = height; + rvo_agent_3d.avoidance_layers_ = avoidance_layers; + rvo_agent_3d.avoidance_mask_ = avoidance_mask; + rvo_agent_3d.avoidance_priority_ = avoidance_priority; + } else { + rvo_agent_2d.neighborDist_ = neighbor_distance; + rvo_agent_2d.maxNeighbors_ = max_neighbors; + rvo_agent_2d.timeHorizon_ = time_horizon_agents; + rvo_agent_2d.timeHorizonObst_ = time_horizon_obstacles; + rvo_agent_2d.radius_ = radius; + rvo_agent_2d.maxSpeed_ = max_speed; + rvo_agent_2d.position_ = RVO2D::Vector2(position.x, position.z); + rvo_agent_2d.elevation_ = position.y; + // Replacing the internal velocity directly causes major jitter / bugs due to unpredictable velocity jumps, left line here for testing. + //rvo_agent_2d.velocity_ = RVO2D::Vector2(velocity.x, velocity.z); + rvo_agent_2d.prefVelocity_ = RVO2D::Vector2(velocity.x, velocity.z); + rvo_agent_2d.height_ = height; + rvo_agent_2d.avoidance_layers_ = avoidance_layers; + rvo_agent_2d.avoidance_mask_ = avoidance_mask; + rvo_agent_2d.avoidance_priority_ = avoidance_priority; + } + + if (map != nullptr) { + if (avoidance_enabled) { + map->set_agent_as_controlled(this); + } else { + map->remove_agent_as_controlled(this); + } + } + agent_dirty = true; +} + void NavAgent::set_map(NavMap *p_map) { map = p_map; + agent_dirty = true; } bool NavAgent::is_map_changed() { @@ -46,25 +104,241 @@ bool NavAgent::is_map_changed() { } } -void NavAgent::set_callback(Callable p_callback) { - callback = p_callback; +void NavAgent::set_avoidance_callback(Callable p_callback) { + avoidance_callback = p_callback; } -bool NavAgent::has_callback() const { - return callback.is_valid(); +bool NavAgent::has_avoidance_callback() const { + return avoidance_callback.is_valid(); } -void NavAgent::dispatch_callback() { - if (!callback.is_valid()) { +void NavAgent::dispatch_avoidance_callback() { + if (!avoidance_callback.is_valid()) { return; } - Vector3 new_velocity = Vector3(agent.newVelocity_.x(), agent.newVelocity_.y(), agent.newVelocity_.z()); + Vector3 new_velocity; + + if (use_3d_avoidance) { + new_velocity = Vector3(rvo_agent_3d.velocity_.x(), rvo_agent_3d.velocity_.y(), rvo_agent_3d.velocity_.z()); + } else { + new_velocity = Vector3(rvo_agent_2d.velocity_.x(), 0.0, rvo_agent_2d.velocity_.y()); + } + + if (clamp_speed) { + new_velocity = new_velocity.limit_length(max_speed); + } // Invoke the callback with the new velocity. Variant args[] = { new_velocity }; const Variant *args_p[] = { &args[0] }; Variant return_value; Callable::CallError call_error; - callback.callp(args_p, 1, return_value, call_error); + + avoidance_callback.callp(args_p, 1, return_value, call_error); +} + +void NavAgent::set_neighbor_distance(real_t p_neighbor_distance) { + neighbor_distance = p_neighbor_distance; + if (use_3d_avoidance) { + rvo_agent_3d.neighborDist_ = neighbor_distance; + } else { + rvo_agent_2d.neighborDist_ = neighbor_distance; + } + agent_dirty = true; +} + +void NavAgent::set_max_neighbors(int p_max_neighbors) { + max_neighbors = p_max_neighbors; + if (use_3d_avoidance) { + rvo_agent_3d.maxNeighbors_ = max_neighbors; + } else { + rvo_agent_2d.maxNeighbors_ = max_neighbors; + } + agent_dirty = true; +} + +void NavAgent::set_time_horizon_agents(real_t p_time_horizon) { + time_horizon_agents = p_time_horizon; + if (use_3d_avoidance) { + rvo_agent_3d.timeHorizon_ = time_horizon_agents; + } else { + rvo_agent_2d.timeHorizon_ = time_horizon_agents; + } + agent_dirty = true; +} + +void NavAgent::set_time_horizon_obstacles(real_t p_time_horizon) { + time_horizon_obstacles = p_time_horizon; + if (use_3d_avoidance) { + rvo_agent_3d.timeHorizonObst_ = time_horizon_obstacles; + } else { + rvo_agent_2d.timeHorizonObst_ = time_horizon_obstacles; + } + agent_dirty = true; +} + +void NavAgent::set_radius(real_t p_radius) { + radius = p_radius; + if (use_3d_avoidance) { + rvo_agent_3d.radius_ = radius; + } else { + rvo_agent_2d.radius_ = radius; + } + agent_dirty = true; +} + +void NavAgent::set_height(real_t p_height) { + height = p_height; + if (use_3d_avoidance) { + rvo_agent_3d.height_ = height; + } else { + rvo_agent_2d.height_ = height; + } + agent_dirty = true; +} + +void NavAgent::set_max_speed(real_t p_max_speed) { + max_speed = p_max_speed; + if (avoidance_enabled) { + if (use_3d_avoidance) { + rvo_agent_3d.maxSpeed_ = max_speed; + } else { + rvo_agent_2d.maxSpeed_ = max_speed; + } + } + agent_dirty = true; +} + +void NavAgent::set_position(const Vector3 p_position) { + position = p_position; + if (avoidance_enabled) { + if (use_3d_avoidance) { + rvo_agent_3d.position_ = RVO3D::Vector3(p_position.x, p_position.y, p_position.z); + } else { + rvo_agent_2d.elevation_ = p_position.y; + rvo_agent_2d.position_ = RVO2D::Vector2(p_position.x, p_position.z); + } + } + agent_dirty = true; +} + +void NavAgent::set_target_position(const Vector3 p_target_position) { + target_position = p_target_position; +} + +void NavAgent::set_velocity(const Vector3 p_velocity) { + // Sets the "wanted" velocity for an agent as a suggestion + // This velocity is not guaranteed, RVO simulation will only try to fulfill it + velocity = p_velocity; + if (avoidance_enabled) { + if (use_3d_avoidance) { + rvo_agent_3d.prefVelocity_ = RVO3D::Vector3(velocity.x, velocity.y, velocity.z); + } else { + rvo_agent_2d.prefVelocity_ = RVO2D::Vector2(velocity.x, velocity.z); + } + } + agent_dirty = true; +} + +void NavAgent::set_velocity_forced(const Vector3 p_velocity) { + // This function replaces the internal rvo simulation velocity + // should only be used after the agent was teleported + // as it destroys consistency in movement in cramped situations + // use velocity instead to update with a safer "suggestion" + velocity_forced = p_velocity; + if (avoidance_enabled) { + if (use_3d_avoidance) { + rvo_agent_3d.velocity_ = RVO3D::Vector3(p_velocity.x, p_velocity.y, p_velocity.z); + } else { + rvo_agent_2d.velocity_ = RVO2D::Vector2(p_velocity.x, p_velocity.z); + } + } + agent_dirty = true; +} + +void NavAgent::update() { + // Updates this agent with the calculated results from the rvo simulation + if (avoidance_enabled) { + if (use_3d_avoidance) { + velocity = Vector3(rvo_agent_3d.velocity_.x(), rvo_agent_3d.velocity_.y(), rvo_agent_3d.velocity_.z()); + } else { + velocity = Vector3(rvo_agent_2d.velocity_.x(), 0.0, rvo_agent_2d.velocity_.y()); + } + } +} + +void NavAgent::set_avoidance_mask(uint32_t p_mask) { + avoidance_mask = p_mask; + if (use_3d_avoidance) { + rvo_agent_3d.avoidance_mask_ = avoidance_mask; + } else { + rvo_agent_2d.avoidance_mask_ = avoidance_mask; + } + agent_dirty = true; +} + +void NavAgent::set_avoidance_layers(uint32_t p_layers) { + avoidance_layers = p_layers; + if (use_3d_avoidance) { + rvo_agent_3d.avoidance_layers_ = avoidance_layers; + } else { + rvo_agent_2d.avoidance_layers_ = avoidance_layers; + } + agent_dirty = true; +} + +void NavAgent::set_avoidance_priority(real_t p_priority) { + ERR_FAIL_COND_MSG(p_priority < 0.0, "Avoidance priority must be between 0.0 and 1.0 inclusive."); + ERR_FAIL_COND_MSG(p_priority > 1.0, "Avoidance priority must be between 0.0 and 1.0 inclusive."); + avoidance_priority = p_priority; + if (use_3d_avoidance) { + rvo_agent_3d.avoidance_priority_ = avoidance_priority; + } else { + rvo_agent_2d.avoidance_priority_ = avoidance_priority; + } + agent_dirty = true; +}; + +bool NavAgent::check_dirty() { + const bool was_dirty = agent_dirty; + agent_dirty = false; + return was_dirty; +} + +const Dictionary NavAgent::get_avoidance_data() const { + // Returns debug data from RVO simulation internals of this agent. + Dictionary _avoidance_data; + if (use_3d_avoidance) { + _avoidance_data["max_neighbors"] = int(rvo_agent_3d.maxNeighbors_); + _avoidance_data["max_speed"] = float(rvo_agent_3d.maxSpeed_); + _avoidance_data["neighbor_distance"] = float(rvo_agent_3d.neighborDist_); + _avoidance_data["new_velocity"] = Vector3(rvo_agent_3d.newVelocity_.x(), rvo_agent_3d.newVelocity_.y(), rvo_agent_3d.newVelocity_.z()); + _avoidance_data["velocity"] = Vector3(rvo_agent_3d.velocity_.x(), rvo_agent_3d.velocity_.y(), rvo_agent_3d.velocity_.z()); + _avoidance_data["position"] = Vector3(rvo_agent_3d.position_.x(), rvo_agent_3d.position_.y(), rvo_agent_3d.position_.z()); + _avoidance_data["prefered_velocity"] = Vector3(rvo_agent_3d.prefVelocity_.x(), rvo_agent_3d.prefVelocity_.y(), rvo_agent_3d.prefVelocity_.z()); + _avoidance_data["radius"] = float(rvo_agent_3d.radius_); + _avoidance_data["time_horizon_agents"] = float(rvo_agent_3d.timeHorizon_); + _avoidance_data["time_horizon_obstacles"] = 0.0; + _avoidance_data["height"] = float(rvo_agent_3d.height_); + _avoidance_data["avoidance_layers"] = int(rvo_agent_3d.avoidance_layers_); + _avoidance_data["avoidance_mask"] = int(rvo_agent_3d.avoidance_mask_); + _avoidance_data["avoidance_priority"] = float(rvo_agent_3d.avoidance_priority_); + } else { + _avoidance_data["max_neighbors"] = int(rvo_agent_2d.maxNeighbors_); + _avoidance_data["max_speed"] = float(rvo_agent_2d.maxSpeed_); + _avoidance_data["neighbor_distance"] = float(rvo_agent_2d.neighborDist_); + _avoidance_data["new_velocity"] = Vector3(rvo_agent_2d.newVelocity_.x(), 0.0, rvo_agent_2d.newVelocity_.y()); + _avoidance_data["velocity"] = Vector3(rvo_agent_2d.velocity_.x(), 0.0, rvo_agent_2d.velocity_.y()); + _avoidance_data["position"] = Vector3(rvo_agent_2d.position_.x(), 0.0, rvo_agent_2d.position_.y()); + _avoidance_data["prefered_velocity"] = Vector3(rvo_agent_2d.prefVelocity_.x(), 0.0, rvo_agent_2d.prefVelocity_.y()); + _avoidance_data["radius"] = float(rvo_agent_2d.radius_); + _avoidance_data["time_horizon_agents"] = float(rvo_agent_2d.timeHorizon_); + _avoidance_data["time_horizon_obstacles"] = float(rvo_agent_2d.timeHorizonObst_); + _avoidance_data["height"] = float(rvo_agent_2d.height_); + _avoidance_data["avoidance_layers"] = int(rvo_agent_2d.avoidance_layers_); + _avoidance_data["avoidance_mask"] = int(rvo_agent_2d.avoidance_mask_); + _avoidance_data["avoidance_priority"] = float(rvo_agent_2d.avoidance_priority_); + } + return _avoidance_data; } diff --git a/modules/navigation/nav_agent.h b/modules/navigation/nav_agent.h index f154ce14d9..497b239f84 100644 --- a/modules/navigation/nav_agent.h +++ b/modules/navigation/nav_agent.h @@ -32,34 +32,121 @@ #define NAV_AGENT_H #include "core/object/class_db.h" +#include "core/templates/local_vector.h" +#include "nav_agent.h" #include "nav_rid.h" -#include <Agent.h> +#include <Agent2d.h> +#include <Agent3d.h> class NavMap; class NavAgent : public NavRid { + Vector3 position; + Vector3 target_position; + Vector3 velocity; + Vector3 velocity_forced; + real_t height = 1.0; + real_t radius = 1.0; + real_t max_speed = 1.0; + real_t time_horizon_agents = 1.0; + real_t time_horizon_obstacles = 0.0; + int max_neighbors = 5; + real_t neighbor_distance = 5.0; + Vector3 safe_velocity; + bool clamp_speed = true; // Experimental, clamps velocity to max_speed. + NavMap *map = nullptr; - RVO::Agent agent; - Callable callback = Callable(); + + RVO2D::Agent2D rvo_agent_2d; + RVO3D::Agent3D rvo_agent_3d; + bool use_3d_avoidance = false; + bool avoidance_enabled = false; + + uint32_t avoidance_layers = 1; + uint32_t avoidance_mask = 1; + real_t avoidance_priority = 1.0; + + Callable avoidance_callback = Callable(); + + bool agent_dirty = true; + uint32_t map_update_id = 0; public: - void set_map(NavMap *p_map); - NavMap *get_map() { - return map; - } + NavAgent(); + + void set_avoidance_enabled(bool p_enabled); + bool is_avoidance_enabled() { return avoidance_enabled; } - RVO::Agent *get_agent() { - return &agent; - } + void set_use_3d_avoidance(bool p_enabled); + bool get_use_3d_avoidance() { return use_3d_avoidance; } + + void set_map(NavMap *p_map); + NavMap *get_map() { return map; } bool is_map_changed(); - void set_callback(Callable p_callback); - bool has_callback() const; + RVO2D::Agent2D *get_rvo_agent_2d() { return &rvo_agent_2d; } + RVO3D::Agent3D *get_rvo_agent_3d() { return &rvo_agent_3d; } + + void set_avoidance_callback(Callable p_callback); + bool has_avoidance_callback() const; + + void dispatch_avoidance_callback(); + + void set_neighbor_distance(real_t p_neighbor_distance); + real_t get_neighbor_distance() const { return neighbor_distance; } + + void set_max_neighbors(int p_max_neighbors); + int get_max_neighbors() const { return max_neighbors; } + + void set_time_horizon_agents(real_t p_time_horizon); + real_t get_time_horizon_agents() const { return time_horizon_agents; } + + void set_time_horizon_obstacles(real_t p_time_horizon); + real_t get_time_horizon_obstacles() const { return time_horizon_obstacles; } + + void set_radius(real_t p_radius); + real_t get_radius() const { return radius; } + + void set_height(real_t p_height); + real_t get_height() const { return height; } + + void set_max_speed(real_t p_max_speed); + real_t get_max_speed() const { return max_speed; } + + void set_position(const Vector3 p_position); + const Vector3 &get_position() const { return position; } + + void set_target_position(const Vector3 p_target_position); + const Vector3 &get_target_position() const { return target_position; } + + void set_velocity(const Vector3 p_velocity); + const Vector3 &get_velocity() const { return velocity; } + + void set_velocity_forced(const Vector3 p_velocity); + const Vector3 &get_velocity_forced() const { return velocity_forced; } + + void set_avoidance_layers(uint32_t p_layers); + uint32_t get_avoidance_layers() const { return avoidance_layers; }; + + void set_avoidance_mask(uint32_t p_mask); + uint32_t get_avoidance_mask() const { return avoidance_mask; }; + + void set_avoidance_priority(real_t p_priority); + real_t get_avoidance_priority() const { return avoidance_priority; }; + + bool check_dirty(); + + // Updates this agent with rvo data after the rvo simulation avoidance step. + void update(); + + // RVO debug data from the last frame update. + const Dictionary get_avoidance_data() const; - void dispatch_callback(); +private: + void _update_rvo_agent_properties(); }; #endif // NAV_AGENT_H diff --git a/modules/navigation/nav_base.h b/modules/navigation/nav_base.h index d4354f929d..b5cdc117f2 100644 --- a/modules/navigation/nav_base.h +++ b/modules/navigation/nav_base.h @@ -48,6 +48,9 @@ protected: public: NavigationUtilities::PathSegmentType get_type() const { return type; } + virtual void set_use_edge_connections(bool p_enabled) {} + virtual bool get_use_edge_connections() const { return false; } + void set_navigation_layers(uint32_t p_navigation_layers) { navigation_layers = p_navigation_layers; } uint32_t get_navigation_layers() const { return navigation_layers; } @@ -59,6 +62,8 @@ public: void set_owner_id(ObjectID p_owner_id) { owner_id = p_owner_id; } ObjectID get_owner_id() const { return owner_id; } + + virtual ~NavBase(){}; }; #endif // NAV_BASE_H diff --git a/modules/navigation/nav_link.cpp b/modules/navigation/nav_link.cpp index ad87cc0b05..5607a3253e 100644 --- a/modules/navigation/nav_link.cpp +++ b/modules/navigation/nav_link.cpp @@ -33,21 +33,33 @@ #include "nav_map.h" void NavLink::set_map(NavMap *p_map) { + if (map == p_map) { + return; + } map = p_map; link_dirty = true; } void NavLink::set_bidirectional(bool p_bidirectional) { + if (bidirectional == p_bidirectional) { + return; + } bidirectional = p_bidirectional; link_dirty = true; } void NavLink::set_start_position(const Vector3 p_position) { + if (start_position == p_position) { + return; + } start_position = p_position; link_dirty = true; } void NavLink::set_end_position(const Vector3 p_position) { + if (end_position == p_position) { + return; + } end_position = p_position; link_dirty = true; } diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index 91b13ba9c4..37e0f232e5 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -30,11 +30,14 @@ #include "nav_map.h" +#include "core/config/project_settings.h" #include "core/object/worker_thread_pool.h" #include "nav_agent.h" #include "nav_link.h" +#include "nav_obstacle.h" #include "nav_region.h" -#include <algorithm> + +#include <Obstacle2d.h> #define THREE_POINTS_CROSS_PRODUCT(m_a, m_b, m_c) (((m_c) - (m_a)).cross((m_b) - (m_a))) @@ -51,21 +54,41 @@ } void NavMap::set_up(Vector3 p_up) { + if (up == p_up) { + return; + } up = p_up; regenerate_polygons = true; } void NavMap::set_cell_size(real_t p_cell_size) { + if (cell_size == p_cell_size) { + return; + } cell_size = p_cell_size; regenerate_polygons = true; } +void NavMap::set_use_edge_connections(bool p_enabled) { + if (use_edge_connections == p_enabled) { + return; + } + use_edge_connections = p_enabled; + regenerate_links = true; +} + void NavMap::set_edge_connection_margin(real_t p_edge_connection_margin) { + if (edge_connection_margin == p_edge_connection_margin) { + return; + } edge_connection_margin = p_edge_connection_margin; regenerate_links = true; } void NavMap::set_link_connection_radius(real_t p_link_connection_radius) { + if (link_connection_radius == p_link_connection_radius) { + return; + } link_connection_radius = p_link_connection_radius; regenerate_links = true; } @@ -522,9 +545,7 @@ gd::ClosestPointQueryResult NavMap::get_closest_point_info(const Vector3 &p_poin gd::ClosestPointQueryResult result; real_t closest_point_ds = FLT_MAX; - for (size_t i(0); i < polygons.size(); i++) { - const gd::Polygon &p = polygons[i]; - + for (const gd::Polygon &p : polygons) { // For each face check the distance to the point for (size_t point_id = 2; point_id < p.points.size(); point_id += 1) { const Face3 f(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos); @@ -549,7 +570,7 @@ void NavMap::add_region(NavRegion *p_region) { void NavMap::remove_region(NavRegion *p_region) { int64_t region_index = regions.find(p_region); - if (region_index != -1) { + if (region_index >= 0) { regions.remove_at_unordered(region_index); regenerate_links = true; } @@ -562,14 +583,14 @@ void NavMap::add_link(NavLink *p_link) { void NavMap::remove_link(NavLink *p_link) { int64_t link_index = links.find(p_link); - if (link_index != -1) { + if (link_index >= 0) { links.remove_at_unordered(link_index); regenerate_links = true; } } bool NavMap::has_agent(NavAgent *agent) const { - return (agents.find(agent) != -1); + return (agents.find(agent) >= 0); } void NavMap::add_agent(NavAgent *agent) { @@ -582,25 +603,57 @@ void NavMap::add_agent(NavAgent *agent) { void NavMap::remove_agent(NavAgent *agent) { remove_agent_as_controlled(agent); int64_t agent_index = agents.find(agent); - if (agent_index != -1) { + if (agent_index >= 0) { agents.remove_at_unordered(agent_index); agents_dirty = true; } } +bool NavMap::has_obstacle(NavObstacle *obstacle) const { + return (obstacles.find(obstacle) >= 0); +} + +void NavMap::add_obstacle(NavObstacle *obstacle) { + if (!has_obstacle(obstacle)) { + obstacles.push_back(obstacle); + obstacles_dirty = true; + } +} + +void NavMap::remove_obstacle(NavObstacle *obstacle) { + int64_t obstacle_index = obstacles.find(obstacle); + if (obstacle_index >= 0) { + obstacles.remove_at_unordered(obstacle_index); + obstacles_dirty = true; + } +} + void NavMap::set_agent_as_controlled(NavAgent *agent) { - const bool exist = (controlled_agents.find(agent) != -1); - if (!exist) { - ERR_FAIL_COND(!has_agent(agent)); - controlled_agents.push_back(agent); - agents_dirty = true; + remove_agent_as_controlled(agent); + if (agent->get_use_3d_avoidance()) { + int64_t agent_3d_index = active_3d_avoidance_agents.find(agent); + if (agent_3d_index < 0) { + active_3d_avoidance_agents.push_back(agent); + agents_dirty = true; + } + } else { + int64_t agent_2d_index = active_2d_avoidance_agents.find(agent); + if (agent_2d_index < 0) { + active_2d_avoidance_agents.push_back(agent); + agents_dirty = true; + } } } void NavMap::remove_agent_as_controlled(NavAgent *agent) { - int64_t active_avoidance_agent_index = controlled_agents.find(agent); - if (active_avoidance_agent_index != -1) { - controlled_agents.remove_at_unordered(active_avoidance_agent_index); + int64_t agent_3d_index = active_3d_avoidance_agents.find(agent); + if (agent_3d_index >= 0) { + active_3d_avoidance_agents.remove_at_unordered(agent_3d_index); + agents_dirty = true; + } + int64_t agent_2d_index = active_2d_avoidance_agents.find(agent); + if (agent_2d_index >= 0) { + active_2d_avoidance_agents.remove_at_unordered(agent_2d_index); agents_dirty = true; } } @@ -706,7 +759,9 @@ void NavMap::sync() { _new_pm_edge_merge_count += 1; } else { CRASH_COND_MSG(E.value.size() != 1, vformat("Number of connection != 1. Found: %d", E.value.size())); - free_edges.push_back(E.value[0]); + if (use_edge_connections && E.value[0].polygon->owner->get_use_edge_connections()) { + free_edges.push_back(E.value[0]); + } } } @@ -891,22 +946,30 @@ void NavMap::sync() { map_update_id = (map_update_id + 1) % 9999999; } - // Update agents tree. - if (agents_dirty) { - // cannot use LocalVector here as RVO library expects std::vector to build KdTree - std::vector<RVO::Agent *> raw_agents; - raw_agents.reserve(controlled_agents.size()); - for (NavAgent *controlled_agent : controlled_agents) { - raw_agents.push_back(controlled_agent->get_agent()); + // Do we have modified obstacle positions? + for (NavObstacle *obstacle : obstacles) { + if (obstacle->check_dirty()) { + obstacles_dirty = true; } - rvo.buildAgentTree(raw_agents); + } + // Do we have modified agent arrays? + for (NavAgent *agent : agents) { + if (agent->check_dirty()) { + agents_dirty = true; + } + } + + // Update avoidance worlds. + if (obstacles_dirty || agents_dirty) { + _update_rvo_simulation(); } regenerate_polygons = false; regenerate_links = false; + obstacles_dirty = false; agents_dirty = false; - // Performance Monitor + // Performance Monitor. pm_region_count = _new_pm_region_count; pm_agent_count = _new_pm_agent_count; pm_link_count = _new_pm_link_count; @@ -917,22 +980,155 @@ void NavMap::sync() { pm_edge_free_count = _new_pm_edge_free_count; } -void NavMap::compute_single_step(uint32_t index, NavAgent **agent) { - (*(agent + index))->get_agent()->computeNeighbors(&rvo); - (*(agent + index))->get_agent()->computeNewVelocity(deltatime); +void NavMap::_update_rvo_obstacles_tree_2d() { + int obstacle_vertex_count = 0; + for (NavObstacle *obstacle : obstacles) { + obstacle_vertex_count += obstacle->get_vertices().size(); + } + + // Cannot use LocalVector here as RVO library expects std::vector to build KdTree + std::vector<RVO2D::Obstacle2D *> raw_obstacles; + raw_obstacles.reserve(obstacle_vertex_count); + + // The following block is modified copy from RVO2D::AddObstacle() + // Obstacles are linked and depend on all other obstacles. + for (NavObstacle *obstacle : obstacles) { + const Vector3 &_obstacle_position = obstacle->get_position(); + const Vector<Vector3> &_obstacle_vertices = obstacle->get_vertices(); + + if (_obstacle_vertices.size() < 2) { + continue; + } + + std::vector<RVO2D::Vector2> rvo_2d_vertices; + rvo_2d_vertices.reserve(_obstacle_vertices.size()); + + uint32_t _obstacle_avoidance_layers = obstacle->get_avoidance_layers(); + + for (const Vector3 &_obstacle_vertex : _obstacle_vertices) { + rvo_2d_vertices.push_back(RVO2D::Vector2(_obstacle_vertex.x + _obstacle_position.x, _obstacle_vertex.z + _obstacle_position.z)); + } + + const size_t obstacleNo = raw_obstacles.size(); + + for (size_t i = 0; i < rvo_2d_vertices.size(); i++) { + RVO2D::Obstacle2D *rvo_2d_obstacle = new RVO2D::Obstacle2D(); + rvo_2d_obstacle->point_ = rvo_2d_vertices[i]; + rvo_2d_obstacle->avoidance_layers_ = _obstacle_avoidance_layers; + + if (i != 0) { + rvo_2d_obstacle->prevObstacle_ = raw_obstacles.back(); + rvo_2d_obstacle->prevObstacle_->nextObstacle_ = rvo_2d_obstacle; + } + + if (i == rvo_2d_vertices.size() - 1) { + rvo_2d_obstacle->nextObstacle_ = raw_obstacles[obstacleNo]; + rvo_2d_obstacle->nextObstacle_->prevObstacle_ = rvo_2d_obstacle; + } + + rvo_2d_obstacle->unitDir_ = normalize(rvo_2d_vertices[(i == rvo_2d_vertices.size() - 1 ? 0 : i + 1)] - rvo_2d_vertices[i]); + + if (rvo_2d_vertices.size() == 2) { + rvo_2d_obstacle->isConvex_ = true; + } else { + rvo_2d_obstacle->isConvex_ = (leftOf(rvo_2d_vertices[(i == 0 ? rvo_2d_vertices.size() - 1 : i - 1)], rvo_2d_vertices[i], rvo_2d_vertices[(i == rvo_2d_vertices.size() - 1 ? 0 : i + 1)]) >= 0.0f); + } + + rvo_2d_obstacle->id_ = raw_obstacles.size(); + + raw_obstacles.push_back(rvo_2d_obstacle); + } + } + + rvo_simulation_2d.kdTree_->buildObstacleTree(raw_obstacles); +} + +void NavMap::_update_rvo_agents_tree_2d() { + // Cannot use LocalVector here as RVO library expects std::vector to build KdTree. + std::vector<RVO2D::Agent2D *> raw_agents; + raw_agents.reserve(active_2d_avoidance_agents.size()); + for (NavAgent *agent : active_2d_avoidance_agents) { + raw_agents.push_back(agent->get_rvo_agent_2d()); + } + rvo_simulation_2d.kdTree_->buildAgentTree(raw_agents); +} + +void NavMap::_update_rvo_agents_tree_3d() { + // Cannot use LocalVector here as RVO library expects std::vector to build KdTree. + std::vector<RVO3D::Agent3D *> raw_agents; + raw_agents.reserve(active_3d_avoidance_agents.size()); + for (NavAgent *agent : active_3d_avoidance_agents) { + raw_agents.push_back(agent->get_rvo_agent_3d()); + } + rvo_simulation_3d.kdTree_->buildAgentTree(raw_agents); +} + +void NavMap::_update_rvo_simulation() { + if (obstacles_dirty) { + _update_rvo_obstacles_tree_2d(); + } + if (agents_dirty) { + _update_rvo_agents_tree_2d(); + _update_rvo_agents_tree_3d(); + } +} + +void NavMap::compute_single_avoidance_step_2d(uint32_t index, NavAgent **agent) { + (*(agent + index))->get_rvo_agent_2d()->computeNeighbors(&rvo_simulation_2d); + (*(agent + index))->get_rvo_agent_2d()->computeNewVelocity(&rvo_simulation_2d); + (*(agent + index))->get_rvo_agent_2d()->update(&rvo_simulation_2d); + (*(agent + index))->update(); +} + +void NavMap::compute_single_avoidance_step_3d(uint32_t index, NavAgent **agent) { + (*(agent + index))->get_rvo_agent_3d()->computeNeighbors(&rvo_simulation_3d); + (*(agent + index))->get_rvo_agent_3d()->computeNewVelocity(&rvo_simulation_3d); + (*(agent + index))->get_rvo_agent_3d()->update(&rvo_simulation_3d); + (*(agent + index))->update(); } void NavMap::step(real_t p_deltatime) { deltatime = p_deltatime; - if (controlled_agents.size() > 0) { - WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &NavMap::compute_single_step, controlled_agents.ptr(), controlled_agents.size(), -1, true, SNAME("NavigationMapAgents")); - WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); + + rvo_simulation_2d.setTimeStep(float(deltatime)); + rvo_simulation_3d.setTimeStep(float(deltatime)); + + if (active_2d_avoidance_agents.size() > 0) { + if (use_threads && avoidance_use_multiple_threads) { + WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &NavMap::compute_single_avoidance_step_2d, active_2d_avoidance_agents.ptr(), active_2d_avoidance_agents.size(), -1, true, SNAME("RVOAvoidanceAgents2D")); + WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); + } else { + for (NavAgent *agent : active_2d_avoidance_agents) { + agent->get_rvo_agent_2d()->computeNeighbors(&rvo_simulation_2d); + agent->get_rvo_agent_2d()->computeNewVelocity(&rvo_simulation_2d); + agent->get_rvo_agent_2d()->update(&rvo_simulation_2d); + agent->update(); + } + } + } + + if (active_3d_avoidance_agents.size() > 0) { + if (use_threads && avoidance_use_multiple_threads) { + WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &NavMap::compute_single_avoidance_step_3d, active_3d_avoidance_agents.ptr(), active_3d_avoidance_agents.size(), -1, true, SNAME("RVOAvoidanceAgents3D")); + WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); + } else { + for (NavAgent *agent : active_3d_avoidance_agents) { + agent->get_rvo_agent_3d()->computeNeighbors(&rvo_simulation_3d); + agent->get_rvo_agent_3d()->computeNewVelocity(&rvo_simulation_3d); + agent->get_rvo_agent_3d()->update(&rvo_simulation_3d); + agent->update(); + } + } } } void NavMap::dispatch_callbacks() { - for (NavAgent *agent : controlled_agents) { - agent->dispatch_callback(); + for (NavAgent *agent : active_2d_avoidance_agents) { + agent->dispatch_avoidance_callback(); + } + + for (NavAgent *agent : active_3d_avoidance_agents) { + agent->dispatch_avoidance_callback(); } } @@ -970,6 +1166,8 @@ void NavMap::clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys } NavMap::NavMap() { + avoidance_use_multiple_threads = GLOBAL_GET("navigation/avoidance/thread_model/avoidance_use_multiple_threads"); + avoidance_use_high_priority_threads = GLOBAL_GET("navigation/avoidance/thread_model/avoidance_use_high_priority_threads"); } NavMap::~NavMap() { diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h index 5ec2c2826c..2bd9459626 100644 --- a/modules/navigation/nav_map.h +++ b/modules/navigation/nav_map.h @@ -38,11 +38,16 @@ #include "core/templates/rb_map.h" #include "nav_utils.h" -#include <KdTree.h> +#include <KdTree2d.h> +#include <RVOSimulator2d.h> + +#include <KdTree3d.h> +#include <RVOSimulator3d.h> class NavLink; class NavRegion; class NavAgent; +class NavObstacle; class NavMap : public NavRid { /// Map Up @@ -52,6 +57,7 @@ class NavMap : public NavRid { /// each cell has the following cell_size. real_t cell_size = 0.25; + bool use_edge_connections = true; /// This value is used to detect the near edges to connect. real_t edge_connection_margin = 0.25; @@ -71,17 +77,25 @@ class NavMap : public NavRid { /// Map polygons LocalVector<gd::Polygon> polygons; - /// Rvo world - RVO::KdTree rvo; + /// RVO avoidance worlds + RVO2D::RVOSimulator2D rvo_simulation_2d; + RVO3D::RVOSimulator3D rvo_simulation_3d; + + /// avoidance controlled agents + LocalVector<NavAgent *> active_2d_avoidance_agents; + LocalVector<NavAgent *> active_3d_avoidance_agents; - /// Is agent array modified? - bool agents_dirty = false; + /// dirty flag when one of the agent's arrays are modified + bool agents_dirty = true; /// All the Agents (even the controlled one) LocalVector<NavAgent *> agents; - /// Controlled agents - LocalVector<NavAgent *> controlled_agents; + /// All the avoidance obstacles (both static and dynamic) + LocalVector<NavObstacle *> obstacles; + + /// Are rvo obstacles modified? + bool obstacles_dirty = true; /// Physics delta time real_t deltatime = 0.0; @@ -89,6 +103,10 @@ class NavMap : public NavRid { /// Change the id each time the map is updated. uint32_t map_update_id = 0; + bool use_threads = true; + bool avoidance_use_multiple_threads = true; + bool avoidance_use_high_priority_threads = true; + // Performance Monitor int pm_region_count = 0; int pm_agent_count = 0; @@ -113,6 +131,11 @@ public: return cell_size; } + void set_use_edge_connections(bool p_enabled); + bool get_use_edge_connections() const { + return use_edge_connections; + } + void set_edge_connection_margin(real_t p_edge_connection_margin); real_t get_edge_connection_margin() const { return edge_connection_margin; @@ -154,6 +177,13 @@ public: void set_agent_as_controlled(NavAgent *agent); void remove_agent_as_controlled(NavAgent *agent); + bool has_obstacle(NavObstacle *obstacle) const; + void add_obstacle(NavObstacle *obstacle); + void remove_obstacle(NavObstacle *obstacle); + const LocalVector<NavObstacle *> &get_obstacles() const { + return obstacles; + } + uint32_t get_map_update_id() const { return map_update_id; } @@ -174,7 +204,15 @@ public: private: void compute_single_step(uint32_t index, NavAgent **agent); + + void compute_single_avoidance_step_2d(uint32_t index, NavAgent **agent); + void compute_single_avoidance_step_3d(uint32_t index, NavAgent **agent); + void clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners) const; + void _update_rvo_simulation(); + void _update_rvo_obstacles_tree_2d(); + void _update_rvo_agents_tree_2d(); + void _update_rvo_agents_tree_3d(); }; #endif // NAV_MAP_H diff --git a/modules/navigation/nav_obstacle.cpp b/modules/navigation/nav_obstacle.cpp new file mode 100644 index 0000000000..5d0bc59cbb --- /dev/null +++ b/modules/navigation/nav_obstacle.cpp @@ -0,0 +1,86 @@ +/**************************************************************************/ +/* nav_obstacle.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 "nav_obstacle.h" + +#include "nav_map.h" + +NavObstacle::NavObstacle() {} + +NavObstacle::~NavObstacle() {} + +void NavObstacle::set_map(NavMap *p_map) { + if (map != p_map) { + map = p_map; + obstacle_dirty = true; + } +} + +void NavObstacle::set_position(const Vector3 p_position) { + if (position != p_position) { + position = p_position; + obstacle_dirty = true; + } +} + +void NavObstacle::set_height(const real_t p_height) { + if (height != p_height) { + height = p_height; + obstacle_dirty = true; + } +} + +void NavObstacle::set_vertices(const Vector<Vector3> &p_vertices) { + if (vertices != p_vertices) { + vertices = p_vertices; + obstacle_dirty = true; + } +} + +bool NavObstacle::is_map_changed() { + if (map) { + bool is_changed = map->get_map_update_id() != map_update_id; + map_update_id = map->get_map_update_id(); + return is_changed; + } else { + return false; + } +} + +void NavObstacle::set_avoidance_layers(uint32_t p_layers) { + avoidance_layers = p_layers; + obstacle_dirty = true; +} + +bool NavObstacle::check_dirty() { + const bool was_dirty = obstacle_dirty; + obstacle_dirty = false; + return was_dirty; +} diff --git a/modules/navigation/nav_obstacle.h b/modules/navigation/nav_obstacle.h new file mode 100644 index 0000000000..f59eba5200 --- /dev/null +++ b/modules/navigation/nav_obstacle.h @@ -0,0 +1,78 @@ +/**************************************************************************/ +/* nav_obstacle.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 NAV_OBSTACLE_H +#define NAV_OBSTACLE_H + +#include "core/object/class_db.h" +#include "core/templates/local_vector.h" +#include "nav_agent.h" +#include "nav_rid.h" + +class NavMap; + +class NavObstacle : public NavRid { + NavMap *map = nullptr; + Vector3 position; + Vector<Vector3> vertices; + + real_t height = 0.0; + + uint32_t avoidance_layers = 1; + + bool obstacle_dirty = true; + + uint32_t map_update_id = 0; + +public: + NavObstacle(); + ~NavObstacle(); + + void set_map(NavMap *p_map); + NavMap *get_map() { return map; } + + void set_position(const Vector3 p_position); + const Vector3 &get_position() const { return position; } + + void set_height(const real_t p_height); + real_t get_height() const { return height; } + + void set_vertices(const Vector<Vector3> &p_vertices); + const Vector<Vector3> &get_vertices() const { return vertices; } + + bool is_map_changed(); + + void set_avoidance_layers(uint32_t p_layers); + uint32_t get_avoidance_layers() const { return avoidance_layers; }; + + bool check_dirty(); +}; + +#endif // NAV_OBSTACLE_H diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp index cad4678e5a..d0f055874e 100644 --- a/modules/navigation/nav_region.cpp +++ b/modules/navigation/nav_region.cpp @@ -33,6 +33,9 @@ #include "nav_map.h" void NavRegion::set_map(NavMap *p_map) { + if (map == p_map) { + return; + } map = p_map; polygons_dirty = true; if (!map) { @@ -40,7 +43,17 @@ void NavRegion::set_map(NavMap *p_map) { } } +void NavRegion::set_use_edge_connections(bool p_enabled) { + if (use_edge_connections != p_enabled) { + use_edge_connections = p_enabled; + polygons_dirty = true; + } +} + void NavRegion::set_transform(Transform3D p_transform) { + if (transform == p_transform) { + return; + } transform = p_transform; polygons_dirty = true; } diff --git a/modules/navigation/nav_region.h b/modules/navigation/nav_region.h index 0942aa22f0..72299e8874 100644 --- a/modules/navigation/nav_region.h +++ b/modules/navigation/nav_region.h @@ -42,6 +42,8 @@ class NavRegion : public NavBase { Ref<NavigationMesh> mesh; Vector<gd::Edge::Connection> connections; + bool use_edge_connections = true; + bool polygons_dirty = true; /// Cache @@ -61,6 +63,11 @@ public: return map; } + void set_use_edge_connections(bool p_enabled); + bool get_use_edge_connections() const { + return use_edge_connections; + } + void set_transform(Transform3D transform); const Transform3D &get_transform() const { return transform; diff --git a/modules/noise/doc_classes/Noise.xml b/modules/noise/doc_classes/Noise.xml index a5cdb5bcbc..c075b5b629 100644 --- a/modules/noise/doc_classes/Noise.xml +++ b/modules/noise/doc_classes/Noise.xml @@ -15,13 +15,24 @@ <return type="Image" /> <param index="0" name="width" type="int" /> <param index="1" name="height" type="int" /> + <param index="2" name="invert" type="bool" default="false" /> + <param index="3" name="in_3d_space" type="bool" default="false" /> + <param index="4" name="normalize" type="bool" default="true" /> + <description> + Returns an [Image] containing 2D noise values. + [b]Note:[/b] With [param normalize] set to [code]false[/code], the default implementation expects the noise generator to return values in the range [code]-1.0[/code] to [code]1.0[/code]. + </description> + </method> + <method name="get_image_3d" qualifiers="const"> + <return type="Image[]" /> + <param index="0" name="width" type="int" /> + <param index="1" name="height" type="int" /> <param index="2" name="depth" type="int" /> <param index="3" name="invert" type="bool" default="false" /> - <param index="4" name="in_3d_space" type="bool" default="false" /> - <param index="5" name="normalize" type="bool" default="true" /> + <param index="4" name="normalize" type="bool" default="true" /> <description> - Returns a 2D [Image] noise image. - Note: With [param normalize] set to [code]false[/code] the default implementation expects the noise generator to return values in the range [code]-1.0[/code] to [code]1.0[/code]. + Returns an [Array] of [Image]s containing 3D noise values for use with [method ImageTexture3D.create]. + [b]Note:[/b] With [param normalize] set to [code]false[/code], the default implementation expects the noise generator to return values in the range [code]-1.0[/code] to [code]1.0[/code]. </description> </method> <method name="get_noise_1d" qualifiers="const"> @@ -66,14 +77,26 @@ <return type="Image" /> <param index="0" name="width" type="int" /> <param index="1" name="height" type="int" /> + <param index="2" name="invert" type="bool" default="false" /> + <param index="3" name="in_3d_space" type="bool" default="false" /> + <param index="4" name="skirt" type="float" default="0.1" /> + <param index="5" name="normalize" type="bool" default="true" /> + <description> + Returns an [Image] containing seamless 2D noise values. + [b]Note:[/b] With [param normalize] set to [code]false[/code], the default implementation expects the noise generator to return values in the range [code]-1.0[/code] to [code]1.0[/code]. + </description> + </method> + <method name="get_seamless_image_3d" qualifiers="const"> + <return type="Image[]" /> + <param index="0" name="width" type="int" /> + <param index="1" name="height" type="int" /> <param index="2" name="depth" type="int" /> <param index="3" name="invert" type="bool" default="false" /> - <param index="4" name="in_3d_space" type="bool" default="false" /> - <param index="5" name="skirt" type="float" default="0.1" /> - <param index="6" name="normalize" type="bool" default="true" /> + <param index="4" name="skirt" type="float" default="0.1" /> + <param index="5" name="normalize" type="bool" default="true" /> <description> - Returns a seamless 2D [Image] noise image. - Note: With [param normalize] set to [code]false[/code] the default implementation expects the noise generator to return values in the range [code]-1.0[/code] to [code]1.0[/code]. + Returns an [Array] of [Image]s containing seamless 3D noise values for use with [method ImageTexture3D.create]. + [b]Note:[/b] With [param normalize] set to [code]false[/code], the default implementation expects the noise generator to return values in the range [code]-1.0[/code] to [code]1.0[/code]. </description> </method> </methods> diff --git a/modules/noise/doc_classes/NoiseTexture3D.xml b/modules/noise/doc_classes/NoiseTexture3D.xml index 0b385d9b9c..7394e7ff08 100644 --- a/modules/noise/doc_classes/NoiseTexture3D.xml +++ b/modules/noise/doc_classes/NoiseTexture3D.xml @@ -5,13 +5,12 @@ </brief_description> <description> Uses [FastNoiseLite] or other libraries to fill the texture data of your desired size. - The class uses [Thread]s to generate the texture data internally, so [method Texture3D.get_data] may return [code]null[/code] if the generation process has not completed yet. In that case, you need to wait for the texture to be generated before accessing the image and the generated byte data: + The class uses [Thread]s to generate the texture data internally, so [method Texture3D.get_data] may return [code]null[/code] if the generation process has not completed yet. In that case, you need to wait for the texture to be generated before accessing the image: [codeblock] var texture = NoiseTexture3D.new() texture.noise = FastNoiseLite.new() await texture.changed var data = texture.get_data() - var image = data[0] [/codeblock] </description> <tutorials> diff --git a/modules/noise/fastnoise_lite.cpp b/modules/noise/fastnoise_lite.cpp index 224c082c0f..4aea98c4de 100644 --- a/modules/noise/fastnoise_lite.cpp +++ b/modules/noise/fastnoise_lite.cpp @@ -416,7 +416,7 @@ void FastNoiseLite::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "noise_type", PROPERTY_HINT_ENUM, "Simplex,Simplex Smooth,Cellular,Perlin,Value Cubic,Value"), "set_noise_type", "get_noise_type"); ADD_PROPERTY(PropertyInfo(Variant::INT, "seed"), "set_seed", "get_seed"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "frequency", PROPERTY_HINT_RANGE, ".001,1"), "set_frequency", "get_frequency"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "frequency", PROPERTY_HINT_RANGE, ".0001,1,.0001"), "set_frequency", "get_frequency"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "offset", PROPERTY_HINT_RANGE, "-999999999,999999999,0.01"), "set_offset", "get_offset"); ADD_GROUP("Fractal", "fractal_"); diff --git a/modules/noise/noise.cpp b/modules/noise/noise.cpp index b8c1587ec3..1115d92f58 100644 --- a/modules/noise/noise.cpp +++ b/modules/noise/noise.cpp @@ -32,21 +32,40 @@ #include <float.h> -Ref<Image> Noise::get_seamless_image(int p_width, int p_height, int p_depth, bool p_invert, bool p_in_3d_space, real_t p_blend_skirt, bool p_normalize) const { - ERR_FAIL_COND_V(p_width <= 0 || p_height <= 0, Ref<Image>()); +Vector<Ref<Image>> Noise::_get_seamless_image(int p_width, int p_height, int p_depth, bool p_invert, bool p_in_3d_space, real_t p_blend_skirt, bool p_normalize) const { + ERR_FAIL_COND_V(p_width <= 0 || p_height <= 0 || p_depth <= 0, Vector<Ref<Image>>()); int skirt_width = MAX(1, p_width * p_blend_skirt); int skirt_height = MAX(1, p_height * p_blend_skirt); + int skirt_depth = MAX(1, p_depth * p_blend_skirt); int src_width = p_width + skirt_width; int src_height = p_height + skirt_height; + int src_depth = p_depth + skirt_depth; + + Vector<Ref<Image>> src = _get_image(src_width, src_height, src_depth, p_invert, p_in_3d_space, p_normalize); + bool grayscale = (src[0]->get_format() == Image::FORMAT_L8); - Ref<Image> src = get_image(src_width, src_height, p_depth, p_invert, p_in_3d_space, p_normalize); - bool grayscale = (src->get_format() == Image::FORMAT_L8); if (grayscale) { - return _generate_seamless_image<uint8_t>(src, p_width, p_height, p_invert, p_blend_skirt); + return _generate_seamless_image<uint8_t>(src, p_width, p_height, p_depth, p_invert, p_blend_skirt); } else { - return _generate_seamless_image<uint32_t>(src, p_width, p_height, p_invert, p_blend_skirt); + return _generate_seamless_image<uint32_t>(src, p_width, p_height, p_depth, p_invert, p_blend_skirt); + } +} + +Ref<Image> Noise::get_seamless_image(int p_width, int p_height, bool p_invert, bool p_in_3d_space, real_t p_blend_skirt, bool p_normalize) const { + Vector<Ref<Image>> images = _get_seamless_image(p_width, p_height, 1, p_invert, p_in_3d_space, p_blend_skirt, p_normalize); + return images[0]; +} + +TypedArray<Image> Noise::get_seamless_image_3d(int p_width, int p_height, int p_depth, bool p_invert, real_t p_blend_skirt, bool p_normalize) const { + Vector<Ref<Image>> images = _get_seamless_image(p_width, p_height, p_depth, p_invert, true, p_blend_skirt, p_normalize); + + TypedArray<Image> ret; + ret.resize(images.size()); + for (int i = 0; i < images.size(); i++) { + ret[i] = images[i]; } + return ret; } // Template specialization for faster grayscale blending. @@ -58,61 +77,104 @@ uint8_t Noise::_alpha_blend<uint8_t>(uint8_t p_bg, uint8_t p_fg, int p_alpha) co return (uint8_t)((alpha * p_fg + inv_alpha * p_bg) >> 8); } -Ref<Image> Noise::get_image(int p_width, int p_height, int p_depth, bool p_invert, bool p_in_3d_space, bool p_normalize) const { - ERR_FAIL_COND_V(p_width <= 0 || p_height <= 0, Ref<Image>()); - - Vector<uint8_t> data; - data.resize(p_width * p_height); +Vector<Ref<Image>> Noise::_get_image(int p_width, int p_height, int p_depth, bool p_invert, bool p_in_3d_space, bool p_normalize) const { + ERR_FAIL_COND_V(p_width <= 0 || p_height <= 0 || p_depth <= 0, Vector<Ref<Image>>()); - uint8_t *wd8 = data.ptrw(); + Vector<Ref<Image>> images; + images.resize(p_depth); if (p_normalize) { // Get all values and identify min/max values. - Vector<real_t> values; - values.resize(p_width * p_height); + LocalVector<real_t> values; + values.resize(p_width * p_height * p_depth); + real_t min_val = FLT_MAX; real_t max_val = -FLT_MAX; - for (int y = 0, i = 0; y < p_height; y++) { - for (int x = 0; x < p_width; x++, i++) { - values.set(i, p_in_3d_space ? get_noise_3d(x, y, p_depth) : get_noise_2d(x, y)); - if (values[i] > max_val) { - max_val = values[i]; - } - if (values[i] < min_val) { - min_val = values[i]; + int idx = 0; + for (int d = 0; d < p_depth; d++) { + for (int y = 0; y < p_height; y++) { + for (int x = 0; x < p_width; x++) { + values[idx] = p_in_3d_space ? get_noise_3d(x, y, d) : get_noise_2d(x, y); + if (values[idx] > max_val) { + max_val = values[idx]; + } + if (values[idx] < min_val) { + min_val = values[idx]; + } + idx++; } } } + idx = 0; // Normalize values and write to texture. - uint8_t ivalue; - for (int i = 0, x = 0; i < p_height; i++) { - for (int j = 0; j < p_width; j++, x++) { - if (max_val == min_val) { - ivalue = 0; - } else { - ivalue = static_cast<uint8_t>(CLAMP((values[x] - min_val) / (max_val - min_val) * 255.f, 0, 255)); - } - - if (p_invert) { - ivalue = 255 - ivalue; + for (int d = 0; d < p_depth; d++) { + Vector<uint8_t> data; + data.resize(p_width * p_height); + + uint8_t *wd8 = data.ptrw(); + uint8_t ivalue; + + for (int y = 0; y < p_height; y++) { + for (int x = 0; x < p_width; x++) { + if (max_val == min_val) { + ivalue = 0; + } else { + ivalue = static_cast<uint8_t>(CLAMP((values[idx] - min_val) / (max_val - min_val) * 255.f, 0, 255)); + } + + if (p_invert) { + ivalue = 255 - ivalue; + } + + wd8[x + y * p_width] = ivalue; + idx++; } - - wd8[x] = ivalue; } + Ref<Image> img = memnew(Image(p_width, p_height, false, Image::FORMAT_L8, data)); + images.write[d] = img; } } else { // Without normalization, the expected range of the noise function is [-1, 1]. - uint8_t ivalue; - for (int y = 0, i = 0; y < p_height; y++) { - for (int x = 0; x < p_width; x++, i++) { - float value = (p_in_3d_space ? get_noise_3d(x, y, p_depth) : get_noise_2d(x, y)); - ivalue = static_cast<uint8_t>(CLAMP(value * 127.5f + 127.5f, 0.0f, 255.0f)); - wd8[i] = p_invert ? (255 - ivalue) : ivalue; + + for (int d = 0; d < p_depth; d++) { + Vector<uint8_t> data; + data.resize(p_width * p_height); + + uint8_t *wd8 = data.ptrw(); + + uint8_t ivalue; + int idx = 0; + for (int y = 0; y < p_height; y++) { + for (int x = 0; x < p_width; x++) { + float value = (p_in_3d_space ? get_noise_3d(x, y, d) : get_noise_2d(x, y)); + ivalue = static_cast<uint8_t>(CLAMP(value * 127.5f + 127.5f, 0.0f, 255.0f)); + wd8[idx] = p_invert ? (255 - ivalue) : ivalue; + idx++; + } } + + Ref<Image> img = memnew(Image(p_width, p_height, false, Image::FORMAT_L8, data)); + images.write[d] = img; } } - return memnew(Image(p_width, p_height, false, Image::FORMAT_L8, data)); + return images; +} + +Ref<Image> Noise::get_image(int p_width, int p_height, bool p_invert, bool p_in_3d_space, bool p_normalize) const { + Vector<Ref<Image>> images = _get_image(p_width, p_height, 1, p_invert, p_in_3d_space, p_normalize); + return images[0]; +} + +TypedArray<Image> Noise::get_image_3d(int p_width, int p_height, int p_depth, bool p_invert, bool p_normalize) const { + Vector<Ref<Image>> images = _get_image(p_width, p_height, p_depth, p_invert, true, p_normalize); + + TypedArray<Image> ret; + ret.resize(images.size()); + for (int i = 0; i < images.size(); i++) { + ret[i] = images[i]; + } + return ret; } void Noise::_bind_methods() { @@ -124,6 +186,8 @@ void Noise::_bind_methods() { ClassDB::bind_method(D_METHOD("get_noise_3dv", "v"), &Noise::get_noise_3dv); // Textures. - ClassDB::bind_method(D_METHOD("get_image", "width", "height", "depth", "invert", "in_3d_space", "normalize"), &Noise::get_image, DEFVAL(false), DEFVAL(false), DEFVAL(true)); - ClassDB::bind_method(D_METHOD("get_seamless_image", "width", "height", "depth", "invert", "in_3d_space", "skirt", "normalize"), &Noise::get_seamless_image, DEFVAL(false), DEFVAL(false), DEFVAL(0.1), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("get_image", "width", "height", "invert", "in_3d_space", "normalize"), &Noise::get_image, DEFVAL(false), DEFVAL(false), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("get_seamless_image", "width", "height", "invert", "in_3d_space", "skirt", "normalize"), &Noise::get_seamless_image, DEFVAL(false), DEFVAL(false), DEFVAL(0.1), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("get_image_3d", "width", "height", "depth", "invert", "normalize"), &Noise::get_image_3d, DEFVAL(false), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("get_seamless_image_3d", "width", "height", "depth", "invert", "skirt", "normalize"), &Noise::get_seamless_image_3d, DEFVAL(false), DEFVAL(0.1), DEFVAL(true)); } diff --git a/modules/noise/noise.h b/modules/noise/noise.h index 856bef8c31..6c49c12bc2 100644 --- a/modules/noise/noise.h +++ b/modules/noise/noise.h @@ -32,6 +32,7 @@ #define NOISE_H #include "core/io/image.h" +#include "core/variant/typed_array.h" class Noise : public Resource { GDCLASS(Noise, Resource); @@ -81,7 +82,7 @@ class Noise : public Resource { }; template <typename T> - Ref<Image> _generate_seamless_image(Ref<Image> p_src, int p_width, int p_height, bool p_invert, real_t p_blend_skirt) const { + Vector<Ref<Image>> _generate_seamless_image(Vector<Ref<Image>> p_src, int p_width, int p_height, int p_depth, bool p_invert, real_t p_blend_skirt) const { /* To make a seamless image, we swap the quadrants so the edges are perfect matches. We initially get a 10% larger image so we have an overlap we can use to blend over the seams. @@ -101,7 +102,7 @@ class Noise : public Resource { on Source it's translated to corner of Q1/s3 unless the ALT_XY modulo moves it to Q4 */ - ERR_FAIL_COND_V(p_blend_skirt < 0, Ref<Image>()); + ERR_FAIL_COND_V(p_blend_skirt < 0, Vector<Ref<Image>>()); int skirt_width = MAX(1, p_width * p_blend_skirt); int skirt_height = MAX(1, p_height * p_blend_skirt); @@ -112,83 +113,139 @@ class Noise : public Resource { int skirt_edge_x = half_width + skirt_width; int skirt_edge_y = half_height + skirt_height; - Vector<uint8_t> dest; - dest.resize(p_width * p_height * Image::get_format_pixel_size(p_src->get_format())); - - img_buff<T> rd_src = { - (T *)p_src->get_data().ptr(), - src_width, src_height, - half_width, half_height, - p_width, p_height - }; - - // `wr` is setup for straight x/y coordinate array access. - img_buff<T> wr = { - (T *)dest.ptrw(), - p_width, p_height, - 0, 0, 0, 0 - }; - // `rd_dest` is a readable pointer to `wr`, i.e. what has already been written to the output buffer. - img_buff<T> rd_dest = { - (T *)dest.ptr(), - p_width, p_height, - 0, 0, 0, 0 - }; + Image::Format format = p_src[0]->get_format(); + int pixel_size = Image::get_format_pixel_size(format); + + Vector<Ref<Image>> images; + images.resize(p_src.size()); + + // First blend across x and y for all slices. + for (int d = 0; d < images.size(); d++) { + Vector<uint8_t> dest; + dest.resize(p_width * p_height * pixel_size); + + img_buff<T> rd_src = { + (T *)p_src[d]->get_data().ptr(), + src_width, src_height, + half_width, half_height, + p_width, p_height + }; + + // `wr` is setup for straight x/y coordinate array access. + img_buff<T> wr = { + (T *)dest.ptrw(), + p_width, p_height, + 0, 0, 0, 0 + }; + // `rd_dest` is a readable pointer to `wr`, i.e. what has already been written to the output buffer. + img_buff<T> rd_dest = { + (T *)dest.ptr(), + p_width, p_height, + 0, 0, 0, 0 + }; + + // Swap the quadrants to make edges seamless. + for (int y = 0; y < p_height; y++) { + for (int x = 0; x < p_width; x++) { + // rd_src has a half offset and the shorter modulo ignores the skirt. + // It reads and writes in Q1-4 order (see map above), skipping the skirt. + wr(x, y) = rd_src(x, y, img_buff<T>::ALT_XY); + } + } - // Swap the quadrants to make edges seamless. - for (int y = 0; y < p_height; y++) { - for (int x = 0; x < p_width; x++) { - // rd_src has a half offset and the shorter modulo ignores the skirt. - // It reads and writes in Q1-4 order (see map above), skipping the skirt. - wr(x, y) = rd_src(x, y, img_buff<T>::ALT_XY); + // Blend the vertical skirt over the middle seam. + for (int x = half_width; x < skirt_edge_x; x++) { + int alpha = 255 * (1 - Math::smoothstep(0.1f, 0.9f, float(x - half_width) / float(skirt_width))); + for (int y = 0; y < p_height; y++) { + // Skip the center square + if (y == half_height) { + y = skirt_edge_y - 1; + } else { + // Starts reading at s2, ALT_Y skips s3, and continues with s1. + wr(x, y) = _alpha_blend<T>(rd_dest(x, y), rd_src(x, y, img_buff<T>::ALT_Y), alpha); + } + } } - } - // Blend the vertical skirt over the middle seam. - for (int x = half_width; x < skirt_edge_x; x++) { - int alpha = 255 * (1 - Math::smoothstep(0.1f, 0.9f, float(x - half_width) / float(skirt_width))); - for (int y = 0; y < p_height; y++) { - // Skip the center square - if (y == half_height) { - y = skirt_edge_y - 1; - } else { - // Starts reading at s2, ALT_Y skips s3, and continues with s1. - wr(x, y) = _alpha_blend<T>(rd_dest(x, y), rd_src(x, y, img_buff<T>::ALT_Y), alpha); + // Blend the horizontal skirt over the middle seam. + for (int y = half_height; y < skirt_edge_y; y++) { + int alpha = 255 * (1 - Math::smoothstep(0.1f, 0.9f, float(y - half_height) / float(skirt_height))); + for (int x = 0; x < p_width; x++) { + // Skip the center square + if (x == half_width) { + x = skirt_edge_x - 1; + } else { + // Starts reading at s4, skips s3, continues with s5. + wr(x, y) = _alpha_blend<T>(rd_dest(x, y), rd_src(x, y, img_buff<T>::ALT_X), alpha); + } } } - } - // Blend the horizontal skirt over the middle seam. - for (int y = half_height; y < skirt_edge_y; y++) { - int alpha = 255 * (1 - Math::smoothstep(0.1f, 0.9f, float(y - half_height) / float(skirt_height))); - for (int x = 0; x < p_width; x++) { - // Skip the center square - if (x == half_width) { - x = skirt_edge_x - 1; - } else { - // Starts reading at s4, skips s3, continues with s5. - wr(x, y) = _alpha_blend<T>(rd_dest(x, y), rd_src(x, y, img_buff<T>::ALT_X), alpha); + // Fill in the center square. Wr starts at the top left of Q4, which is the equivalent of the top left of s3, unless a modulo is used. + for (int y = half_height; y < skirt_edge_y; y++) { + for (int x = half_width; x < skirt_edge_x; x++) { + int xpos = 255 * (1 - Math::smoothstep(0.1f, 0.9f, float(x - half_width) / float(skirt_width))); + int ypos = 255 * (1 - Math::smoothstep(0.1f, 0.9f, float(y - half_height) / float(skirt_height))); + + // Blend s3(Q1) onto s5(Q2) for the top half. + T top_blend = _alpha_blend<T>(rd_src(x, y, img_buff<T>::ALT_X), rd_src(x, y, img_buff<T>::DEFAULT), xpos); + // Blend s1(Q3) onto Q4 for the bottom half. + T bottom_blend = _alpha_blend<T>(rd_src(x, y, img_buff<T>::ALT_XY), rd_src(x, y, img_buff<T>::ALT_Y), xpos); + // Blend the top half onto the bottom half. + wr(x, y) = _alpha_blend<T>(bottom_blend, top_blend, ypos); } } + Ref<Image> image = memnew(Image(p_width, p_height, false, format, dest)); + p_src.write[d].unref(); + images.write[d] = image; } - // Fill in the center square. Wr starts at the top left of Q4, which is the equivalent of the top left of s3, unless a modulo is used. - for (int y = half_height; y < skirt_edge_y; y++) { - for (int x = half_width; x < skirt_edge_x; x++) { - int xpos = 255 * (1 - Math::smoothstep(0.1f, 0.9f, float(x - half_width) / float(skirt_width))); - int ypos = 255 * (1 - Math::smoothstep(0.1f, 0.9f, float(y - half_height) / float(skirt_height))); - - // Blend s3(Q1) onto s5(Q2) for the top half. - T top_blend = _alpha_blend<T>(rd_src(x, y, img_buff<T>::ALT_X), rd_src(x, y, img_buff<T>::DEFAULT), xpos); - // Blend s1(Q3) onto Q4 for the bottom half. - T bottom_blend = _alpha_blend<T>(rd_src(x, y, img_buff<T>::ALT_XY), rd_src(x, y, img_buff<T>::ALT_Y), xpos); - // Blend the top half onto the bottom half. - wr(x, y) = _alpha_blend<T>(bottom_blend, top_blend, ypos); + // Now blend across z. + if (p_depth > 1) { + int skirt_depth = MAX(1, p_depth * p_blend_skirt); + int half_depth = p_depth * 0.5; + int skirt_edge_z = half_depth + skirt_depth; + + // Swap halves on depth. + for (int i = 0; i < half_depth; i++) { + Ref<Image> img = images[i]; + images.write[i] = images[i + half_depth]; + images.write[i + half_depth] = img; } + + Vector<Ref<Image>> new_images = images; + new_images.resize(p_depth); + + // Scale seamless generation to third dimension. + for (int z = half_depth; z < skirt_edge_z; z++) { + int alpha = 255 * (1 - Math::smoothstep(0.1f, 0.9f, float(z - half_depth) / float(skirt_depth))); + + Vector<uint8_t> img = images[z % p_depth]->get_data(); + Vector<uint8_t> skirt = images[(z - half_depth) + p_depth]->get_data(); + + Vector<uint8_t> dest; + dest.resize(images[0]->get_width() * images[0]->get_height() * Image::get_format_pixel_size(images[0]->get_format())); + + for (int i = 0; i < img.size(); i++) { + uint8_t fg, bg, out; + + fg = skirt[i]; + bg = img[i]; + + uint16_t a = alpha + 1; + uint16_t inv_a = 256 - alpha; + + out = (uint8_t)((a * fg + inv_a * bg) >> 8); + + dest.write[i] = out; + } + + Ref<Image> new_image = memnew(Image(images[0]->get_width(), images[0]->get_height(), false, images[0]->get_format(), dest)); + new_images.write[z % p_depth] = new_image; + } + return new_images; } - Ref<Image> image = memnew(Image(p_width, p_height, false, p_src->get_format(), dest)); - p_src.unref(); - return image; + return images; } template <typename T> @@ -233,8 +290,13 @@ public: virtual real_t get_noise_3dv(Vector3 p_v) const = 0; virtual real_t get_noise_3d(real_t p_x, real_t p_y, real_t p_z) const = 0; - virtual Ref<Image> get_image(int p_width, int p_height, int p_depth, bool p_invert = false, bool p_in_3d_space = false, bool p_normalize = true) const; - virtual Ref<Image> get_seamless_image(int p_width, int p_height, int p_depth, bool p_invert = false, bool p_in_3d_space = false, real_t p_blend_skirt = 0.1, bool p_normalize = true) const; + Vector<Ref<Image>> _get_image(int p_width, int p_height, int p_depth, bool p_invert = false, bool p_in_3d_space = false, bool p_normalize = true) const; + virtual Ref<Image> get_image(int p_width, int p_height, bool p_invert = false, bool p_in_3d_space = false, bool p_normalize = true) const; + virtual TypedArray<Image> get_image_3d(int p_width, int p_height, int p_depth, bool p_invert = false, bool p_normalize = true) const; + + Vector<Ref<Image>> _get_seamless_image(int p_width, int p_height, int p_depth, bool p_invert = false, bool p_in_3d_space = false, real_t p_blend_skirt = 0.1, bool p_normalize = true) const; + virtual Ref<Image> get_seamless_image(int p_width, int p_height, bool p_invert = false, bool p_in_3d_space = false, real_t p_blend_skirt = 0.1, bool p_normalize = true) const; + virtual TypedArray<Image> get_seamless_image_3d(int p_width, int p_height, int p_depth, bool p_invert = false, real_t p_blend_skirt = 0.1, bool p_normalize = true) const; }; #endif // NOISE_H diff --git a/modules/noise/noise_texture_2d.cpp b/modules/noise/noise_texture_2d.cpp index a31f77a38d..e4b2e0b4ac 100644 --- a/modules/noise/noise_texture_2d.cpp +++ b/modules/noise/noise_texture_2d.cpp @@ -162,9 +162,9 @@ Ref<Image> NoiseTexture2D::_generate_texture() { Ref<Image> new_image; if (seamless) { - new_image = ref_noise->get_seamless_image(size.x, size.y, 0, invert, in_3d_space, seamless_blend_skirt, normalize); + new_image = ref_noise->get_seamless_image(size.x, size.y, invert, in_3d_space, seamless_blend_skirt, normalize); } else { - new_image = ref_noise->get_image(size.x, size.y, 0, invert, in_3d_space, normalize); + new_image = ref_noise->get_image(size.x, size.y, invert, in_3d_space, normalize); } if (color_ramp.is_valid()) { new_image = _modulate_with_gradient(new_image, color_ramp); diff --git a/modules/noise/noise_texture_3d.cpp b/modules/noise/noise_texture_3d.cpp index 58403397de..25d75b8ffb 100644 --- a/modules/noise/noise_texture_3d.cpp +++ b/modules/noise/noise_texture_3d.cpp @@ -44,7 +44,9 @@ NoiseTexture3D::~NoiseTexture3D() { if (texture.is_valid()) { RS::get_singleton()->free(texture); } - noise_thread.wait_to_finish(); + if (noise_thread.is_started()) { + noise_thread.wait_to_finish(); + } } void NoiseTexture3D::_bind_methods() { @@ -147,18 +149,9 @@ TypedArray<Image> NoiseTexture3D::_generate_texture() { Vector<Ref<Image>> images; if (seamless) { - images = _get_seamless(width, height, depth, invert, seamless_blend_skirt); + images = ref_noise->_get_seamless_image(width, height, depth, invert, true, seamless_blend_skirt, normalize); } else { - images.resize(depth); - - for (int i = 0; i < images.size(); i++) { - images.write[i] = ref_noise->get_image(width, height, i, invert, true, false); - } - } - - // Normalize on whole texture at once rather than on each image individually as it would result in visible artifacts on z (depth) axis. - if (normalize) { - images = _normalize(images); + images = ref_noise->_get_image(width, height, depth, invert, true, normalize); } if (color_ramp.is_valid()) { @@ -177,116 +170,6 @@ TypedArray<Image> NoiseTexture3D::_generate_texture() { return new_data; } -Vector<Ref<Image>> NoiseTexture3D::_get_seamless(int p_width, int p_height, int p_depth, bool p_invert, real_t p_blend_skirt) { - // Prevent memdelete due to unref() on other thread. - Ref<Noise> ref_noise = noise; - - if (ref_noise.is_null()) { - return Vector<Ref<Image>>(); - } - - int skirt_depth = MAX(1, p_depth * p_blend_skirt); - int src_depth = p_depth + skirt_depth; - - Vector<Ref<Image>> images; - images.resize(src_depth); - - for (int i = 0; i < src_depth; i++) { - images.write[i] = ref_noise->get_seamless_image(p_width, p_height, i, p_invert, true, p_blend_skirt, false); - } - - int half_depth = p_depth * 0.5; - int skirt_edge_z = half_depth + skirt_depth; - - // swap halves on depth. - for (int i = 0; i < half_depth; i++) { - Ref<Image> img = images[i]; - images.write[i] = images[i + half_depth]; - images.write[i + half_depth] = img; - } - - Vector<Ref<Image>> new_images = images; - new_images.resize(p_depth); - - // scale seamless generation to third dimension. - for (int z = half_depth; z < skirt_edge_z; z++) { - int alpha = 255 * (1 - Math::smoothstep(0.1f, 0.9f, float(z - half_depth) / float(skirt_depth))); - - Vector<uint8_t> img = images[z % p_depth]->get_data(); - Vector<uint8_t> skirt = images[(z - half_depth) + p_depth]->get_data(); - - Vector<uint8_t> dest; - dest.resize(images[0]->get_width() * images[0]->get_height() * Image::get_format_pixel_size(images[0]->get_format())); - - for (int i = 0; i < img.size(); i++) { - uint8_t fg, bg, out; - - fg = skirt[i]; - bg = img[i]; - - uint16_t a; - uint16_t inv_a; - - a = alpha + 1; - inv_a = 256 - alpha; - - out = (uint8_t)((a * fg + inv_a * bg) >> 8); - - dest.write[i] = out; - } - - Ref<Image> new_image = memnew(Image(images[0]->get_width(), images[0]->get_height(), false, images[0]->get_format(), dest)); - new_images.write[z % p_depth] = new_image; - } - - return new_images; -} - -Vector<Ref<Image>> NoiseTexture3D::_normalize(Vector<Ref<Image>> p_images) { - real_t min_val = FLT_MAX; - real_t max_val = -FLT_MAX; - - int w = p_images[0]->get_width(); - int h = p_images[0]->get_height(); - - for (int i = 0; i < p_images.size(); i++) { - Vector<uint8_t> data = p_images[i]->get_data(); - - for (int j = 0; j < data.size(); j++) { - if (data[j] > max_val) { - max_val = data[j]; - } - if (data[j] < min_val) { - min_val = data[j]; - } - } - } - - Vector<Ref<Image>> new_images; - new_images.resize(p_images.size()); - - for (int i = 0; i < p_images.size(); i++) { - Vector<uint8_t> data = p_images[i]->get_data(); - - for (int j = 0; j < data.size(); j++) { - uint8_t value; - - if (max_val == min_val) { - value = 0; - } else { - value = static_cast<uint8_t>(CLAMP((data[j] - min_val) / (max_val - min_val) * 255.f, 0, 255)); - } - - data.write[j] = value; - } - - Ref<Image> new_image = memnew(Image(w, h, false, Image::FORMAT_L8, data)); - new_images.write[i] = new_image; - } - - return new_images; -} - Ref<Image> NoiseTexture3D::_modulate_with_gradient(Ref<Image> p_image, Ref<Gradient> p_gradient) { int w = p_image->get_width(); int h = p_image->get_height(); diff --git a/modules/noise/noise_texture_3d.h b/modules/noise/noise_texture_3d.h index b5dab10321..397711ca98 100644 --- a/modules/noise/noise_texture_3d.h +++ b/modules/noise/noise_texture_3d.h @@ -68,8 +68,6 @@ private: void _update_texture(); void _set_texture_data(const TypedArray<Image> &p_data); - Vector<Ref<Image>> _get_seamless(int p_width, int p_height, int p_depth, bool p_invert, real_t p_blend_skirt); - Vector<Ref<Image>> _normalize(Vector<Ref<Image>> p_images); Ref<Image> _modulate_with_gradient(Ref<Image> p_image, Ref<Gradient> p_gradient); protected: diff --git a/modules/noise/tests/test_fastnoise_lite.h b/modules/noise/tests/test_fastnoise_lite.h index db489c6672..0a435c6a5c 100644 --- a/modules/noise/tests/test_fastnoise_lite.h +++ b/modules/noise/tests/test_fastnoise_lite.h @@ -605,7 +605,7 @@ TEST_CASE("[FastNoiseLite] Generating seamless 2D images (11x11px) and compare t noise.set_cellular_jitter(0.0); SUBCASE("Blend skirt 0.0") { - Ref<Image> img = noise.get_seamless_image(11, 11, 0, false, false, 0.0); + Ref<Image> img = noise.get_seamless_image(11, 11, false, false, 0.0); Ref<Image> ref_img_1 = memnew(Image); ref_img_1->set_data(11, 11, false, Image::FORMAT_L8, ref_img_1_data); @@ -614,7 +614,7 @@ TEST_CASE("[FastNoiseLite] Generating seamless 2D images (11x11px) and compare t } SUBCASE("Blend skirt 0.1") { - Ref<Image> img = noise.get_seamless_image(11, 11, 0, false, false, 0.1); + Ref<Image> img = noise.get_seamless_image(11, 11, false, false, 0.1); Ref<Image> ref_img_2 = memnew(Image); ref_img_2->set_data(11, 11, false, Image::FORMAT_L8, ref_img_2_data); @@ -623,7 +623,7 @@ TEST_CASE("[FastNoiseLite] Generating seamless 2D images (11x11px) and compare t } SUBCASE("Blend skirt 1.0") { - Ref<Image> img = noise.get_seamless_image(11, 11, 0, false, false, 0.1); + Ref<Image> img = noise.get_seamless_image(11, 11, false, false, 0.1); Ref<Image> ref_img_3 = memnew(Image); ref_img_3->set_data(11, 11, false, Image::FORMAT_L8, ref_img_3_data); diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp index d2f6be2233..63abbf0d71 100644 --- a/modules/openxr/action_map/openxr_action_map.cpp +++ b/modules/openxr/action_map/openxr_action_map.cpp @@ -415,7 +415,7 @@ void OpenXRActionMap::create_default_action_sets() { profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose"); profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click"); - profile->add_new_binding(select_button, "/user/hand/left/input/system/click"); // we'll map system to select + profile->add_new_binding(select_button, "/user/hand/right/input/system/click"); // we'll map system to select profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); @@ -439,7 +439,7 @@ void OpenXRActionMap::create_default_action_sets() { profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose"); profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click"); - profile->add_new_binding(select_button, "/user/hand/left/input/system/click"); // we'll map system to select + profile->add_new_binding(select_button, "/user/hand/right/input/system/click"); // we'll map system to select profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); diff --git a/modules/openxr/extensions/openxr_htc_controller_extension.cpp b/modules/openxr/extensions/openxr_htc_controller_extension.cpp index 4d141b0695..116762ee8d 100644 --- a/modules/openxr/extensions/openxr_htc_controller_extension.cpp +++ b/modules/openxr/extensions/openxr_htc_controller_extension.cpp @@ -108,7 +108,7 @@ void OpenXRHTCControllerExtension::on_register_metadata() { metadata->register_io_path("/interaction_profiles/htc/vive_focus3_controller", "Trigger touch", "/user/hand/left", "/user/hand/left/input/trigger/touch", "", OpenXRAction::OPENXR_ACTION_BOOL); metadata->register_io_path("/interaction_profiles/htc/vive_focus3_controller", "Trigger", "/user/hand/right", "/user/hand/right/input/trigger/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); metadata->register_io_path("/interaction_profiles/htc/vive_focus3_controller", "Trigger click", "/user/hand/right", "/user/hand/right/input/trigger/click", "", OpenXRAction::OPENXR_ACTION_BOOL); - metadata->register_io_path("/interaction_profiles/htc/vive_focus3_controller", "Trigger touch", "/user/hand/right", "/user/hand/right/input/trigger/touch ", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/htc/vive_focus3_controller", "Trigger touch", "/user/hand/right", "/user/hand/right/input/trigger/touch", "", OpenXRAction::OPENXR_ACTION_BOOL); metadata->register_io_path("/interaction_profiles/htc/vive_focus3_controller", "Squeeze click", "/user/hand/left", "/user/hand/left/input/squeeze/click", "", OpenXRAction::OPENXR_ACTION_BOOL); metadata->register_io_path("/interaction_profiles/htc/vive_focus3_controller", "Squeeze touch", "/user/hand/left", "/user/hand/left/input/squeeze/touch", "", OpenXRAction::OPENXR_ACTION_BOOL); @@ -122,6 +122,7 @@ void OpenXRHTCControllerExtension::on_register_metadata() { metadata->register_io_path("/interaction_profiles/htc/vive_focus3_controller", "Thumbstick click", "/user/hand/right", "/user/hand/right/input/thumbstick/click", "", OpenXRAction::OPENXR_ACTION_BOOL); metadata->register_io_path("/interaction_profiles/htc/vive_focus3_controller", "Thumbstick touch", "/user/hand/right", "/user/hand/right/input/thumbstick/touch", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/htc/vive_focus3_controller", "Thumbrest touch", "/user/hand/left", "/user/hand/left/input/thumbrest/touch", "", OpenXRAction::OPENXR_ACTION_BOOL); metadata->register_io_path("/interaction_profiles/htc/vive_focus3_controller", "Thumbrest touch", "/user/hand/right", "/user/hand/right/input/thumbrest/touch", "", OpenXRAction::OPENXR_ACTION_BOOL); metadata->register_io_path("/interaction_profiles/htc/vive_focus3_controller", "Haptic output", "/user/hand/left", "/user/hand/left/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC); diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub index 73c0851dc7..49b15c7a90 100644 --- a/modules/text_server_adv/SCsub +++ b/modules/text_server_adv/SCsub @@ -520,7 +520,11 @@ if env["builtin_icu4c"]: module_obj = [] if env["builtin_msdfgen"] and msdfgen_enabled: - env_text_server_adv.Prepend(CPPPATH=["#thirdparty/msdfgen"]) + # Treat msdfgen headers as system headers to avoid raising warnings. Not supported on MSVC. + if not env.msvc: + env_text_server_adv.Append(CPPFLAGS=["-isystem", Dir("#thirdparty/msdfgen").path]) + else: + env_text_server_adv.Prepend(CPPPATH=["#thirdparty/msdfgen"]) if env["builtin_freetype"] and freetype_enabled: env_text_server_adv.Append(CPPDEFINES=["FT_CONFIG_OPTION_USE_BROTLI"]) diff --git a/modules/text_server_fb/SCsub b/modules/text_server_fb/SCsub index f1d57ec4d3..582e622147 100644 --- a/modules/text_server_fb/SCsub +++ b/modules/text_server_fb/SCsub @@ -12,7 +12,11 @@ if "svg" in env.module_list: env_text_server_fb.Prepend(CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib"]) if env["builtin_msdfgen"] and msdfgen_enabled: - env_text_server_fb.Prepend(CPPPATH=["#thirdparty/msdfgen"]) + # Treat msdfgen headers as system headers to avoid raising warnings. Not supported on MSVC. + if not env.msvc: + env_text_server_fb.Append(CPPFLAGS=["-isystem", Dir("#thirdparty/msdfgen").path]) + else: + env_text_server_fb.Prepend(CPPPATH=["#thirdparty/msdfgen"]) if env["builtin_freetype"] and freetype_enabled: env_text_server_fb.Append(CPPDEFINES=["FT_CONFIG_OPTION_USE_BROTLI"]) diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 8b210bdc38..b76214cf94 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -3865,8 +3865,8 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { } prev_font = gl.font_rid; - double scale = _font_get_scale(gl.font_rid, gl.font_size); if (gl.font_rid.is_valid()) { + double scale = _font_get_scale(gl.font_rid, gl.font_size); bool subpos = (scale != 1.0) || (_font_get_subpixel_positioning(gl.font_rid) == SUBPIXEL_POSITIONING_ONE_HALF) || (_font_get_subpixel_positioning(gl.font_rid) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (_font_get_subpixel_positioning(gl.font_rid) == SUBPIXEL_POSITIONING_AUTO && gl.font_size <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE); if (sd->text[j - sd->start] != 0 && !is_linebreak(sd->text[j - sd->start])) { if (sd->orientation == ORIENTATION_HORIZONTAL) { diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index 816d5276b9..109d5102c3 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -332,7 +332,7 @@ void WSLPeer::_do_client_handshake() { if (connection == tcp) { // Start SSL handshake tls = Ref<StreamPeerTLS>(StreamPeerTLS::create()); - ERR_FAIL_COND_MSG(tls.is_null(), "SSL is not available in this build."); + ERR_FAIL_COND(tls.is_null()); if (tls->connect_to_stream(tcp, requested_host, tls_options) != OK) { close(-1); return; // Error. @@ -501,6 +501,8 @@ Error WSLPeer::connect_to_url(const String &p_url, Ref<TLSOptions> p_options) { path = "/"; } + ERR_FAIL_COND_V_MSG(use_tls && !StreamPeerTLS::is_available(), ERR_UNAVAILABLE, "WSS is not available in this build."); + requested_url = p_url; requested_host = host; |
