JOB system RVO multi floor nav + avoiding walls


I finally got the Job version of RVO working with my entities and need help clarifying a few things:

1- I just realized the y coordinate coming out of the simulator is always 0. So I checked the 3D scene. Am I correct to assume all you’re doing to handle 3D is ground the Y of every agent each frame? (The raycast in ApplyMove()).

2- I’m also assuming this means units will try to avoid each other, even if on different floors?

3- I’m having issues with entities being pushed out of the mesh. If I remember correctly, RichAI handles it by telling the RVOController when it hits a wall or gets off the mesh, but since I use the JOB version with thousands of entities, I skipped the controller and use the simulator directly (was that a bad idea?) and I’m a bit confused as to what still works and which code to look at for guidance.

Thank you and get well soon!

Just realized there are parameters I’m not using in the RVO agent, especially the LAYER… how could I miss that. Do all those parameters still work in the JOB version? And do you have auto layer changes implemented anywhere?

Sorry for the late reply.

That is incorrect. The RVO system tracks the Y coordinate of the agents as well. It treats the agents as cylinders, and makes them avoid each other if the cylinders overlap at any coordinate along the Y axis. Just make sure you set the position of the agents correctly.
Modifying the layer field is not necessary for this.

  1. Yes, See (1)

  2. If you have information that the agents have collided with a wall, you can use rvoAgent.SetCollisionNormal to give the simulator that information. It will prevent the agent from moving further into the wall.

No problem, I got around my Y coordinate issue by combining the Y from my ground check, but it’s good to know it’s not supposed to be 0. I have some checking to do.

A few other questions came up:

1-What’s the best way to check if thousands of units are inside or outside the nav mesh? And when units get out of the navmesh do I still inform the RVO sim using rvoAgent.SetCollisionNormal ? (FYI I use the beta / job version of RVO)

2-Since I’m not using your scripts and the RVO Controller, and create RVOAgents manually, is there anything I should know or may have missed about these values that should be set? IAgent - A* Pathfinding Project

3̶-I̶’̶m̶ ̶a̶s̶k̶i̶n̶g̶ ̶b̶e̶c̶a̶u̶s̶e̶ ̶I̶’̶m̶ ̶t̶r̶y̶i̶n̶g̶ ̶t̶o̶ ̶a̶d̶j̶u̶s̶t̶ ̶t̶h̶e̶ ̶b̶e̶h̶a̶v̶i̶o̶u̶r̶,̶ ̶I̶ ̶w̶o̶u̶l̶d̶ ̶l̶i̶k̶e̶ ̶t̶h̶e̶m̶ ̶t̶o̶ ̶b̶u̶n̶c̶h̶ ̶u̶p̶ ̶o̶n̶ ̶t̶h̶e̶ ̶t̶a̶r̶g̶e̶t̶ ̶a̶n̶d̶ ̶a̶c̶t̶ ̶m̶o̶r̶e̶ ̶l̶i̶k̶e̶ ̶a̶ ̶b̶l̶o̶b̶ ̶a̶t̶ ̶c̶l̶o̶s̶e̶ ̶r̶a̶n̶g̶e̶ ̶t̶h̶a̶n̶ ̶m̶o̶t̶h̶s̶ ̶a̶r̶o̶u̶n̶d̶ ̶a̶ ̶l̶i̶g̶h̶t̶b̶u̶l̶b̶.̶ ̶I̶ ̶r̶e̶a̶d̶ ̶s̶o̶m̶e̶t̶h̶i̶n̶g̶ ̶i̶n̶ ̶y̶o̶u̶r̶ ̶c̶o̶d̶e̶ ̶(̶c̶o̶m̶m̶e̶n̶t̶s̶)̶ ̶a̶b̶o̶u̶t̶ ̶a̶d̶j̶u̶s̶t̶i̶n̶g̶ ̶t̶h̶e̶ ̶b̶e̶h̶a̶v̶i̶o̶u̶r̶ ̶o̶f̶ ̶t̶h̶e̶ ̶a̶g̶e̶n̶t̶s̶,̶ ̶b̶u̶t̶ ̶c̶o̶u̶l̶d̶n̶’̶t̶ ̶f̶i̶n̶d̶ ̶d̶o̶c̶u̶m̶e̶n̶t̶a̶t̶i̶o̶n̶ ̶o̶n̶ ̶i̶t̶.̶

3- (update) I just found RVODestinationCrowdedBehavior is there documentation on how to implement it?

Thank you.


Sorry for the late reply. I’ve been quite sick these last few weeks.

The fastest way is probably to run var node = and keep track of the node (if the agent is outside the navmesh the node will be null). The next frame you can do a faster check by checking node.ContainsPoint(myPosition) and only if that returns false you do a full PointOnNavmesh check. Caching the node the agent is inside will most likely give you a huge performance boost compared to doing a full lookup every frame. If the agent is outside you can look for the direction to the closest point on the navmesh, and use SetCollisionNormal to ensure the agent at least doesn’t move further away from the navmesh.

That’s hard to say… It’s a very vague question. Nothing in particular comes to mind.

Not really. That behavior is pretty experimental (and doesn’t work perfectly all the time). You can check how the AIPath script uses it.

Hi Aron, thank you for the last reply, your help is invaluable as always!

1 Like

Hi, I’m still fighting this thing. Would you please confirm if I’m doing things properly and in the right order? I tried so many things and read so much code that I can’t think straight :exploding_head:


  • I’m using the simulator burst directly for performance reasons
  • Whatever Y value is set in step 1 is the one I always get in step 5 from simulationData.position[index]
  • Writing this, I’m wondering if the order is wrong, if It is ok to bypass the RVOAgents
  • I’m not using movementPlane.ToPlane and .ToWorld, not sure why I’d want to apply the plane’s rotation. Seems to work without it. Also, in RVOController.CalculateMovementDelta when using ToWorld the elevation is set to 0? I’m missing something here.

Here is how I use the simulation burst:

  1. Create agents during Start()


  1. Run a SetupAgent job as a IJobParallelFor still in Start():

simulationData.radius[index] = radius;
simulationData.agentTimeHorizon[index] = agentTimeHorizon;
simulationData.obstacleTimeHorizon[index] = obstacleTimeHorizon;
simulationData.maxNeighbours[index] = maxNeighbours;
simulationData.debugDraw[index] = index == 0 && debug;

  1. Set the move targets during Update() in a for loop:

If (waypointChanged[index]) {
targetPoint = (directionToWaypoint.normalized * remainingPathLength) + currentRenderPos[index];
simulationData.SetTarget(index, targetPoint, desiredSpeed, maxSpeed);

  1. Schedule a UpdatePositionsAndRotations job and ground check job as dependency and complete it while still in Update().

deltaPosition = outputData.targetPoint[index] - simulationData.positions[index];
simulationData.positions[index] += deltaPosition;

// The rotation is done in direction of movement and the groud check is a sphereCast

  1. Update Rendering Position and Rotation in a for loop during LateUpdate();

newPos = simulationData.position[index];
newPos.y = groundHeight[index]; (from ground check)
currentRenderPos[index] = newPos;
currentRenderRot[index] = rotationsFromJob[index];

  1. Bonus question: I almost finished my raycast and spherecheck implementation in RVOQuadtreeBurst.cs, are you interested?

Yes, it’s fine. Though it’s not done by a lot of users, so it might not be the most ergonomic.
The LightweightRVO script in the example scenes does just this. You might want to have a look at it for reference.

The local avoidance system always calculates a velocity vector in the agent’s movement plane. I.e. the RVO system will never tell the agent to move upwards or downwards. Therefore, the elevation component of the velocity will always be 0.

This looks wrong. If this is done every frame you will need to clamp the delta position by the agent’s velocity multipled by the delta time.

The RVOController does

delta = Vector2.ClampMagnitude(delta, rvoAgent.CalculatedSpeed * Time.deltaTime);

Maybe, what does it do exactly?

Oh I did, I dissected the whole thing and made sushi with it :stuck_out_tongue:

Yes, reading it now I had a feeling something was up in here, I will recheck this. Thank you.

BTW I fixed my y position always coming out as 0. If I remembered correctly I was setting the target after running the update jobs (or was it the other way around? haha it was a while ago, it works now).

As you know, with DOTS or DrawMeshInstancedIndirect (like I use), you do not have game objects in the scene representing each character. So In order to shoot them, ram them with a car or make them explode I needed a way to shoot a ray in the scene and get a list of the entities that intersect with it OR in the case of an explosion a list of entities intersecting with a sphere.

The best way to do this is a Quadtree or an Octree with intersection methods to find entities. Unfortunately, maintaining a tree is a costly operation, so you don’t want to have more than one running.

I added a few public methods, and I believe the only thing I changed in your Quadtree code is to add 10 to the height you set as the top of the tree’s bounds. Without this, entities standing near the top of the bounds ended up with their heads and body outside of it, and I could only get hits on their feets. A better approach would probably be to add the height of the tallest RVOAgent instead.

Internally, all agents are considered cylinders, and the values returned tell you how centered your shot was on the agent and at what height you hit it. Externally, I have other code that translate the height into which section was hit (head, body, legs) and each section have a crit zone defined by an offset and radius (respectively the brain, heart and “private area”) :smile:

Currently, I’m working on converting my entities back and forth between being rendered with DrawMeshInstancedIndirect or as game objects for full physics interactions using active ragdolls. The switch is flawless except when an anim is in a transition… mecanim is being a PITA and the lack of documentation is killing me, but I’m getting there!

I get about 170fps with 10k fully animated AI right now, all moving using paths + the RVO and half my code is not yet optimized and jobified. Can’t wait to drive a car into that crowd!

Still having an issue with them walking through geometry though, I’ve been trying to restrict them to the nav mesh, but didn’t manage to get it working right. Too many things to do for 1 brain :exploding_head:

If you want to take a peek, I shared an album with a few pics (please disregard the bug where they are all in sync, that was fixed):

and two videos:

1 Like