summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/astcenc/SCsub1
-rw-r--r--modules/csg/csg.cpp36
-rw-r--r--modules/csg/csg.h4
-rw-r--r--modules/csg/csg_shape.cpp40
-rw-r--r--modules/csg/csg_shape.h6
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml17
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp1
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp5
-rw-r--r--modules/gdscript/gdscript_editor.cpp14
-rw-r--r--modules/gdscript/gdscript_parser.cpp1
-rw-r--r--modules/gdscript/language_server/godot_lsp.h4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.out9
-rw-r--r--modules/gltf/doc_classes/GLTFAccessor.xml2
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp4
-rw-r--r--modules/gltf/gltf_document.cpp6
-rw-r--r--modules/gltf/structures/gltf_accessor.h2
-rw-r--r--modules/gridmap/grid_map.cpp4
-rw-r--r--modules/mbedtls/SCsub4
-rw-r--r--modules/mbedtls/crypto_mbedtls.cpp59
-rw-r--r--modules/mbedtls/crypto_mbedtls.h2
-rw-r--r--modules/mbedtls/packet_peer_mbed_dtls.cpp1
-rw-r--r--modules/mono/csharp_script.cpp7
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs3
-rw-r--r--modules/mono/editor/bindings_generator.cpp10
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs37
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs35
-rw-r--r--modules/navigation/SCsub29
-rw-r--r--modules/navigation/godot_navigation_server.cpp218
-rw-r--r--modules/navigation/godot_navigation_server.h33
-rw-r--r--modules/navigation/nav_agent.cpp290
-rw-r--r--modules/navigation/nav_agent.h113
-rw-r--r--modules/navigation/nav_base.h5
-rw-r--r--modules/navigation/nav_link.cpp12
-rw-r--r--modules/navigation/nav_map.cpp266
-rw-r--r--modules/navigation/nav_map.h52
-rw-r--r--modules/navigation/nav_obstacle.cpp86
-rw-r--r--modules/navigation/nav_obstacle.h78
-rw-r--r--modules/navigation/nav_region.cpp13
-rw-r--r--modules/navigation/nav_region.h7
-rw-r--r--modules/noise/doc_classes/Noise.xml41
-rw-r--r--modules/noise/doc_classes/NoiseTexture3D.xml3
-rw-r--r--modules/noise/fastnoise_lite.cpp2
-rw-r--r--modules/noise/noise.cpp152
-rw-r--r--modules/noise/noise.h200
-rw-r--r--modules/noise/noise_texture_2d.cpp4
-rw-r--r--modules/noise/noise_texture_3d.cpp127
-rw-r--r--modules/noise/noise_texture_3d.h2
-rw-r--r--modules/noise/tests/test_fastnoise_lite.h6
-rw-r--r--modules/openxr/action_map/openxr_action_map.cpp4
-rw-r--r--modules/openxr/extensions/openxr_htc_controller_extension.cpp3
-rw-r--r--modules/text_server_adv/SCsub6
-rw-r--r--modules/text_server_fb/SCsub6
-rw-r--r--modules/text_server_fb/text_server_fb.cpp2
-rw-r--r--modules/websocket/wsl_peer.cpp4
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;