-
-
Notifications
You must be signed in to change notification settings - Fork 253
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
Navmesh for Non-Blocky Voxel (Lod) Terrain #610
Comments
*TLDR is that I have to start investigating this eventually, just can't tell when. Will mark the issue as "enhancement" to have an explicit one tracking the state of it. What you describe isn't really new to what I was thinking so far, the same problems remain in terms of actually doing these things:
One thing however, is that it's true I haven't looked in more details for this, as I've been focused on other things. I'm working on this engine on free time, so priorities are partly what people ask, but also partly what I want to do. Maybe it's simpler than I realize (putting aside the point I listed earlier). I imagine I could start with doing "a thing" after each chunk mesh loads/updates/unloads. Chunked navigation is a go-to I think. But right now it's mostly speculations, not much actionable yet. For now I guess the first step is, I have to start looking into it, but again can't tell when that will happen yet. |
Oh wow, thank you for responding so quickly lol I admit that I've put a lot of weight on Godot's Navigation system, as if it were a perfect system (it is not). However having a system that outputs a
The point about the "LOD" you brought up is also very important, however, an AI in Godot will just simply travel to the closest point to its objective within the navigation mesh (So it won't stay still even if the target is very far away from the navigation mesh, unlike Unity), a very "low detail" version for a far away navmesh could still be useful for the AI to evade "A* traps" (As the AI's path also updates slightly with every point it crosses). One of the things you mentioned in the second to last point, having a To answer one of the final questions, a I'll probably just end up taking a look at how Godot actually bakes a NavigationMesh, and maybe see what parts of it could be useful for Voxel navmeshes. Again, I'm still currently getting comfortable with C++, so excuse me for having a "naive" view of things lol. |
I also just thought about yet another annoyance with chunked navmesh: Let's say you just use individual chunk meshes to make a navmesh. That will work if you consider just one mesh, but then what if that chunk represents only the floor of a very narrow cave? Because it's voxel terrain, it could mean the chunk above it (which might not even be ready yet!) contains a ceiling that's too close for the agent, meaning the navmesh will be incorrect. Same problem with walls on side and below chunks, and same problem if neighbor chunks contain some obstacle spawning on top. And then if you add neighbor chunks into a bake, it must still only produce a navmesh within that chunk, and not extend further just because the neighbors are provided to the baker. Not sure if there are APIs to limit this. And this is even more problematic when we consider that meshes themselves need neighbor voxel chunks to even be produced. The end result being, That meshes currently appear only inside an inset-by-1-chunk region of voxels, and then navmesh chunks would end up themselves as an inset-by-1-chunk region of meshes... (on top of that, voxel chunks can have a different size than meshes!). Checking 26 neighbors volumetrically in all space for various things is one area of the engine that can can be relatively expensive. It's happening so often with voxel chunks that I'm considering to alter the data structure to trade memory usage for speed, but still not the panacea. There are less mesh chunks than voxel chunks tho (if we ignore empty ones). People often underestimate the sheer amount of shortcuts that just don't work with streaming voxel terrain^^" Also I guess this is one more place to reference this proposal: godotengine/godot-proposals#5138 |
I spent some time making a prototype of automatic navmesh baker for This is a new node which for now I called I intentionally chose 3D noise here because it creates overhangs and kinda stress-tests the navigation system, which we really need to be robust enough to handle all the crazyness that can exist on such procedural/player-editable terrains, without crashing or breaking somehow. I havent tried using an agent to pathfind on the resulting navmesh for now. "Sync" problemUnfortunately, things are in a rough state, mainly because of this incredibly annoying error that keeps getting thrown by Godot:
It doesn't always occur, but as terrain generates, it happens really quickly. I'm already pretty sure meshes are reasonably manifold ("reasonably" because I didnt run a definite test on it, but from having looked at Transvoxel meshes for a while, I can say they are devoid of self-intersections).
See godotengine/godot#85548 for more details. I can't really be sure if all points mentionned to use I even wonder if the error is actually a big problem or something that could be tolerated to some degree. Either way, even if it were to happen due to terrain being too harsh in game, it should not be something we have to constrain manually up-front somehow (because the world is not pre-authored!) and then crossing fingers hoping it doesnt happen when players generate and edit new terrain... there has to be another way to deal with it at runtime with minimal cost, but for now I don't what. Transvoxel derives from marching cubes, which naturally produce triangles that can be very thin. I have no idea whether that has an impact or not here. That's why I though of trying meshoptimizer to "clean" it up, but that didn't help. Besides, simplifying the mesh has some downsides (lower visual quality, much longer meshing time, and trouble getting perfectly-matching cell triangles when using detail rendering, requiring to keep more memory allocated with the non-simplified meshes). Regarding the editorwhile the node also bakes inside the editor and uses a
BranchI pushed my WIP to the |
It currently is because I havent optimized anything. The main parts that take time are:
Also, I chose a single-navmesh approach for now because it is A LOT easier to do. Chunking would be ideal in theory, but from what I've seen so far in my experiment, it brings up tons of problems and even makes certain things worse, so I haven't figured it out.
This is unfortunate, and I'm afraid this will keep happening even if chunking is implemented. I think that's a Godot issue. Godot should keep the last path it was using and eventually re-path without interrupting agents, otherwise it defeats any attempts at dynamically updating the navmesh. With chunks you could do any edit in a chunk agents are going through and that would bother them as well, which is dumb.
Not much the voxel engine can do about this, you have to tweak navmesh settings and the way your agent follows its path maybe. I tried some chunking today on a separate project without the voxel engine. I used a very simple heightmap terrain divided in chunks and one region per chunk. I guess I eventually got it to a working-ish state, but it was awful:
Sorry I got no screenshot of that project as I'm on another computer.
As you might have figured out from my first experiment, that means instead of gathering everything once, gathering will have to run for every chunk AND its theoretical 26 neighbors, overall resulting in much more work done by the CPU, due to redundancy of data and lookups. Though it's of course true that incremental changes are then cheaper.
As seen in my experiment, I'm not sure of the relevance of manually filtering triangles that are not within the baking AABB. The baker can already do it for us, the downside is there is a lot more data to fetch from the gathering phase because Godot wants a single big list of vertices and indices. So I don't know what will turn out to be faster.
In my experiment I did this with multiple regions, so I had multiple navmeshes, I didnt try to make a single one. Though as mentionned, there are weird problems I dont know how to get rid of.
Not sure if that's a good idea given the API we have access to. Also I don't know if that's doable efficiently. Navmesh polygons in each chunk should line up and therefore be detected by the navigation map connection logic. That said, I don't know how fast that system is (one more thing the single-navmesh approach doesnt need hehe) |
I feel slow seeing the progress you're doing ^^'
Going back to this point, what happens is that the NavigationMesh visibly changes whenever it updates, so with a chunking method it wouldn't happen as often, or at least not cause it to get stuck.
Thick magenta lines means that the two navmeshes are considered as "Connected" automatically by Godot, which is one benefit of having each "Chunk" have its own region, as basically Godot does the stitching for us, I do wonder if it would actually be better or worse, I imagine it would end up being more memory intensive than just having one Region. |
If that's so, then it's spectacularly bad, as very few polygons were connected, despite almost all of them were visually touching each other... (excluding the few ones that unexpectedly deviated) |
Yes, it is bad, cause it can only connect if:
I actually considered suggesting the idea of having one Region per block/chunk, but then figured that might've been a "naive" solution, though at the same time, it would get rid of having to check what polygon should be merged or created. |
I've been working on a navigation solution for my game using one region per chunk, and I think it's workable. There are some difficulties - in particular, you have to turn off 'Use Edge Connections', which means that it can only connect an edge is both vertices that define the edge are in the same spots, and it's still a little slower than I'd like. I added a proposal for Godot to make adding new nav regions more efficient, and I think it'll get added eventually: godotengine/godot-proposals#9381 You can see my current effort here, written entirely in GDScript. It does greedy meshing for the nav mesh atm, which doesn't actually work, but it at least demonstrates the performance is manageable: https://gitlab.com/AngularAngel/omnicraft-infinite-war/-/blob/master/Scenes/Game/NavigationGenerator.gd?ref_type=heads |
I've done some more investigation regarding the sync error: godotengine/godot#85548 (comment) |
I tried out Zylanns current experiment, and it works great! Other than the lag spikes on generating new chunks, anyway. Oh, no sync errors for me, either. No idea what the deal with those is. Though actually, now that I think of it, I occasionally got sync errors with my own solution? So, maybe that's just something that happens when working with nav meshes. :/ |
Your terrain is blocky, so maybe that plays a role here. What I'm testing with is a relatively intense smooth terrain. |
I noticed that while I can thread most of the baking, Godot's Sadly it seems there is nothing to run that on a thread. It runs directly in the middle of physics process, it's just going to stall all the time for every change we make to a navmesh (chunked or not): (one extreme workaround that starts flying in my head is to implement a whole navigation query system entirely in the voxel module to be better tailored to streaming procedural open-worlds, and completely skip Godot's regions system... but that would be really, really sad) |
Another wonderful thing: Update: apparently it's possible to call the synchronous bake function from a custom thread. So we may have to do that if the editor case is too much of a problem. Also, Update 2, regarding chunking: apparently using the border option is preferable instead of padding the AABB (see docs). Recast will make sure the edges match, without the need to use Godot's sync margins. Also, that option might also enable us to pass only the relevant chunk to baking, simplifying the gathering process. It could even allow us to bake from the meshing tasks themselves. Current state as of 2024/04/11: |
Quick update: Godot just merged a PR that allows plugins to register custom navmesh geometry sources: godotengine/godot#90876 However this is unfortunately not well-suited with the kind of terrain the voxel module generates, unless maybe you want to bake a fixed navmesh in the editor and never update it at runtime. So it's not really "missing" it. I suppose it would work if used at runtime, however the general-purpose logic Godot does to gather geometry for the baker is IMO not scalable enough to be done frequently at runtime in a large 3D procedural streaming environment. What I've done already in the My thoughts about why the new Godot API is not suitable for runtime with this module:
Regarding the I also added a doc page: https://voxel-tools.readthedocs.io/en/latest/navigation/ |
Is your feature request related to a problem? Please describe.
It's rather difficult to consider having any kind of in-game AI when you're unable to pathfind with it, a basic, even if rudimentary, Navmesh system for non-blocky Voxel Terrain probably has probably been long overdue and from what I've been able to research, there hasn't been particular progress as to making one.
Describe the solution you'd like
I've done a fair amount of research and considered one way of doing things that maybe would be acceptable in performance.
(Though I'm still unexperienced with both C++ and Godot Module development, so pardon if parts of this solution are not possible)
We begin when the Voxel Engine has just created the physics mesh (Or at the same time, using the same source) for a block in the terrain, we take that mesh and hand it to Godot, either to NavigationServer3D's
bake_from_source_geometry_data
, its async variant or with NavigationMeshGenerator's.(This is assuming that calling these Godot methods are viable/performant)
Doing this we would get a
NavigationMesh
for this block of terrain but it wouldn't quite work just yet because of howagent_radius
works. There's going to be a small margin to the borders of the physics mesh (where it would connect to other voxel blocks) so we will have to fix it.We probably would save this result somewhere, so when the terrain is updated only a single block is parsed at a time instead of the whole terrain.
Finally, the navmesh is stitched to the other navmeshes, making a single one. We're assuming that there would only be one
NavigationRegion3D
or equivalent, thus, the feature it has for "automatically merging navmeshes with other regions" will not apply as this is all going to be inside a single region, additionally, overlapping navmeshes are very unpredictable in behavior, so merging is required.In an ideal world, perhaps this merging step can be done in a similar fashion it is done when making the visual or physics mesh but maybe it will be necessary to make its own implementation for navmeshes.
And this is still mostly a "barebones" kind of implementation, as all the Navmesh would be:
But we've gotta start somewhere.
Describe alternatives you've considered
Firstly, I tried the obvious, try to use
NavigationRegion3D
on aVoxelLodTerrain
... but it literally gave me nothing, then I've read other issues that talked about Navigation on non-blocky terrain here, but most of the "solutions" I've seen were either based on mostly static terrain, baked the entire terrain each time it was called (In addition of using 2 plugins) or finally requires the user to just "figure it out", while barely having the tools to properly have access to all of the critical voxel data to make a navigation mesh of some sort (And most of these issues are at least 2-3 years old).Additional context
While I'm still learning C++, any indication of where I could search within the source code of the module to make a prototype of some sort would be appreciated. In addition of any observations/critiques/etc.
This is a great module for Godot, and I truly want to make some projects with it, but without any kind of Navigation for AI, it's losing out a lot.
The text was updated successfully, but these errors were encountered: