NavigationServer2D baking creates mesh, but agent can't find a path

Godot Version

4.2.1.stable

Question

When I use NavigationServer2D.bake_from_source_geometry_data_async the mesh appears on screen (I have navigation selected in the debug menu), but the agent won’t find a path. When I use NavigationRegion2D.bake_navigation_polygon() the path appears and the agent can find the path. From my reading these should both work, or at least if NavigationServer2D.bake_from_source_geometry_data_async didn’t work I wouldn’t expect it to show the debug navigation mesh.

I’m just trying to figure out why these are different and if there are docs about how they’re different or why I might be seeing this behavior.

I set up a quick example project to demonstrate what I’m seeing: https://github.com/ToxicGLaDOS/minimal_navigation_reproduction. Make sure to enable visible navigation in Debug > Visible Navigation. Press 1 to bake with NavigationRegion2D.bake_navigation_polygon, and press 2 to bake with NavigationServer2D.bake_from_source_geometry_data_async.

Additionally, clicking the bake button on the NavigationRegion2D node also works as expected.

Interestingly, baking with NavigationRegion2D.bake_navigation_polygon and then baking with NavigationServer2D.bake_from_source_geometry_data_async afterward maintains correct functionality.

1 Like

change the navigation.gd to this:

extends CharacterBody2D

@export var nav_agent: NavigationAgent2D
@export var speed: float
@export var nav_region: NavigationRegion2D

func _ready():
# call_deferred ensures that we wait until the
# navigation server is ready before trying to
# get a path to the target
	call_deferred("set_target")

func set_target():
	nav_agent.set_target_position(Vector2(100, 100))

func _process(_delta):
	if Input.is_action_just_pressed("bake_working"):
		bake_working()
	elif Input.is_action_just_pressed("bake_broken"):
		bake_broken()

func bake_working():
	var main_outline = nav_region.navigation_polygon.get_outline(0)
	nav_region.navigation_polygon.clear_outlines()
	nav_region.navigation_polygon.add_outline(main_outline)
	nav_region.bake_navigation_polygon()

var nav_data

func bake_broken():
	parse_source_geometry.call_deferred()

func parse_source_geometry() -> void:
	nav_data = NavigationMeshSourceGeometryData2D.new()
	NavigationServer2D.parse_source_geometry_data(
		nav_region.navigation_polygon, nav_data, nav_region,
		on_parsing_done
	)

func on_parsing_done() -> void:
	NavigationServer2D.bake_from_source_geometry_data_async(
		nav_region.navigation_polygon, nav_data,
		_post_baking
	)

func _physics_process(_delta):
	if nav_agent.is_navigation_finished():
		return

	var next_path_position: Vector2 = nav_agent.get_next_path_position()
	var new_velocity: Vector2 = global_position.direction_to(next_path_position) * speed
	velocity = new_velocity
	move_and_slide()

func _post_baking():
	nav_region.navigation_polygon=nav_region.navigation_polygon
	nav_region.queue_redraw()


func _on_path_changed():
	pass # Replace with function body.

func _on_navigation_finished():
	print('finshed')

tested working on button 1 or 2 now

A NavigationRegion node is a functionality bundle that does a lot of unasked stuff automatically for users. E.g. setting the changed navigation mesh after the bake on both the node and the server region as an update.

When you parse and bake navigation mesh with the NavigationServer functions you do exactly only that, and not a single step extra. So in this case you need to both manually update your node or server region (if you update the node it will update the server region).

The 2D debug uses canvas draw which is deferred, so if something else triggers it it will draw with the updated navigation mesh data in the end so you see your server baked region update even if that specific update did not trigger the debug draw.

I’m not sure I understand what you mean by “update your node or server region”. Is there a specific function I call to do that, or just make any change like

nav_region.navigation_polygon=nav_region.navigation_polygon

from @zdrmlpzdrmlp’s answer.

If it is just any change, is a no-op change like that the canonical way to trigger an update?

i found that line to be funny, because it needs to reassign with new one which is by itself after the baking is done to make it work

actually you can read how to use the NavigationServer2D.bake_from_source_geometry_data_async
from here, but you soon will realize it’s meant to be used with RID and not from created NavigationRegion2D node. so it’s fully NavigationServer2D side

I feel like I understand how to do it now, but I’m still fuzzy on the details. Isn’t

nav_region.navigation_polygon=nav_region.navigation_polygon

a no-op? At best it makes the node perform an update (and maybe that is the key?)

In the docs you linked they use NavigationServer2D.region_set_navigation_polygon(region_rid, navigation_mesh) which makes more sense because navigation_mesh isn’t already linked to a region. But I would think that in my example nav_region.navigation_polygon is already linked to nav_region and updates to the navigation_polygon would be reflected immediately.

My understanding is that bake_from_source_geometry_data updates the NavigationPolygon that you pass in. So if I pass in nav_region.navigation_polygon, I would expect that to update the mesh without a need to call NavigationServer2D.region_set_navigation_polygon or nav_region.navigation_polygon = .... This kind of feels like a pass-by-value vs. pass-by-reference thing, is that what I’m not getting?

i found why
because of this:

nav_region.navigation_polygon=nav_region.navigation_polygon

this above line actually trying to set the navigation_polygon again, which trigger the NavigationServer2D’s region_set_navigation_polygon method
which is also what the documentation have done after baking:


hence why it works

this line

nav_region.navigation_polygon=nav_region.navigation_polygon

is equal to:

nav_region.set_navigation_polygon(nav_region.navigation_polygon)

what set_navigation_polygon actually does behind the hood:

Huh… I would have thought that setting the navigation_polygon of a NavigationRegion2D in the editor would set the region of the NavigationRegion2D to the navigation_polygon (as if you’d called region_set_navigation_polygon), but maybe that’s not true?

Because if it were already set up then we’d be calling NavigationServer2D::get_singleton(...)->region_set_navigation_polygon with the polygon that’s already set for that region right?

it’s not, because this NavigationServer2D.bake_from_source_geometry_data_async is creating/baking new mesh, and you want it to update to the navigation region2d you already have after it done baking

But we pass nav_region.navigation_polygon to NavigationServer2D.bake_from_source_geometry_data_async. So any updates to it should be reflected immediately. I’m not familiar with the Godot codebase, but I found this:

which to me says that we pass in the navigation polygon by reference and set the vertices on it. Because it’s a Ref<> I would expect that to be visible to the NavigationRegion2D as soon as the vertices are set. No need for any updating, because we aren’t creating a new NavigationPolygon object, we’re just updating the one we passed in as nav_region.navigation_polygon. There must be something I’m missing here, but I just can’t seem to figure it out :sweat_smile:

EDIT: Maybe these lines are slightly more relevant, but it’s more or less the same godot/modules/navigation/nav_mesh_generator_2d.cpp at b09f793f564a6c95dc76acc654b390e68441bd01 · godotengine/godot · GitHub

after searching, the Ref is Ref from Eigen.

I was assuming that the Ref<> template type was implemented similarly to a smart_pointer. We’re passing a Ref<NavigationPolygon> by value for sure, but the underlying NavigationPolygon is the same because it’s wrapped in the Ref. I can’t seem to find the implementation of the Ref class, so that’s just an assumption. But if that’s not the case then how do we get the value back out? Typically if you pass by value I’d expect a return value so we can access the new object, but here we return void, so there has to be something going on behind that Ref<>.

it’s stored in navigation server 2d, duh.

I don’t see anything that stores the mesh once you call NavigationServer2D.bake_from_source_geometry_data_async. Seems to me, all the operations are on the passed in NavigationPolygon :person_shrugging:

well yes, it actually stored as resource, and called as RID, that made you search whole Navigation Server 2d.cpp?

it actually stored as resource, and called as RID

Where exactly?

that made you search whole Navigation Server 2d.cpp?

I’m not sure what you mean exactly, but I started here

and tried to follow the path until I got to here

and didn’t see anything that looked like it would store a RID.

@smix8 explain how it works if you felt like you have to, i see you made this navigation :muscle:

@toxicglados might actually try to enhance your navigation code, he wondered why the navigation polygon doesn’t get updated directly upon baking done