-
-
Notifications
You must be signed in to change notification settings - Fork 21.8k
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 2D navigation mesh baking #80796
Conversation
689a2f7
to
7b15806
Compare
Been waiting for this, glad you managed to pull past the various breaking changes! Question: Does this do a bunch of polygon ops or does it do the same thing that the 3D version does and do a bunch of point casts? I'd assume it's the former based on circle edges and the fact that you're using a polygon clipping library, but if that's the case, the "cell size" parameter confuses me, as I can't imagine it'd be relevant. Also, regarding circle edge density: Have you considered making it an adjustable factor of their radius? |
It uses Clipper2 polygon ops like Union and Difference under the hood.
The Previously the cell_size was also used to upscale the arrays as is required for the rasterization of the Clipper1 lib to avoid bugs but this slowed things down considerably. After switching to Clipper2 I found no reason to do this anymore, instead found one more reason why Clipper1 is so obsolete.
The physics shapes have no way for users to change their detail level which is by default to high for navmesh baking. If users really need more edge detail for navmesh baking they should add additional shapes. Complicating the interface for everyone with properties for a niche detail that can be easily solved by adding another shape is imo not a good idea.
The |
7b15806
to
5ae35b1
Compare
Would it be very hard to consider all layers? Edit: All layers with a specific |
It is primarily a user interface issue as the parsing process does not care what it parses from the TileMap but it also can not make random assumptions how a TileMap user intends to use and mix its TileMap layers. Some users mix everything together while others use their TileMap layers very distinct and separated or with their own logic. There is no one-size-fits-all how the TileMap Layers are used and this is the entire reason this TileMap navmesh baking limitation currently exists. The issue for the TileMap was already discussed in dev chat a few times and we basically settled that the TileMap devs would take care of this problem in a later TileMap update after finishing their current reworks. This limitation also only exists for the automated parsing as you can perfectly fine create and populate your own NavigationMeshSourceGeometryData2D with the data from your TileMap that you want to add and bake from that. |
5ae35b1
to
423a839
Compare
423a839
to
31914ae
Compare
31914ae
to
c651984
Compare
I've set up some crazy experiment: using NavigationPolygon for GridMap. This is a project, that allows adding / destroying cells with navigation at runtime. I'm not using standard baking, rolled my own algorithm, that utilizes "make_polygons_from_outlines" at some stage. Sometimes it fails to detect holes / islands, say 10% of time. I tried this project with your version (with some changes - multiply cells' coordinates by 100, for example, because it can't create navigation mesh with floating-point coordinates, maybe because this version uses integer Point64 in Clipper2). Still it fails more often than with "make_polygons_from_outlines", maybe around 30% of time or even more. Sometimes it even can't handle similar cases. So now, I gave up to find the cause of error. Maybe it's in Clipper2 library, or in my algorithm (maybe need to use only positive coordinates), or in new version of navigation mesh creation. Anyway, would be great to have new mesh baking version, alongside Clipper2 in Godot! EDIT: One idea comes to mind: error is in how I detecting holes / islands: check if first point of each other outlines is in most outer polygon. It should check instead if this point is in any of each convex polygons of this outer polygon after decomposing it. Wonder if this would be an overkill to performance. And why it finds right answer in many cases then... EDIT2: Problem solved. Just have to make outlines cw / ccw according to their type (island / hole), then use add_outline function of NavigationPolygon. With your version all works 100% perfectly! |
6cbd56e
to
cf90759
Compare
Changes:
Since you added the "nav" group to both the NavigationPolygon and your main root Node2D in the scene I think what you wanted to use instead was the
Some node types had wrong xform calculations but should be fixed with the last update, please retry. |
ah, my mistake.
works now! thanks! |
As mentioned in chat as long as a polygon has a surface it should work. Be aware that precision is a thing that might go wrong sometimes so better use 2-4 pixels to have some error margin when you use very thin polygons. By default the 2D navigation mesh baking is more focused on 2D "top-down" because that is how the agent radius offset is done. It also works for 2D sideview platformers with some prep work. For 2D sideview platforms a TileMap like this can also work as long as everything is on TileMap Layer0. Just you do not draw an outline with the NavigationRegion2D. Without any outline the navigation mesh baking will parse and merge all the cell navigation polygons from the TileMap, then parses and merge all the collision polygons, then diff them so you still should have all that "empty space" for your sideview platformer while getting the agent radius offset from the obstruction so your agents dont get stuck on collision all the time. |
cf90759
to
23bee5b
Compare
Added back everyone's favorite semi-broken function The function is still deprecated and as broken as it was. It is only added back for compatibility for those projects that can not do without the old bake behavior for some reason. Everyone else enjoy the more robust NavigationServer baking. |
23bee5b
to
6cd704c
Compare
Not sure how broken was "make_polygons_from_outlines", but in my case it detects holes / islands automatically. I'd prefer to use new approach, but then I need to detect type of outlines myself like this:
|
For starters as soon as you created overlap everything would break with the old bake. Also had many edge cases where even if the user placed everything correct it would still break. As you say it detects it only in many cases, this just not enough, you can't do a project with e.g. user placed objects and runtime rebake when there is a high chance that suddenly the entire navigation mesh is gone. This is also why the new bake now merges everything first. This allows users to partially overlap their polygons without issues where the old bake would already break. Just do not fully place outlines inside each other which would again trigger the hole calculation. That the holes sometimes work even with the new bake is more an afterthought because the underlying Clipper2 tries to figure out wtf the user tried to do here with so many outlines stacked inside each other. The result is still based on luck, because a single wrong placement is enough and the entire navigation mesh flips to the wrong side and interprets a large junk of the traversable polygon surface as a hole. Clipper2 has fillrules which would allow to select different ways how it interprets the outlines but this is a really difficult concept to grasp for most users and would make the baking process not accessible so that property is hidden and it runs with NONZERO fillrule by default. Holes should preferably be added now by placing obstacle polygons, not by drawing inner outlines into other outlines for the traversable polygons. Because we merge the traversable polygons first, then merge the obstacles second, then get the diff between those two it is the far safer way to bake without stuff easily exploding in your face because a single vertex was placed on the edge of another polygon. |
Thank you for such detailed response. I agree with everything you said and will try using obstacles. Main goal in my case was to speed up navigation mesh baking for GridMap. Most performance-critical part is in building outlines. Example in documentation ("Navmesh for 3D GridMaps") is unsuitable for real-time creation / destroying of cells with navigation. In action (recorded at 30 fps): godot_nav_test.mp4WIP. Still far from what I'm trying to achieve, chunking system and initial navmesh generation using compute shader not implemented yet. |
32f2f71
to
2c71939
Compare
Adds 2D navigation mesh baking.
2c71939
to
0ee7e31
Compare
Thanks! |
Doesn't seem like enabling/disabling NavigationRegion2Ds at runtime works anymore after this. |
@wayjack Please open an issue report with a reproduction project. |
Adds 2D navigation mesh baking for
NavigationRegion2D
andNavigationServer2D
.Slight changed behavior on the baking process of the NavigationPolygon, see info further below.
Supersedes #70724.
Fixed Issues
Condition "least_cost_id == -1" is true.
Error usingNavigationAgent2D
onTileMap
forautotile
#66283 (through bake and merge step).Related older Issues
Implemented proposals
New 2D navigation mesh baking
The navigation mesh baking for 2D is accessible through
NavigationRegion2D
.The NavigationRegion2D has new buttons in the Editor for baking or clearing the current NavigationPolygon.
The NavigationRegion2D also has a new function
bake_navigation_polygon()
to bake the navigation polygon with scripts.The 2D navigation mesh baking workflow is in general kept very similar to 3D but there are few key differences.
2D requires at least 1 bounding outline that defines the outer limits of the parsed area and baked surface.
Everything inside those bounds is by default traversable surface and all parsed objects are considered obstructions.
Another, more advanced way to bake navigation meshes for 2D is available for scripts when using the
NavigationServer2D
singleton directly. TheNavigationServer2D
has new, dedicated 2D functions for parsing and baking 2D navigation meshes.parse_source_geometry_data()
can be used to only parse source geometry to a reuseable and serializableNavigationMeshSourceGeometryData2D
resource without the baking step (see description below).bake_from_source_geometry_data()
can be used to bake a NavigationPolygon from already parsed data e.g. to avoid runtime performance issues with (redundant) parsing.bake_from_source_geometry_data_async()
is the same but bakes the NavigationPolygon deferred with threads, not blocking the main thread.The source geometry parsing is implemented with dedicated parsers for each supported node type. Currently the following node parsers are added:
Note that only 2D meshes are parsed and all 3D meshes are ignored. Meshes are considered 2D when the mesh format
ARRAY_FLAG_USE_2D_VERTICES
flag is set.Note that all rounded default shapes have been hardcoded to far low amount of edges compared to their visuals.
Too many edges not only drag the bake performance down but also result in polygon "star" artifacts in the navmesh that cause pathfinding issues.
In general rounded or other overdetailed objects are not recommended to be used as source geometry for navigation mesh baking and should all be replaced by polygons with a more simplified shape. Also prefer physics collision shapes over visual polygons.
TileMap navigation mesh baking
The TileMap is a special case for 2D navigation mesh baking as it is a node that combines internally both traversable surfaces as well as obstructions. Baking a TileMap has certain limitations due to the current TileMap design.
A NavigationRegion2D can bake a navigation mesh for the entire TileMap by merging the navigation polygons and collision polygons from all used TileMap cells on the first Tilemap layer.
This full TileMap cell merge is required as it is the only reliable way to get working navigation meshes that are fitted for an agent size. It yields the best possible result for both navigation performance and quality by removing all those little TileMap cell edge seams that are responsible for a lot of pathfinding and performance problems with the TileMap build-in navigation.
NavigationMeshSourceGeometryData2D
NavigationMeshSourceGeometryData is the resulting data of a parsing operation done with the NavigationServer used for navigation mesh baking.
The advantage of having this data available in a resources is that it can be stored and loaded from disk or resued. This helps to avoid runtime performance issues with parsing operations on larger scenes. NavigationMeshSourceGeometryData makes it possibility to split the source geometry parsing process from the navigation mesh baking process. It also makes it possible to reuse the same source data to bake multiple meshes with different parameters, e.g. use one source geometry to bake navigation meshes for multiple different agent sizes.
Compatibility
The NavigationPolygon has some changed behavior now.
The
NavigationPolygons.make_polygons_from_outlines()
function is deprecated. It still works like before with all known bugs and issues. If possible consider upgrading old projects to the new baking with the NavigationServer.Existing NavigationPolygons that were already baked with
make_polygons_from_outlines()
are compatible.Existing outlines are also compatible but if baked with the new bake the result might be interpreted differently when nested "hole" outlines were used. Consider using obstacle outlines to cut holes in the traversable polygons instead of nesting outlines into other outlines which has always a chance to flip the polygon in unintended ways.
Old NavigationPolygon that have at least one outline will bake just fine with the new bake but since the NavigationPolygon by default has an agent radius of 10 pixels this will shrink new polygons. If you don't want this set the agent radius to zero to create polygons similar to the old polygons.
Internals
sync()
point to dispatch async baked navmeshes now happens each iteration just before the physics loop. The old had it as part of the physics process loop which made it unreliable and frustrating to use.Thirdparty
This pr includes thirdparty Clipper2 library as a requirement for the polygon operations without touching any of the existing Clipper1 uses in the engine.
I couldn't get any good results with the old Clipper1 as it broke far to often with edge cases or very complex polygons and was in general also way too slow for runtime navmesh baking so upgrading was imo mandatory.
Clipper1 is currently used across the engine e.g. the Geometry2D class and some Sprite ops. I added Clipper2 to the core thirdparty so it is available for the rest of the engine so all those old parts can be upgraded over time. Since I never really use those 2D functions I don't want to touch them myself.