diff --git a/doc/classes/NavigationMeshSourceGeometryData2D.xml b/doc/classes/NavigationMeshSourceGeometryData2D.xml index 9c05248eff0..609877fadce 100644 --- a/doc/classes/NavigationMeshSourceGeometryData2D.xml +++ b/doc/classes/NavigationMeshSourceGeometryData2D.xml @@ -16,6 +16,14 @@ Adds the outline points of a shape as obstructed area. + + + + + + Adds a projected obstruction shape to the source geometry. If [param carve] is [code]true[/code] the carved shape will not be affected by additional offsets (e.g. agent radius) of the navigation mesh baking process. + + @@ -29,12 +37,26 @@ Clears the internal data. + + + + Clears all projected obstructions. + + Returns all the obstructed area outlines arrays. + + + + Returns the projected obstructions as an [Array] of dictionaries. Each [Dictionary] contains the following entries: + - [code]vertices[/code] - A [PackedFloat32Array] that defines the outline points of the projected shape. + - [code]carve[/code] - A [bool] that defines how the projected shape affects the navigation mesh baking. If [code]true[/code] the projected shape will not be affected by addition offsets, e.g. agent radius. + + @@ -61,6 +83,19 @@ Sets all the obstructed area outlines arrays. + + + + + Sets the projected obstructions with an Array of Dictionaries with the following key value pairs: + [codeblocks] + [gdscript] + "vertices" : PackedFloat32Array + "carve" : bool + [/gdscript] + [/codeblocks] + + diff --git a/doc/classes/NavigationMeshSourceGeometryData3D.xml b/doc/classes/NavigationMeshSourceGeometryData3D.xml index a3dcd4d209c..322e2e7c66c 100644 --- a/doc/classes/NavigationMeshSourceGeometryData3D.xml +++ b/doc/classes/NavigationMeshSourceGeometryData3D.xml @@ -33,18 +33,44 @@ Adds an [Array] the size of [constant Mesh.ARRAY_MAX] and with vertices at index [constant Mesh.ARRAY_VERTEX] and indices at index [constant Mesh.ARRAY_INDEX] to the navigation mesh baking data. The array must have valid triangulated mesh data to be considered. Since [NavigationMesh] resources have no transform, all vertex positions need to be offset by the node's transform using [param xform]. + + + + + + + + Adds a projected obstruction shape to the source geometry. The [param vertices] are considered projected on a xz-axes plane, placed at the global y-axis [param elevation] and extruded by [param height]. If [param carve] is [code]true[/code] the carved shape will not be affected by additional offsets (e.g. agent radius) of the navigation mesh baking process. + + Clears the internal data. + + + + Clears all projected obstructions. + + Returns the parsed source geometry data indices array. + + + + Returns the projected obstructions as an [Array] of dictionaries. Each [Dictionary] contains the following entries: + - [code]vertices[/code] - A [PackedFloat32Array] that defines the outline points of the projected shape. + - [code]elevation[/code] - A [float] that defines the projected shape placement on the y-axis. + - [code]height[/code] - A [float] that defines how much the projected shape is extruded along the y-axis. + - [code]carve[/code] - A [bool] that defines how the obstacle affects the navigation mesh baking. If [code]true[/code] the projected shape will not be affected by addition offsets, e.g. agent radius. + + @@ -72,6 +98,21 @@ [b]Warning:[/b] Inappropriate data can crash the baking process of the involved third-party libraries. + + + + + Sets the projected obstructions with an Array of Dictionaries with the following key value pairs: + [codeblocks] + [gdscript] + "vertices" : PackedFloat32Array + "elevation" : float + "height" : float + "carve" : bool + [/gdscript] + [/codeblocks] + + diff --git a/doc/classes/NavigationObstacle2D.xml b/doc/classes/NavigationObstacle2D.xml index 19b515c7cb2..12205e2ac32 100644 --- a/doc/classes/NavigationObstacle2D.xml +++ b/doc/classes/NavigationObstacle2D.xml @@ -1,13 +1,12 @@ - 2D Obstacle used in navigation to constrain avoidance controlled agents outside or inside an area. + 2D obstacle used to affect navigation mesh baking or constrain velocities of avoidance controlled agents. - 2D Obstacle used in navigation to constrain avoidance controlled agents outside or inside an area. The obstacle needs a navigation map and outline vertices defined to work correctly. - If the obstacle's vertices are winded in clockwise order, avoidance agents will be pushed in by the obstacle, otherwise, avoidance agents will be pushed out. Outlines must not cross or overlap. - Obstacles are [b]not[/b] a replacement for a (re)baked navigation mesh. Obstacles [b]don't[/b] change the resulting path from the pathfinding, obstacles only affect the navigation avoidance agent movement by altering the suggested velocity of the avoidance agent. - Obstacles using vertices can warp to a new position but should not moved every frame as each move requires a rebuild of the avoidance map. + An obstacle needs a navigation map and outline [member vertices] defined to work correctly. The outlines can not cross or overlap. + Obstacles can be included in the navigation mesh baking process when [member affect_navigation_mesh] is enabled. They do not add walkable geometry, instead their role is to discard other source geometry inside the shape. This can be used to prevent navigation mesh from appearing in unwanted places. If [member carve_navigation_mesh] is enabled the baked shape will not be affected by offsets of the navigation mesh baking, e.g. the agent radius. + With [member avoidance_enabled] the obstacle can constrain the avoidance velocities of avoidance using agents. If the obstacle's vertices are wound in clockwise order, avoidance agents will be pushed in by the obstacle, otherwise, avoidance agents will be pushed out. Obstacles using vertices and avoidance can warp to a new position but should not be moved every single frame as each change requires a rebuild of the avoidance map. $DOCS_URL/tutorials/navigation/navigation_using_navigationobstacles.html @@ -49,12 +48,20 @@ + + If enabled and parsed in a navigation mesh baking process the obstacle will discard source geometry inside its [member vertices] defined shape. + If [code]true[/code] the obstacle affects avoidance using agents. A bitfield determining the avoidance layers for this obstacle. Agents with a matching bit on the their avoidance mask will avoid this obstacle. + + If enabled the obstacle vertices will carve into the baked navigation mesh with the shape unaffected by additional offsets (e.g. agent radius). + It will still be affected by further postprocessing of the baking process, like edge and polygon simplification. + Requires [member affect_navigation_mesh] to be enabled. + Sets the avoidance radius for the obstacle. diff --git a/doc/classes/NavigationObstacle3D.xml b/doc/classes/NavigationObstacle3D.xml index 998279b3c49..bc6dbabc0e7 100644 --- a/doc/classes/NavigationObstacle3D.xml +++ b/doc/classes/NavigationObstacle3D.xml @@ -1,13 +1,12 @@ - 3D Obstacle used in navigation to constrain avoidance controlled agents outside or inside an area. + 3D obstacle used to affect navigation mesh baking or constrain velocities of avoidance controlled agents. - 3D Obstacle used in navigation to constrain avoidance controlled agents outside or inside an area. The obstacle needs a navigation map and outline vertices defined to work correctly. - If the obstacle's vertices are winded in clockwise order, avoidance agents will be pushed in by the obstacle, otherwise, avoidance agents will be pushed out. Outlines must not cross or overlap. - Obstacles are [b]not[/b] a replacement for a (re)baked navigation mesh. Obstacles [b]don't[/b] change the resulting path from the pathfinding, obstacles only affect the navigation avoidance agent movement by altering the suggested velocity of the avoidance agent. - Obstacles using vertices can warp to a new position but should not moved every frame as each move requires a rebuild of the avoidance map. + An obstacle needs a navigation map and outline [member vertices] defined to work correctly. The outlines can not cross or overlap and are restricted to a plane projection. This means the y-axis of the vertices is ignored, instead the obstacle's global y-axis position is used for placement. The projected shape is extruded by the obstacles height along the y-axis. + Obstacles can be included in the navigation mesh baking process when [member affect_navigation_mesh] is enabled. They do not add walkable geometry, instead their role is to discard other source geometry inside the shape. This can be used to prevent navigation mesh from appearing in unwanted places, e.g. inside "solid" geometry or on top of it. If [member carve_navigation_mesh] is enabled the baked shape will not be affected by offsets of the navigation mesh baking, e.g. the agent radius. + With [member avoidance_enabled] the obstacle can constrain the avoidance velocities of avoidance using agents. If the obstacle's vertices are wound in clockwise order, avoidance agents will be pushed in by the obstacle, otherwise, avoidance agents will be pushed out. Obstacles using vertices and avoidance can warp to a new position but should not be moved every single frame as each change requires a rebuild of the avoidance map. $DOCS_URL/tutorials/navigation/navigation_using_navigationobstacles.html @@ -49,12 +48,20 @@ + + If enabled and parsed in a navigation mesh baking process the obstacle will discard source geometry inside its [member vertices] and [member height] defined shape. + If [code]true[/code] the obstacle affects avoidance using agents. A bitfield determining the avoidance layers for this obstacle. Agents with a matching bit on the their avoidance mask will avoid this obstacle. + + If enabled the obstacle vertices will carve into the baked navigation mesh with the shape unaffected by additional offsets (e.g. agent radius). + It will still be affected by further postprocessing of the baking process, like edge and polygon simplification. + Requires [member affect_navigation_mesh] to be enabled. + Sets the obstacle height used in 2D avoidance. 2D avoidance using agent's ignore obstacles that are below or above them. diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp index 3dbf9f8735e..069cc0f4a8b 100644 --- a/modules/navigation/2d/nav_mesh_generator_2d.cpp +++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp @@ -35,6 +35,7 @@ #include "core/config/project_settings.h" #include "scene/2d/mesh_instance_2d.h" #include "scene/2d/multimesh_instance_2d.h" +#include "scene/2d/navigation_obstacle_2d.h" #include "scene/2d/physics/static_body_2d.h" #include "scene/2d/polygon_2d.h" #include "scene/2d/tile_map.h" @@ -233,6 +234,7 @@ void NavMeshGenerator2D::generator_parse_geometry_node(Ref p_ generator_parse_polygon2d_node(p_navigation_mesh, p_source_geometry_data, p_node); generator_parse_staticbody2d_node(p_navigation_mesh, p_source_geometry_data, p_node); generator_parse_tilemap_node(p_navigation_mesh, p_source_geometry_data, p_node); + generator_parse_navigationobstacle_node(p_navigation_mesh, p_source_geometry_data, p_node); if (p_recurse_children) { for (int i = 0; i < p_node->get_child_count(); i++) { @@ -660,6 +662,58 @@ void NavMeshGenerator2D::generator_parse_tilemap_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + NavigationObstacle2D *obstacle = Object::cast_to(p_node); + if (obstacle == nullptr) { + return; + } + + if (!obstacle->get_affect_navigation_mesh()) { + return; + } + + const Transform2D node_xform = p_source_geometry_data->root_node_transform * Transform2D(0.0, obstacle->get_global_position()); + + const float obstacle_radius = obstacle->get_radius(); + + if (obstacle_radius > 0.0) { + Vector obstruction_circle_vertices; + + // The point of this is that the moving obstacle can make a simple hole in the navigation mesh and affect the pathfinding. + // Without, navigation paths can go directly through the middle of the obstacle and conflict with the avoidance to get agents stuck. + // No place for excessive "round" detail here. Every additional edge adds a high cost for something that needs to be quick, not pretty. + static const int circle_points = 12; + + obstruction_circle_vertices.resize(circle_points); + Vector2 *circle_vertices_ptrw = obstruction_circle_vertices.ptrw(); + const real_t circle_point_step = Math_TAU / circle_points; + + for (int i = 0; i < circle_points; i++) { + const float angle = i * circle_point_step; + circle_vertices_ptrw[i] = node_xform.xform(Vector2(Math::cos(angle) * obstacle_radius, Math::sin(angle) * obstacle_radius)); + } + + p_source_geometry_data->add_projected_obstruction(obstruction_circle_vertices, obstacle->get_carve_navigation_mesh()); + } + + const Vector &obstacle_vertices = obstacle->get_vertices(); + + if (obstacle_vertices.is_empty()) { + return; + } + + Vector obstruction_shape_vertices; + obstruction_shape_vertices.resize(obstacle_vertices.size()); + + const Vector2 *obstacle_vertices_ptr = obstacle_vertices.ptr(); + Vector2 *obstruction_shape_vertices_ptrw = obstruction_shape_vertices.ptrw(); + + for (int i = 0; i < obstacle_vertices.size(); i++) { + obstruction_shape_vertices_ptrw[i] = node_xform.xform(obstacle_vertices_ptr[i]); + } + p_source_geometry_data->add_projected_obstruction(obstruction_shape_vertices, obstacle->get_carve_navigation_mesh()); +} + void NavMeshGenerator2D::generator_parse_source_geometry_data(Ref p_navigation_mesh, Ref p_source_geometry_data, Node *p_root_node) { List parse_nodes; @@ -779,6 +833,30 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref &projected_obstructions = p_source_geometry_data->_get_projected_obstructions(); + + if (!projected_obstructions.is_empty()) { + for (const NavigationMeshSourceGeometryData2D::ProjectedObstruction &projected_obstruction : projected_obstructions) { + if (projected_obstruction.carve) { + continue; + } + if (projected_obstruction.vertices.is_empty() || projected_obstruction.vertices.size() % 2 != 0) { + continue; + } + + Path64 clip_path; + clip_path.reserve(projected_obstruction.vertices.size() / 2); + for (int i = 0; i < projected_obstruction.vertices.size() / 2; i++) { + const Point64 &point = Point64(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]); + clip_path.push_back(point); + } + if (!IsPositive(clip_path)) { + std::reverse(clip_path.begin(), clip_path.end()); + } + obstruction_polygon_paths.push_back(clip_path); + } + } + Rect2 baking_rect = p_navigation_mesh->get_baking_rect(); if (baking_rect.has_area()) { Vector2 baking_rect_offset = p_navigation_mesh->get_baking_rect_offset(); @@ -809,6 +887,33 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref 0.0) { path_solution = InflatePaths(path_solution, -agent_radius_offset, JoinType::Miter, EndType::Polygon); } + + if (!projected_obstructions.is_empty()) { + obstruction_polygon_paths.resize(0); + for (const NavigationMeshSourceGeometryData2D::ProjectedObstruction &projected_obstruction : projected_obstructions) { + if (!projected_obstruction.carve) { + continue; + } + if (projected_obstruction.vertices.is_empty() || projected_obstruction.vertices.size() % 2 != 0) { + continue; + } + + Path64 clip_path; + clip_path.reserve(projected_obstruction.vertices.size() / 2); + for (int i = 0; i < projected_obstruction.vertices.size() / 2; i++) { + const Point64 &point = Point64(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]); + clip_path.push_back(point); + } + if (!IsPositive(clip_path)) { + std::reverse(clip_path.begin(), clip_path.end()); + } + obstruction_polygon_paths.push_back(clip_path); + } + if (obstruction_polygon_paths.size() > 0) { + path_solution = Difference(path_solution, obstruction_polygon_paths, FillRule::NonZero); + } + } + //path_solution = RamerDouglasPeucker(path_solution, 0.025); // real_t border_size = p_navigation_mesh->get_border_size(); diff --git a/modules/navigation/2d/nav_mesh_generator_2d.h b/modules/navigation/2d/nav_mesh_generator_2d.h index b606f3f6fc3..2567a170ef6 100644 --- a/modules/navigation/2d/nav_mesh_generator_2d.h +++ b/modules/navigation/2d/nav_mesh_generator_2d.h @@ -81,6 +81,7 @@ class NavMeshGenerator2D : public Object { static void generator_parse_polygon2d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); static void generator_parse_staticbody2d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); static void generator_parse_tilemap_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); + static void generator_parse_navigationobstacle_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); static bool generator_emit_callback(const Callable &p_callback); diff --git a/modules/navigation/3d/nav_mesh_generator_3d.cpp b/modules/navigation/3d/nav_mesh_generator_3d.cpp index e1ed9c51aae..b3391398a68 100644 --- a/modules/navigation/3d/nav_mesh_generator_3d.cpp +++ b/modules/navigation/3d/nav_mesh_generator_3d.cpp @@ -37,6 +37,7 @@ #include "core/os/thread.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/multimesh_instance_3d.h" +#include "scene/3d/navigation_obstacle_3d.h" #include "scene/3d/physics/static_body_3d.h" #include "scene/resources/3d/box_shape_3d.h" #include "scene/resources/3d/capsule_shape_3d.h" @@ -251,6 +252,7 @@ void NavMeshGenerator3D::generator_parse_geometry_node(const Ref #ifdef MODULE_GRIDMAP_ENABLED generator_parse_gridmap_node(p_navigation_mesh, p_source_geometry_data, p_node); #endif + generator_parse_navigationobstacle_node(p_navigation_mesh, p_source_geometry_data, p_node); if (p_recurse_children) { for (int i = 0; i < p_node->get_child_count(); i++) { @@ -569,6 +571,59 @@ void NavMeshGenerator3D::generator_parse_gridmap_node(const Ref } #endif // MODULE_GRIDMAP_ENABLED +void NavMeshGenerator3D::generator_parse_navigationobstacle_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + NavigationObstacle3D *obstacle = Object::cast_to(p_node); + if (obstacle == nullptr) { + return; + } + + if (!obstacle->get_affect_navigation_mesh()) { + return; + } + + const Transform3D node_xform = p_source_geometry_data->root_node_transform * Transform3D(Basis(), obstacle->get_global_position()); + + const float obstacle_radius = obstacle->get_radius(); + + if (obstacle_radius > 0.0) { + Vector obstruction_circle_vertices; + + // The point of this is that the moving obstacle can make a simple hole in the navigation mesh and affect the pathfinding. + // Without, navigation paths can go directly through the middle of the obstacle and conflict with the avoidance to get agents stuck. + // No place for excessive "round" detail here. Every additional edge adds a high cost for something that needs to be quick, not pretty. + static const int circle_points = 12; + + obstruction_circle_vertices.resize(circle_points); + Vector3 *circle_vertices_ptrw = obstruction_circle_vertices.ptrw(); + const real_t circle_point_step = Math_TAU / circle_points; + + for (int i = 0; i < circle_points; i++) { + const float angle = i * circle_point_step; + circle_vertices_ptrw[i] = node_xform.xform(Vector3(Math::cos(angle) * obstacle_radius, 0.0, Math::sin(angle) * obstacle_radius)); + } + + p_source_geometry_data->add_projected_obstruction(obstruction_circle_vertices, obstacle->get_global_position().y + p_source_geometry_data->root_node_transform.origin.y - obstacle_radius, obstacle_radius, obstacle->get_carve_navigation_mesh()); + } + + const Vector &obstacle_vertices = obstacle->get_vertices(); + + if (obstacle_vertices.is_empty()) { + return; + } + + Vector obstruction_shape_vertices; + obstruction_shape_vertices.resize(obstacle_vertices.size()); + + const Vector3 *obstacle_vertices_ptr = obstacle_vertices.ptr(); + Vector3 *obstruction_shape_vertices_ptrw = obstruction_shape_vertices.ptrw(); + + for (int i = 0; i < obstacle_vertices.size(); i++) { + obstruction_shape_vertices_ptrw[i] = node_xform.xform(obstacle_vertices_ptr[i]); + obstruction_shape_vertices_ptrw[i].y = 0.0; + } + p_source_geometry_data->add_projected_obstruction(obstruction_shape_vertices, obstacle->get_global_position().y + p_source_geometry_data->root_node_transform.origin.y, obstacle->get_height(), obstacle->get_carve_navigation_mesh()); +} + void NavMeshGenerator3D::generator_parse_source_geometry_data(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_root_node) { List parse_nodes; @@ -741,10 +796,46 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref &projected_obstructions = p_source_geometry_data->_get_projected_obstructions(); + + // Add obstacles to the source geometry. Those will be affected by e.g. agent_radius. + if (!projected_obstructions.is_empty()) { + for (const NavigationMeshSourceGeometryData3D::ProjectedObstruction &projected_obstruction : projected_obstructions) { + if (projected_obstruction.carve) { + continue; + } + if (projected_obstruction.vertices.is_empty() || projected_obstruction.vertices.size() % 3 != 0) { + continue; + } + + const float *projected_obstruction_verts = projected_obstruction.vertices.ptr(); + const int projected_obstruction_nverts = projected_obstruction.vertices.size() / 3; + + rcMarkConvexPolyArea(&ctx, projected_obstruction_verts, projected_obstruction_nverts, projected_obstruction.elevation, projected_obstruction.elevation + projected_obstruction.height, RC_NULL_AREA, *chf); + } + } + bake_state = "Eroding walkable area..."; // step #6 ERR_FAIL_COND(!rcErodeWalkableArea(&ctx, cfg.walkableRadius, *chf)); + // Carve obstacles to the eroded geometry. Those will NOT be affected by e.g. agent_radius because that step is already done. + if (!projected_obstructions.is_empty()) { + for (const NavigationMeshSourceGeometryData3D::ProjectedObstruction &projected_obstruction : projected_obstructions) { + if (!projected_obstruction.carve) { + continue; + } + if (projected_obstruction.vertices.is_empty() || projected_obstruction.vertices.size() % 3 != 0) { + continue; + } + + const float *projected_obstruction_verts = projected_obstruction.vertices.ptr(); + const int projected_obstruction_nverts = projected_obstruction.vertices.size() / 3; + + rcMarkConvexPolyArea(&ctx, projected_obstruction_verts, projected_obstruction_nverts, projected_obstruction.elevation, projected_obstruction.elevation + projected_obstruction.height, RC_NULL_AREA, *chf); + } + } + bake_state = "Partitioning..."; // step #7 if (p_navigation_mesh->get_sample_partition_type() == NavigationMesh::SAMPLE_PARTITION_WATERSHED) { diff --git a/modules/navigation/3d/nav_mesh_generator_3d.h b/modules/navigation/3d/nav_mesh_generator_3d.h index 0251b022355..9c9b3bdefef 100644 --- a/modules/navigation/3d/nav_mesh_generator_3d.h +++ b/modules/navigation/3d/nav_mesh_generator_3d.h @@ -86,6 +86,7 @@ class NavMeshGenerator3D : public Object { #ifdef MODULE_GRIDMAP_ENABLED static void generator_parse_gridmap_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); #endif // MODULE_GRIDMAP_ENABLED + static void generator_parse_navigationobstacle_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); static bool generator_emit_callback(const Callable &p_callback); diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp index 07a39107206..3bf90249f8b 100644 --- a/scene/2d/navigation_obstacle_2d.cpp +++ b/scene/2d/navigation_obstacle_2d.cpp @@ -55,14 +55,24 @@ void NavigationObstacle2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_avoidance_layers", "layers"), &NavigationObstacle2D::set_avoidance_layers); ClassDB::bind_method(D_METHOD("get_avoidance_layers"), &NavigationObstacle2D::get_avoidance_layers); + ClassDB::bind_method(D_METHOD("set_avoidance_layer_value", "layer_number", "value"), &NavigationObstacle2D::set_avoidance_layer_value); ClassDB::bind_method(D_METHOD("get_avoidance_layer_value", "layer_number"), &NavigationObstacle2D::get_avoidance_layer_value); + ClassDB::bind_method(D_METHOD("set_affect_navigation_mesh", "enabled"), &NavigationObstacle2D::set_affect_navigation_mesh); + ClassDB::bind_method(D_METHOD("get_affect_navigation_mesh"), &NavigationObstacle2D::get_affect_navigation_mesh); + + ClassDB::bind_method(D_METHOD("set_carve_navigation_mesh", "enabled"), &NavigationObstacle2D::set_carve_navigation_mesh); + ClassDB::bind_method(D_METHOD("get_carve_navigation_mesh"), &NavigationObstacle2D::get_carve_navigation_mesh); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.0,500,0.01,suffix:px"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "vertices"), "set_vertices", "get_vertices"); + ADD_GROUP("NavigationMesh", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "affect_navigation_mesh"), "set_affect_navigation_mesh", "get_affect_navigation_mesh"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "carve_navigation_mesh"), "set_carve_navigation_mesh", "get_carve_navigation_mesh"); ADD_GROUP("Avoidance", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.0,500,0.01,suffix:px"), "set_radius", "get_radius"); - ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "vertices"), "set_vertices", "get_vertices"); ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers"); } @@ -277,6 +287,22 @@ void NavigationObstacle2D::set_velocity(const Vector2 p_velocity) { velocity_submitted = true; } +void NavigationObstacle2D::set_affect_navigation_mesh(bool p_enabled) { + affect_navigation_mesh = p_enabled; +} + +bool NavigationObstacle2D::get_affect_navigation_mesh() const { + return affect_navigation_mesh; +} + +void NavigationObstacle2D::set_carve_navigation_mesh(bool p_enabled) { + carve_navigation_mesh = p_enabled; +} + +bool NavigationObstacle2D::get_carve_navigation_mesh() const { + return carve_navigation_mesh; +} + void NavigationObstacle2D::_update_map(RID p_map) { map_current = p_map; NavigationServer2D::get_singleton()->obstacle_set_map(obstacle, p_map); diff --git a/scene/2d/navigation_obstacle_2d.h b/scene/2d/navigation_obstacle_2d.h index f9d0e277146..30328f7086e 100644 --- a/scene/2d/navigation_obstacle_2d.h +++ b/scene/2d/navigation_obstacle_2d.h @@ -54,6 +54,9 @@ class NavigationObstacle2D : public Node2D { Vector2 previous_velocity; bool velocity_submitted = false; + bool affect_navigation_mesh = false; + bool carve_navigation_mesh = false; + #ifdef DEBUG_ENABLED private: RID debug_canvas_item; @@ -97,6 +100,12 @@ public: void _avoidance_done(Vector3 p_new_velocity); // Dummy + void set_affect_navigation_mesh(bool p_enabled); + bool get_affect_navigation_mesh() const; + + void set_carve_navigation_mesh(bool p_enabled); + bool get_carve_navigation_mesh() const; + private: void _update_map(RID p_map); void _update_position(const Vector2 p_position); diff --git a/scene/3d/navigation_obstacle_3d.cpp b/scene/3d/navigation_obstacle_3d.cpp index 96f7fb137c3..f2ac8f789c9 100644 --- a/scene/3d/navigation_obstacle_3d.cpp +++ b/scene/3d/navigation_obstacle_3d.cpp @@ -63,12 +63,21 @@ void NavigationObstacle3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_use_3d_avoidance", "enabled"), &NavigationObstacle3D::set_use_3d_avoidance); ClassDB::bind_method(D_METHOD("get_use_3d_avoidance"), &NavigationObstacle3D::get_use_3d_avoidance); - ADD_GROUP("Avoidance", ""); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity"); + ClassDB::bind_method(D_METHOD("set_affect_navigation_mesh", "enabled"), &NavigationObstacle3D::set_affect_navigation_mesh); + ClassDB::bind_method(D_METHOD("get_affect_navigation_mesh"), &NavigationObstacle3D::get_affect_navigation_mesh); + + ClassDB::bind_method(D_METHOD("set_carve_navigation_mesh", "enabled"), &NavigationObstacle3D::set_carve_navigation_mesh); + ClassDB::bind_method(D_METHOD("get_carve_navigation_mesh"), &NavigationObstacle3D::get_carve_navigation_mesh); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.0,100,0.01,suffix:m"), "set_radius", "get_radius"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.0,100,0.01,suffix:m"), "set_height", "get_height"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices"), "set_vertices", "get_vertices"); + ADD_GROUP("NavigationMesh", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "affect_navigation_mesh"), "set_affect_navigation_mesh", "get_affect_navigation_mesh"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "carve_navigation_mesh"), "set_carve_navigation_mesh", "get_carve_navigation_mesh"); + ADD_GROUP("Avoidance", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity"); ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_3d_avoidance"), "set_use_3d_avoidance", "get_use_3d_avoidance"); } @@ -321,6 +330,22 @@ void NavigationObstacle3D::set_velocity(const Vector3 p_velocity) { velocity_submitted = true; } +void NavigationObstacle3D::set_affect_navigation_mesh(bool p_enabled) { + affect_navigation_mesh = p_enabled; +} + +bool NavigationObstacle3D::get_affect_navigation_mesh() const { + return affect_navigation_mesh; +} + +void NavigationObstacle3D::set_carve_navigation_mesh(bool p_enabled) { + carve_navigation_mesh = p_enabled; +} + +bool NavigationObstacle3D::get_carve_navigation_mesh() const { + return carve_navigation_mesh; +} + void NavigationObstacle3D::_update_map(RID p_map) { NavigationServer3D::get_singleton()->obstacle_set_map(obstacle, p_map); map_current = p_map; diff --git a/scene/3d/navigation_obstacle_3d.h b/scene/3d/navigation_obstacle_3d.h index 51a84c9a54a..e9a4669fa27 100644 --- a/scene/3d/navigation_obstacle_3d.h +++ b/scene/3d/navigation_obstacle_3d.h @@ -57,6 +57,9 @@ class NavigationObstacle3D : public Node3D { Vector3 previous_velocity; bool velocity_submitted = false; + bool affect_navigation_mesh = false; + bool carve_navigation_mesh = false; + #ifdef DEBUG_ENABLED RID fake_agent_radius_debug_instance; Ref fake_agent_radius_debug_mesh; @@ -108,6 +111,12 @@ public: void _avoidance_done(Vector3 p_new_velocity); // Dummy + void set_affect_navigation_mesh(bool p_enabled); + bool get_affect_navigation_mesh() const; + + void set_carve_navigation_mesh(bool p_enabled); + bool get_carve_navigation_mesh() const; + private: void _update_map(RID p_map); void _update_position(const Vector3 p_position); diff --git a/scene/resources/navigation_mesh_source_geometry_data_2d.cpp b/scene/resources/navigation_mesh_source_geometry_data_2d.cpp index 7c33aa9e388..dfa3b598c82 100644 --- a/scene/resources/navigation_mesh_source_geometry_data_2d.cpp +++ b/scene/resources/navigation_mesh_source_geometry_data_2d.cpp @@ -35,6 +35,12 @@ void NavigationMeshSourceGeometryData2D::clear() { traversable_outlines.clear(); obstruction_outlines.clear(); + clear_projected_obstructions(); +} + +void NavigationMeshSourceGeometryData2D::clear_projected_obstructions() { + RWLockWrite write_lock(geometry_rwlock); + _projected_obstructions.clear(); } void NavigationMeshSourceGeometryData2D::_set_traversable_outlines(const Vector> &p_traversable_outlines) { @@ -117,6 +123,109 @@ void NavigationMeshSourceGeometryData2D::merge(const Reftraversable_outlines); obstruction_outlines.append_array(p_other_geometry->obstruction_outlines); + + if (p_other_geometry->_projected_obstructions.size() > 0) { + RWLockWrite write_lock(geometry_rwlock); + + for (const ProjectedObstruction &other_projected_obstruction : p_other_geometry->_projected_obstructions) { + ProjectedObstruction projected_obstruction; + projected_obstruction.vertices.resize(other_projected_obstruction.vertices.size()); + + const float *other_obstruction_vertices_ptr = other_projected_obstruction.vertices.ptr(); + float *obstruction_vertices_ptrw = projected_obstruction.vertices.ptrw(); + + for (int j = 0; j < other_projected_obstruction.vertices.size(); j++) { + obstruction_vertices_ptrw[j] = other_obstruction_vertices_ptr[j]; + } + + projected_obstruction.carve = other_projected_obstruction.carve; + + _projected_obstructions.push_back(projected_obstruction); + } + } +} + +void NavigationMeshSourceGeometryData2D::add_projected_obstruction(const Vector &p_vertices, bool p_carve) { + ERR_FAIL_COND(p_vertices.size() < 2); + + ProjectedObstruction projected_obstruction; + projected_obstruction.vertices.resize(p_vertices.size() * 2); + projected_obstruction.carve = p_carve; + + float *obstruction_vertices_ptrw = projected_obstruction.vertices.ptrw(); + + int vertex_index = 0; + for (const Vector2 &vertex : p_vertices) { + obstruction_vertices_ptrw[vertex_index++] = vertex.x; + obstruction_vertices_ptrw[vertex_index++] = vertex.y; + } + + RWLockWrite write_lock(geometry_rwlock); + _projected_obstructions.push_back(projected_obstruction); +} + +void NavigationMeshSourceGeometryData2D::set_projected_obstructions(const Array &p_array) { + clear_projected_obstructions(); + + for (int i = 0; i < p_array.size(); i++) { + Dictionary data = p_array[i]; + ERR_FAIL_COND(!data.has("version")); + + uint32_t po_version = data["version"]; + + if (po_version == 1) { + ERR_FAIL_COND(!data.has("vertices")); + ERR_FAIL_COND(!data.has("carve")); + } + + ProjectedObstruction projected_obstruction; + projected_obstruction.vertices = Vector(data["vertices"]); + projected_obstruction.carve = data["carve"]; + + RWLockWrite write_lock(geometry_rwlock); + _projected_obstructions.push_back(projected_obstruction); + } +} + +Vector NavigationMeshSourceGeometryData2D::_get_projected_obstructions() const { + RWLockRead read_lock(geometry_rwlock); + return _projected_obstructions; +} + +Array NavigationMeshSourceGeometryData2D::get_projected_obstructions() const { + RWLockRead read_lock(geometry_rwlock); + + Array ret; + ret.resize(_projected_obstructions.size()); + + for (int i = 0; i < _projected_obstructions.size(); i++) { + const ProjectedObstruction &projected_obstruction = _projected_obstructions[i]; + + Dictionary data; + data["version"] = (int)ProjectedObstruction::VERSION; + data["vertices"] = projected_obstruction.vertices; + data["carve"] = projected_obstruction.carve; + + ret[i] = data; + } + + return ret; +} + +bool NavigationMeshSourceGeometryData2D::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "projected_obstructions") { + set_projected_obstructions(p_value); + return true; + } + return false; +} + +bool NavigationMeshSourceGeometryData2D::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "projected_obstructions") { + r_ret = get_projected_obstructions(); + return true; + } + return false; } void NavigationMeshSourceGeometryData2D::_bind_methods() { @@ -134,6 +243,12 @@ void NavigationMeshSourceGeometryData2D::_bind_methods() { ClassDB::bind_method(D_METHOD("merge", "other_geometry"), &NavigationMeshSourceGeometryData2D::merge); + ClassDB::bind_method(D_METHOD("add_projected_obstruction", "vertices", "carve"), &NavigationMeshSourceGeometryData2D::add_projected_obstruction); + ClassDB::bind_method(D_METHOD("clear_projected_obstructions"), &NavigationMeshSourceGeometryData2D::clear_projected_obstructions); + ClassDB::bind_method(D_METHOD("set_projected_obstructions", "projected_obstructions"), &NavigationMeshSourceGeometryData2D::set_projected_obstructions); + ClassDB::bind_method(D_METHOD("get_projected_obstructions"), &NavigationMeshSourceGeometryData2D::get_projected_obstructions); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "traversable_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_traversable_outlines", "get_traversable_outlines"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "obstruction_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_obstruction_outlines", "get_obstruction_outlines"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "projected_obstructions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_projected_obstructions", "get_projected_obstructions"); } diff --git a/scene/resources/navigation_mesh_source_geometry_data_2d.h b/scene/resources/navigation_mesh_source_geometry_data_2d.h index 4accdbc1f4a..0e321fbeb90 100644 --- a/scene/resources/navigation_mesh_source_geometry_data_2d.h +++ b/scene/resources/navigation_mesh_source_geometry_data_2d.h @@ -31,19 +31,36 @@ #ifndef NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_2D_H #define NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_2D_H +#include "core/os/rw_lock.h" #include "scene/2d/node_2d.h" #include "scene/resources/navigation_polygon.h" class NavigationMeshSourceGeometryData2D : public Resource { GDCLASS(NavigationMeshSourceGeometryData2D, Resource); + RWLock geometry_rwlock; Vector> traversable_outlines; Vector> obstruction_outlines; +public: + struct ProjectedObstruction; + +private: + Vector _projected_obstructions; + protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; static void _bind_methods(); public: + struct ProjectedObstruction { + static inline uint32_t VERSION = 1; // Increase when format changes so we can detect outdated formats and provide compatibility. + + Vector vertices; + bool carve = false; + }; + void _set_traversable_outlines(const Vector> &p_traversable_outlines); const Vector> &_get_traversable_outlines() const { return traversable_outlines; } @@ -70,6 +87,13 @@ public: bool has_data() { return traversable_outlines.size(); }; void clear(); + void clear_projected_obstructions(); + + void add_projected_obstruction(const Vector &p_vertices, bool p_carve); + Vector _get_projected_obstructions() const; + + void set_projected_obstructions(const Array &p_array); + Array get_projected_obstructions() const; void merge(const Ref &p_other_geometry); diff --git a/scene/resources/navigation_mesh_source_geometry_data_3d.cpp b/scene/resources/navigation_mesh_source_geometry_data_3d.cpp index 43fb592bba8..1bd98fe1ace 100644 --- a/scene/resources/navigation_mesh_source_geometry_data_3d.cpp +++ b/scene/resources/navigation_mesh_source_geometry_data_3d.cpp @@ -41,6 +41,12 @@ void NavigationMeshSourceGeometryData3D::set_indices(const Vector &p_indice void NavigationMeshSourceGeometryData3D::clear() { vertices.clear(); indices.clear(); + clear_projected_obstructions(); +} + +void NavigationMeshSourceGeometryData3D::clear_projected_obstructions() { + RWLockWrite write_lock(geometry_rwlock); + _projected_obstructions.clear(); } void NavigationMeshSourceGeometryData3D::_add_vertex(const Vector3 &p_vec3) { @@ -174,6 +180,121 @@ void NavigationMeshSourceGeometryData3D::merge(const Ref_projected_obstructions.size() > 0) { + RWLockWrite write_lock(geometry_rwlock); + + for (const ProjectedObstruction &other_projected_obstruction : p_other_geometry->_projected_obstructions) { + ProjectedObstruction projected_obstruction; + projected_obstruction.vertices.resize(other_projected_obstruction.vertices.size()); + + const float *other_obstruction_vertices_ptr = other_projected_obstruction.vertices.ptr(); + float *obstruction_vertices_ptrw = projected_obstruction.vertices.ptrw(); + + for (int j = 0; j < other_projected_obstruction.vertices.size(); j++) { + obstruction_vertices_ptrw[j] = other_obstruction_vertices_ptr[j]; + } + + projected_obstruction.elevation = other_projected_obstruction.elevation; + projected_obstruction.height = other_projected_obstruction.height; + projected_obstruction.carve = other_projected_obstruction.carve; + + _projected_obstructions.push_back(projected_obstruction); + } + } +} + +void NavigationMeshSourceGeometryData3D::add_projected_obstruction(const Vector &p_vertices, float p_elevation, float p_height, bool p_carve) { + ERR_FAIL_COND(p_vertices.size() < 3); + ERR_FAIL_COND(p_height < 0.0); + + ProjectedObstruction projected_obstruction; + projected_obstruction.vertices.resize(p_vertices.size() * 3); + projected_obstruction.elevation = p_elevation; + projected_obstruction.height = p_height; + projected_obstruction.carve = p_carve; + + float *obstruction_vertices_ptrw = projected_obstruction.vertices.ptrw(); + + int vertex_index = 0; + for (const Vector3 &vertex : p_vertices) { + obstruction_vertices_ptrw[vertex_index++] = vertex.x; + obstruction_vertices_ptrw[vertex_index++] = vertex.y; + obstruction_vertices_ptrw[vertex_index++] = vertex.z; + } + + RWLockWrite write_lock(geometry_rwlock); + _projected_obstructions.push_back(projected_obstruction); +} + +void NavigationMeshSourceGeometryData3D::set_projected_obstructions(const Array &p_array) { + clear_projected_obstructions(); + + for (int i = 0; i < p_array.size(); i++) { + Dictionary data = p_array[i]; + ERR_FAIL_COND(!data.has("version")); + + uint32_t po_version = data["version"]; + + if (po_version == 1) { + ERR_FAIL_COND(!data.has("vertices")); + ERR_FAIL_COND(!data.has("elevation")); + ERR_FAIL_COND(!data.has("height")); + ERR_FAIL_COND(!data.has("carve")); + } + + ProjectedObstruction projected_obstruction; + projected_obstruction.vertices = Vector(data["vertices"]); + projected_obstruction.elevation = data["elevation"]; + projected_obstruction.height = data["height"]; + projected_obstruction.carve = data["carve"]; + + RWLockWrite write_lock(geometry_rwlock); + _projected_obstructions.push_back(projected_obstruction); + } +} + +Vector NavigationMeshSourceGeometryData3D::_get_projected_obstructions() const { + RWLockRead read_lock(geometry_rwlock); + return _projected_obstructions; +} + +Array NavigationMeshSourceGeometryData3D::get_projected_obstructions() const { + RWLockRead read_lock(geometry_rwlock); + + Array ret; + ret.resize(_projected_obstructions.size()); + + for (int i = 0; i < _projected_obstructions.size(); i++) { + const ProjectedObstruction &projected_obstruction = _projected_obstructions[i]; + + Dictionary data; + data["version"] = (int)ProjectedObstruction::VERSION; + data["vertices"] = projected_obstruction.vertices; + data["elevation"] = projected_obstruction.elevation; + data["height"] = projected_obstruction.height; + data["carve"] = projected_obstruction.carve; + + ret[i] = data; + } + + return ret; +} + +bool NavigationMeshSourceGeometryData3D::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "projected_obstructions") { + set_projected_obstructions(p_value); + return true; + } + return false; +} + +bool NavigationMeshSourceGeometryData3D::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "projected_obstructions") { + r_ret = get_projected_obstructions(); + return true; + } + return false; } void NavigationMeshSourceGeometryData3D::_bind_methods() { @@ -191,6 +312,12 @@ void NavigationMeshSourceGeometryData3D::_bind_methods() { ClassDB::bind_method(D_METHOD("add_faces", "faces", "xform"), &NavigationMeshSourceGeometryData3D::add_faces); ClassDB::bind_method(D_METHOD("merge", "other_geometry"), &NavigationMeshSourceGeometryData3D::merge); + ClassDB::bind_method(D_METHOD("add_projected_obstruction", "vertices", "elevation", "height", "carve"), &NavigationMeshSourceGeometryData3D::add_projected_obstruction); + ClassDB::bind_method(D_METHOD("clear_projected_obstructions"), &NavigationMeshSourceGeometryData3D::clear_projected_obstructions); + ClassDB::bind_method(D_METHOD("set_projected_obstructions", "projected_obstructions"), &NavigationMeshSourceGeometryData3D::set_projected_obstructions); + ClassDB::bind_method(D_METHOD("get_projected_obstructions"), &NavigationMeshSourceGeometryData3D::get_projected_obstructions); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "indices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_indices", "get_indices"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "projected_obstructions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_projected_obstructions", "get_projected_obstructions"); } diff --git a/scene/resources/navigation_mesh_source_geometry_data_3d.h b/scene/resources/navigation_mesh_source_geometry_data_3d.h index 981f20a74b1..79e2f3740d6 100644 --- a/scene/resources/navigation_mesh_source_geometry_data_3d.h +++ b/scene/resources/navigation_mesh_source_geometry_data_3d.h @@ -31,15 +31,25 @@ #ifndef NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_3D_H #define NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_3D_H +#include "core/os/rw_lock.h" #include "scene/resources/mesh.h" class NavigationMeshSourceGeometryData3D : public Resource { GDCLASS(NavigationMeshSourceGeometryData3D, Resource); + RWLock geometry_rwlock; Vector vertices; Vector indices; +public: + struct ProjectedObstruction; + +private: + Vector _projected_obstructions; + protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; static void _bind_methods(); private: @@ -49,6 +59,15 @@ private: void _add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform); public: + struct ProjectedObstruction { + static inline uint32_t VERSION = 1; // Increase when format changes so we can detect outdated formats and provide compatibility. + + Vector vertices; + float elevation = 0.0; + float height = 0.0; + bool carve = false; + }; + // kept root node transform here on the geometry data // if we add this transform to all exposed functions we need to break comp on all functions later // when navmesh changes from global transform to relative to navregion @@ -63,6 +82,7 @@ public: bool has_data() { return vertices.size() && indices.size(); }; void clear(); + void clear_projected_obstructions(); void add_mesh(const Ref &p_mesh, const Transform3D &p_xform); void add_mesh_array(const Array &p_mesh_array, const Transform3D &p_xform); @@ -70,6 +90,12 @@ public: void merge(const Ref &p_other_geometry); + void add_projected_obstruction(const Vector &p_vertices, float p_elevation, float p_height, bool p_carve); + Vector _get_projected_obstructions() const; + + void set_projected_obstructions(const Array &p_array); + Array get_projected_obstructions() const; + NavigationMeshSourceGeometryData3D() {} ~NavigationMeshSourceGeometryData3D() { clear(); } };