Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NavigationObstacle options to affect navigation mesh baking #89034

Merged
merged 1 commit into from
Mar 15, 2024

Conversation

smix8
Copy link
Contributor

@smix8 smix8 commented Mar 1, 2024

Adds obstacle options to contribute to the navigation mesh baking.
Also helps with bake performance and removing unwanted navigation mesh parts.

Both NavigationObstacle2D and NavigationObstacle3D have new properties that allow the obstacle to take part in the navigation mesh baking process.

NavigationObstacle2D

obstacel2dcarve

For 2D the obstacle acts similar to normal source geometry.
The difference is that it "cuts" from the navigation mesh in a late pass after all other geometry.
It also can be made unaffected by the agent radius offset so that it "cuts" without additional margin added.

NavigationObstacle3D

obstacle _bake

For 3D the obstacle acts similar to 2D with one major difference compared to normal source geometry.

An obstacle does not add walkable geometry like collision shapes or visual meshes do, instead it acts as a discard for parts of all other source geometry inside its shape.

This can be used to remove navigation mesh from being baked in unwanted places that should not have it, e.g. inside "solid" geometry or on top of geometry.

In turn the obstacle alone can not be used to create navigation mesh when there is no other geometry around!

An obstacle does not add, it only removes. It does so by nullifying all the geometry (voxel) cells in the baking process that are inside its parsed shape.

The reason why this "remove navmesh inside geometry" is made available only on the obstacle, and not for all "solid" collision shapes or visual meshes, is that it requires a very restricted shape to be reasonibly accurate and performant to process.
Those restrictions are not possible to put in place for the many allowed uses of physics collision shapes or visual meshes.

This PR was also totally not made after seeing user navigation mesh like this:

stacked

Tbh this feature was cooking on the backburner for a while. Seeing that stuff was just what pushed it over the finishing line.

Shout out to team scott-veggie for sharing definitely-not-jotson's live struggle.

Affect the navigation mesh baking

obstacle_properties

The shape is defined with the already existing properties for height and outline vertices that were previously used only for the avoidance.

The property affect_navigation_mesh makes the obstacle contribute to the navigation mesh baking. It will be parsed or not-parsed like all other node objects in a navigation mesh baking process.

The property carve_navigation_mesh makes the shape unaffected by offsets of the baking, e.g. agent radius. It will basically act as a stencil and cut into the already offset navigation mesh surface. It will still be affected by further postprocessing of the baking process like edge simplification.

Performance

If you do not plan to use the by default enabled avoidance of the obstacle turn off enabled_avoidance to save performance.

The contributed geometry is a simple, projected shape of the obstacle vertices (top-down for 3D) or a low-detail version of the radius. This means this can be used to create comparably performance cheap projected "holes" for the navigation mesh baking.

It is one of the most efficient (but limited) ways available now to affect source geometry when baking navigation mesh. You will still need to combine it with other source geometry as the obstacle only discards.

Since the obstacle has ready-made geometry 100% stored on the CPU similar to physics shapes no parse and server-stalling related performance issues will happen. E.g. they are a regular issue when using MeshInstance3D for the source geometry when baking.

Projects with bake performance issues at runtime often should not look further than if they are using a rendering mesh as source geometry somewhere. Parsing even a single one of them at runtime can completely annihilate the frame rate because all that geometry data of a rendering mesh needs to be received from the GPU blocking the rendering.

It is recommended to use physics collision and obstacles exklusive as source geometry and replace all uses of rendering meshes when baking navigation mesh with them, at least for runtime.

Limitations and Caveats

Both the bake and carve features are still limited to the (voxel) cell resolution. It is not intended as an equivalent to more detailed boolean operations like e.g. CSG Meshes that are all super-costly in comparison.

The obstacle shape is very similar in function to an AABB except that instead of a square / box shape it uses an outline. Still, it needs to stay aligned to the horizontal xz axes and no transform like rotation or scale will be used, only the positions. This was already the case for the avoidance that has similar axis restrictions so it came naturally to use the same properties.

Do not expect the contours of the obstacle to perfectly carve into the navigation mesh. There are too many steps involved in the baking process that affect the final navigation mesh result and none of them aims for perfect accuracy. They all aim for bake performance and a low-detail polygon surface with a small edge count so the pathfinding can be fast.

For more accuracy the bake cell size and the simplification threshold can be lowered but a noticeable performance hit needs to be accepted when doing so as it creates a high (voxel) cell count that needs to be processed.

For accurate outside edges when baking, e.g. chunk navigation meshes, obstacles are not a good choice. The newer baking Rect / AABB and border_size can be used. Especially in 3D the baking AABB will also help to fix the voxel span count so navigation mesh polygon edges will not change so much when things are rebaked.

Prevent navigation mesh from appearing "inside" or "on top" of source geometry

There is also a more subtle feature added with this that many users have asked for over the years.

Since obstacle have a very restricted, plane projected shape that discards geometry they can be used to discard the entire internal geometry of the shape from the navigation mesh baking at comparably low cost. This prevents navigation mesh from appearing both inside as well as on top the source geometry.

So if you have certain areas around a level that should not have unwanted navigation mesh place an obstacle shape there.

obstacle _carve

I dont want to go into too much technical detail but this would be near impossible to do in a performant manner with visual meshes or collision shapes as source geometry. Both can end up unpredictable complex, or worse, transformed and skewed. That is also why this is such a common issue in nearly all game engines based on ReCast navmesh baking. ReCast has no concept of "inner" geometry.

With an obstacle shape the enclosed voxel cells are now discarded in the baking process so this "block navmesh from appearing in internal geometry" is now done automatically when an obstacle is baked. It still has an added cost but from tests it is nowhere as brutal as with all the other geometry options.

Adding obstacles to NavigationMeshSourceGeometryData

For those that bake navigation mesh mostly procedural in scripts the source geometry objects have new functions added.

The add_projected_obstruction() can be used to add an obstacle shape to the source geometry.

The vertices are in global coordinates by default, just watch out what you set as the root node for the parsing should it not be at origin.

The carve parameter defines if the obstacle is affected by offsets, e.g. the agent radius, when baked.

For 3D the y-axis of the vertices is ignored. Instead, the position and shape on the y-axis is controlled with the elevation and the extrusion height.

var outline = PackedVector3Array([
	Vector3(-5, 0, -5),
	Vector3(5, 0, -5),
	Vector3(5, 0, 5),
	Vector3(-5, 0, 5)
])

var navmesh = NavigationMesh.new()
var source_geometry = NavigationMeshSourceGeometryData3D.new()

NavigationServer3D.parse_source_geometry_data(navmesh, source_geometry, $MyTestRootNode)

var elevation: float = $MyTestObstacleNode.global_position.y
var height: float = 50.0
var carve: bool = true

source_geometry.add_projected_obstruction(outline, elevation, height, carve)

NavigationServer3D.bake_from_source_geometry_data(navmesh, source_geometry)

Additional background why this is added to the obstacle

One of the main reasons why this is added to the obstacle is shape restrictions but there are also other reasons why this is not just made e.g. a new node type.

The community regularly recommends to use NavigationObstacles to make agents not move somewhere unwanted.

This was and is for the most part well-meaning but wrong advice.

You can not fix any pathfinding issues with the current obstacle, the navigation mesh path will still go right through the obstacle because the navigation mesh is still there unaffected. This creates conflict with the pathfinding wanting to move inside the obstacle while the avoidance pushes back getting the agent stuck.

The attempts to fix this "wrong advice" issue with more and more documentation was more or less in vain. Nearly every new user and even more experienced long-term godot users trip over this regularly.

That the problem even exists I blame on the poor choice of the node name.

The NavigationObstacle never had any functionality outside of avoidance. In hindsight it really should have been named AvoidanceObstacle from the start because that is all it ever did and could do (before 4.1 it could not even do that really).

Now that the NavigationObstacle name can no longer easily be changed and the documentation does not help the solution is another. The obstacle gets the functionality that is the most expected from users and quickly associated due to the node name.

@smix8 smix8 force-pushed the navobstacle_bake_n_carve branch 2 times, most recently from 35621f6 to 68b7b77 Compare March 1, 2024 14:32
doc/classes/NavigationMeshSourceGeometryData2D.xml Outdated Show resolved Hide resolved
doc/classes/NavigationMeshSourceGeometryData3D.xml Outdated Show resolved Hide resolved
doc/classes/NavigationObstacle3D.xml Outdated Show resolved Hide resolved
modules/navigation/2d/nav_mesh_generator_2d.cpp Outdated Show resolved Hide resolved
modules/navigation/3d/nav_mesh_generator_3d.cpp Outdated Show resolved Hide resolved
Comment on lines 823 to 837
if (!projected_obstructions.is_empty()) {
for (const NavigationMeshSourceGeometryData3D::ProjectedObstruction &projected_obstruction : projected_obstructions) {
if (projected_obstruction.carve == false) {
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);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps we could extract a part common with the block before to a separate function to remove duplication as well

@smix8 smix8 force-pushed the navobstacle_bake_n_carve branch from 68b7b77 to 5d74d74 Compare March 7, 2024 11:48
@smix8 smix8 force-pushed the navobstacle_bake_n_carve branch from 5d74d74 to 8483d0b Compare March 9, 2024 13:01
@smix8
Copy link
Contributor Author

smix8 commented Mar 9, 2024

Added support for the recently added source geometry merge() function.

@smix8 smix8 force-pushed the navobstacle_bake_n_carve branch 3 times, most recently from 7a8dd4c to 2790a01 Compare March 11, 2024 21:04
scene/3d/navigation_obstacle_3d.cpp Outdated Show resolved Hide resolved
scene/2d/navigation_obstacle_2d.cpp Outdated Show resolved Hide resolved
modules/navigation/2d/nav_mesh_generator_2d.cpp Outdated Show resolved Hide resolved
doc/classes/NavigationObstacle2D.xml Outdated Show resolved Hide resolved
doc/classes/NavigationObstacle3D.xml Outdated Show resolved Hide resolved
doc/classes/NavigationObstacle3D.xml Outdated Show resolved Hide resolved
@jotson
Copy link
Contributor

jotson commented Mar 13, 2024

chef kiss thank you, you beauties! May all your dreams and wishes come true!

Adds NavigationObstacle options to affect and carve navigation mesh.
@smix8 smix8 force-pushed the navobstacle_bake_n_carve branch from 2790a01 to 5d5e85f Compare March 15, 2024 00:45
@akien-mga akien-mga merged commit f2fd015 into godotengine:master Mar 15, 2024
16 checks passed
@akien-mga
Copy link
Member

Thanks!

@smix8 smix8 deleted the navobstacle_bake_n_carve branch March 15, 2024 10:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants