X Tutup
Skip to content

Rework Navigation Avoidance#69988

Merged
akien-mga merged 1 commit intogodotengine:masterfrom
smix8:navigation_rvo_rework_4.x
May 10, 2023
Merged

Rework Navigation Avoidance#69988
akien-mga merged 1 commit intogodotengine:masterfrom
smix8:navigation_rvo_rework_4.x

Conversation

@smix8
Copy link
Contributor

@smix8 smix8 commented Dec 12, 2022

This PR reworks and largely fixes long-standing issue or limitations with the Navigation RVO Avoidance.
Started as an implementation of this proposal and escalated ... ups!

While build on top of two "official" RVO libraries for 2D and 3D there are a lot of modifications and custom features included to make avoidance more useful in Godot by covering more gameplay situations.

What is new?

  • Adds RVO-2D avoidance and reworks existing RVO-3D avoidance.
  • Adds static avoidance obstacles (2D avoidance).
  • Adds avoidance mask and layers, and avoidance priority.
  • Adds "layered" 2D avoidance for 3D with agent height.
  • Adds avoidance debug visuals for static obstacles and avoidance radius.
  • Adds more graceful "recovery" if avoidance simulation is broken
  • Adds automatic avoidance agent constrain to navigation mesh for NavigationRegion2D (experimental)
  • Adds script templates for common navigationagent types that extend Node/CharacterBody/Rigidbody 2D/3D (removed)
  • Tons of smaller or larger fixes and changes.

Fixed Issues

While there were many smaller issues one of the major causes of bugs in the old Godot avoidance was that Godot was updating many internal RVO values every single frame, something explicitly discouraged by the RVO library devs and even part of their manual. Many other engines solve this by locking or queuing avoidance user input for a time period which is rather awkward to use and leads to a sluggish on-rails avoidance movement. As a compromise Godot still allows most changes every frame but now by default protects some internal RVO values to keep some consistency.

Implemented proposals

Closes godotengine/godot-proposals#4522 Add RVO NavigationMesh Obstacle Planes for NavigationAgent2D/3D Avoidance (through static obstacles)
Closes godotengine/godot-proposals#1966 Improve Navigation with multiple agents (through static obstacles and avoidance layers / mask / priority)
Closes godotengine/godot-proposals#3812 Add the ability to select an obstacle or an agent as a target (through avoidance layers / mask / priority)

New RVO-2D avoidance

The RVO-2D library is optimizes for avoidance on a 2D plane and comes with static obstacle support defined by vertices.
2D avoidance does not mean it is limited to 2D games, it only means it is limited to movement in 2D space along the xy-axis, or in case of 3D games, the xz-axis. 2D avoidance is the preferred avoidance mode for any game type with normal ground movement on mostly flat surfaces for both performance and quality reasons.

rvo_obstacles2

Reworked RVO-3D avoidance

The existing RVO lib that Godot uses now restored to the original purpose of avoidance in 3D space along the xyz-axis. 3D avoidance supports (but also expects) movement in all xyz-axis for e.g. 3D space or underwater games. The old 3D implementation attempted to create a 2D avoidance by disabling the y-axis with the "ignore_y" property which caused all kinds of velocity bugs. This was replaced with a "use_3d_avoidance" property that switches the agent / obstacle between the the real 2D and 3D avoidance (on the next physics frame).

rvo_obstacles5

New static avoidance obstacles

Available for the 2D avoidance. Compared to the old obstacles that used only a radius the new obstacles can be defined by vertices to create a hard do-not-cross obstacle edge. The vertices winding order decides if the agents are pushed out or in by the obstacle. Static obstacles can be used to reliably constrain agents even in cramped situations, something that the old "soft" obstacles (that still exist) never could. The static avoidance obstacles of the default RVO2-2D library are not intended to be moved or changed after simulation start. Obviously this limitation did not work for Godot so they can be warped / rebuild when changes are made but this has a cost and should not be done every frame for complex obstacles.

rvo_obstacles11

If you need a moving and predictable obstacle use the obstacle radius and set the obstacle velocity.
If you want a reliable way to constrain agents with a static polygon shape use the obstacle vertices.

New avoidance mask and layers, and avoidance priority

Similar to physics, navigation or visuals, the avoidance has now a layers bitmask system. An agent with a matching mask bit will avoid all agents or obstacles with a matching layer bit. On top there is now a avoidance priority value on a range from 0.0 - 1.0. Agents with higher priority will act as bullies and ignore agents with lower priority even when their bitmasks match. This leads to the low priority agents making room for the high priority agents whenever possible.

rvo_obstacles9

New "layered" 2D avoidance for 3D with agent height

2D avoidance considers the new agent height property and y-axis position in 3D and is also height "layered", e.g. think of a 3D building with different floors. Previously agents would avoid each other through floors in 3D due to their often required large avoidance radius spheres. Now if an agent or obstacle position + height is below or above an agent's position + height the agent will ignore the other avoidance object automatically even if their avoidance would otherwise overlap. This also makes it possible to stack multiple 2D avoidance in 3D on different elevation levels without interfering.

rvo_obstacles6

New avoidance debug visuals

All the previously invisible properties like agent or obstacle radius now have visuals in the Editor or when a game is run with "Visible Avoidance" from the Editor Debug menu enabled. The colors of the visuals can be changed in ProjectSettings and individual debugs can be enabled / disabled (may require a Editor / Scene restart).

rvo_obstacles_debug

New graceful "recovery" if avoidance simulation is broken

Previously if users created unsolvable situations avoidance agents would get stuck and jitter in place cause Godot was resetting the simulation every frame. This was less noticeable in isolated test demos with only a few agents but in the wilds it was a regular issue in cramped situations with many agents when users forced conflicting positions or velocities updates every frame on the agents. The jitter and stuck agents made it very, very obvious for common users that something broke in the game. This made it very hard to ship a game that uses avoidance as such situations can sometimes happen due to bad luck and timing. While this broken situations can still be created now users no longer replace by default the internal velocities. This gives the simulation the ability to recover more graceful by moving all agents to new valid simulation positions while still causing no collision. In order to accomplish this the avoidance "cheats" a little on the velocities and makes agents move slightly faster until they reached their new and valid simulation position. While not perfect this makes a broken avoidance simulation far less noticeable to the untrained eye, some would even think it is part of an intended "catch-up" feature. There is no agent limit, it works with 2 agents that fight for the same position or 20000.

rvo_obstacles10

If you teleport agents to a new position use the agent_set_velocity_forced() functions on the same frame to reset the internal velocities. If you don't do that the agent will have lightning speed on the next frame. If your project requires the old, bug-ridden avoidance you can use agent_set_velocity_forced() function every frame to simulate the old avoidance behavior.

New automatic avoidance agent constrain to navmesh for NavigationRegion2D (experimental)

Automatically creates avoidance obstacles around the NavigationPolygon used by a NavigationRegion2D to constrain avoidance using agents, including one level of polygon holes. This is an experimental feature with known limitations as it shares the same issues why 2D has currently no navmesh baking which is a lack of good, automatic margin generation for the polygons. Since the navmesh edges and the created avoidance obstacles currently overlap agents can get often stuck when pushed on top the edge e.g. by physics or bad velocities.

rvo_obstacles4

Known Issues / Performance

NavigationAgent pathfollowing with avoidance
Most visible avoidance related bugs are now the result of the NavigationAgent movement logic that requires a far larger rework. E.g. current NavigationAgent movement logic continuously resets paths when to far away from the next path point. This can lead to agents getting stuck when surrounded by avoidance constrains. It can also get agents stuck in an endless loop when the target position is occupied by another avoidance agent or obstacle that the pathfinding is not aware of. Also current NavigationAgent movement logic can create situations with conflicting velocity interests that can get agents also stuck on edges when navigation mesh edges and avoidance obstacle edges overlap.

Static obstacles update performance
The avoidance world is currently rebuilding all static obstacles when a single static obstacle is changed cause each obstacle holds ref to some of it's neighbors which can be costly at runtime. The original RVO-2D library did not support editing static obstacles at all at runtime so this is an evolving custom feature with a lot of room to optimize in a follow-up.

Conversions between Godot and RVO
There is currently a lot of back and forth conversions and copy between Godot and the RVO libraries, e.g. stuffing everything into std::vectors or converting Godot Vector2/3 to RVO vectors back and forth. If the core avoidance functions from the libraries would be extracted and integrated directly in Godot to work with native Godot types performance could possibly be improved considerably in a follow-up.

TO DO

  • EditorPlugins are wip and a last second addition. They are functional to draw obstacle vertices but other parts like proper handle or vertex edit are unfinished. There is for now some intentional leftover code from copy&paste from the CollisionPolygon3D plugin.
  • Correctly credit the third-party libs and create working "patches" (no idea really how).
  • Probably forgot to mention half the new / changed stuff.
  • Book a long holiday after this ....

Test Project

This project includes some of the more salvageable pieces and 3D demo scenes.
RVOTestProject4.zip

@smix8 smix8 requested review from a team as code owners December 12, 2022 22:05
@smix8 smix8 requested review from a team December 12, 2022 22:05
@smix8 smix8 requested a review from a team as a code owner December 12, 2022 22:05
@YuriSizov YuriSizov added this to the 4.0 milestone Dec 12, 2022
@YuriSizov
Copy link
Contributor

YuriSizov commented Dec 12, 2022

Amazing! Would you mind sharing your test projects, so we could record high quality clips for future blogposts?

@smix8 smix8 force-pushed the navigation_rvo_rework_4.x branch 4 times, most recently from afc68ea to e97f8ea Compare December 18, 2022 02:23
@smix8 smix8 force-pushed the navigation_rvo_rework_4.x branch 4 times, most recently from 3c3daa1 to bd3790d Compare December 22, 2022 10:09
@smix8 smix8 force-pushed the navigation_rvo_rework_4.x branch from bd3790d to 11794b0 Compare January 4, 2023 16:55
@smix8 smix8 force-pushed the navigation_rvo_rework_4.x branch 4 times, most recently from 9667c18 to 7947101 Compare January 5, 2023 21:54
@viksl
Copy link
Contributor

viksl commented Feb 21, 2023

Hi, sorry to bother but would it be possible to add an overload to region_bake_navigation_mesh method in the NavigationServer which would take an array of nodes instead of a root_node, please?
My case is, I'm trying to make multiple maps which overlap which means it's necessary to reparent the nodes everytime I need to rebake the NavigationMesh which belong to different overlapping maps, with lots of objects this could potentially mean thousands of nodes needing reparenting and that can take a lot of time freezing the frame for a bit or it's necessary to split it into multiple frames which isn't ideal either.
I like the idea of the root node I'm just curious if it would be possible to extend it and make it possible to just give it an array of nodes to bake the navmesh from?

I hope I made myself clear but if it's difficult to understand my question please let me know. :-).

@smix8
Copy link
Contributor Author

smix8 commented Feb 21, 2023

@viksl I dont think this has anything to do with this pr that is about avoidance. The function region_bake_navigation_mesh will be removed with the NavigationMeshGenerator rework #70724 as it was already only a proxy for NavigationMeshGenerator.bake(). You don't need to reparent anything as you can already work with explicit groupnames to filter which nodes to parse for source geometry. The generator rework will include a reusable source geometry resource for baking or you can add even a custom geometry parser. You can discuss it in the linked generator pr or if it does not solve your requirement make a dedicated post / feature proposal.

@viksl
Copy link
Contributor

viksl commented Feb 21, 2023

@smix8 yeah, I'm sorry about that, I was going through various nav pages on github and didn't realize I was in a wrong page. I apogokize. Do you want me to delete it?
Also thanks for pointing it out.

@smix8 smix8 force-pushed the navigation_rvo_rework_4.x branch 2 times, most recently from b16ec51 to 7133d99 Compare March 4, 2023 20:20
@smix8
Copy link
Contributor Author

smix8 commented Mar 4, 2023

Added experimental option to clamp the callback velocity to max_speed.

This will result in more agent congestion in cramped situations but the velocity from callbacks will never exceed the agent max_speed.

@smix8 smix8 force-pushed the navigation_rvo_rework_4.x branch 2 times, most recently from 6e74a03 to d7935ae Compare March 14, 2023 07:43
@smix8 smix8 force-pushed the navigation_rvo_rework_4.x branch from d7935ae to 2361aaa Compare March 22, 2023 00:04
@smix8 smix8 force-pushed the navigation_rvo_rework_4.x branch 2 times, most recently from d695e29 to 9c981af Compare April 3, 2023 10:32
@akien-mga
Copy link
Member

CC @DarkKilauea @Scony @lawnjelly for review.
This is a big chunk of work, and warrants both code review and testing to make sure it's in a good state to merge. Probably doesn't have to be perfect since there will still be time to polish things for 4.1, especially after user testing in the first betas.

@smix8 smix8 force-pushed the navigation_rvo_rework_4.x branch from 9c981af to 38bec0f Compare April 3, 2023 15:06
@smix8
Copy link
Contributor Author

smix8 commented Apr 3, 2023

I added a zip with a small test project with some of the more salvageable pieces from my original project, mostly 3D test scenes.

@smix8 smix8 force-pushed the navigation_rvo_rework_4.x branch from 38bec0f to f0c2ef5 Compare April 7, 2023 13:26
Copy link
Contributor

@MewPurPur MewPurPur left a comment

Choose a reason for hiding this comment

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

Took a look at the descriptions at the top.

Rework Navigation Avoidance.
Copy link
Member

@akien-mga akien-mga left a comment

Choose a reason for hiding this comment

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

Only did a cursory review, but it's time to get this merged and tested more widely.

The changes made to RVO-2D and RVO-3D probably mean that we're fully divorcing from upstream and won't aim to update those libraries further, or if we do it will have to be done manually by redoing relevant upstream fixes on top of our modified code.

@akien-mga
Copy link
Member

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
X Tutup