Mecanim and RVO

Hi

I’m in the process of migrating our project (which uses Mecanim) to A*. Everything’s worked pretty well so far using a variation of AIPath that passes velocity.magnitude and targetDirection.normalized into Mecanim to move the NPCs.

The problem I have is when trying to integrate RVO. All the current classes (agent and controller) seem to work in this manner:
Input to agent: desiredVelocity
Output from agent: translated position and rotation

ie. the controller and agent itself does the move. With mecanim however, this needs works the other way round, with the controller and agent simply suggesting a new velocity to input into Mecanim rather than moving the object around itself.

So we need:
Input to agent: desiredVelocity, actualPosition
Output from agent: resultingVelocity
Mecanim: takes rotation and speed from resultingVelocity to move the actual character

My current attempts to do this don’t seem to be getting anywhere. My steps are each frame (using a modified AIPath and RVOController):

  1. CalculateVelocity() -> velocity and targetDirection
  2. agent.Position = transform.position
  3. Do obstacle avoidance same as in RVOController
  4. agent.Position.DesiredVelocity = targetDirection.normalized * velocity.magnitude
  5. read out agent.Velocity

The value I get from 5. does not seem to be what is needed to drive the NPC in the correct direction. I’m either doing something stupid or I’m not quite understanding how RVOController and RVO Agent works?

I’m a bit confused because in AIPath.cs, controller.Move() seems to refer to local space (ie. dir is always just (0,0,x)) while in RVOExampleAgent.cs, controller.Move() seems to refer to world space (dir = waypoint - pos)

Thanks!

Current code for 4.:
`
var dir = targetDirection.normalized * velocity.magnitude;

		_rvo.Move(dir);
		
		Vector3 relativeVel = Quaternion.Inverse (tr.rotation) * _rvo.velocity;

		float angle = Mathf.Atan2 (relativeVel.x, relativeVel.z) * Mathf.Rad2Deg;
			
		_locomotion.Do (_rvo.velocity.magnitude, angle);

`

Hi

Mecanim for movement is on my todo list. I haven’t got to it yet though.
What would work best would be
Input to RVOAgent: desiredVelocity
Output from agent: velocity (from previous frame)
Use the velocity like you use it.
Hm… one thing, you seem to have confused the Atan2 call. I think z should be the first parameter and x the second one. (yes, atan2 is weird and is called like atan2(y,x) and not atan2(x,y) ).
Then, the important part. Reassign the new position to the new position after movement to the rvoagent. If you use an RVOController, I think it will do this for you, but otherwise you need to do it manually. Otherwise the internal agent will end up in a different position than your actual character.

I haven’t tested this myself, but I think this should work.

Yeah the atan2 is swapped as a shorthand for adding 90 degrees.

The results are that it “works”, kind of but looks far from good with the character often oscillating to the left and right of the path, and generally going quite AWOL when trying to do local avoidance. I’ll send you a video of what it looks like.

Ah, nice trick with atan2.

Hm… the video you sent does look kinda weird…
Would it be possible for you to share that project with me so I can debug it? It would be a real for me getting mecanim working in the A* Pathfinding Project.

Thanks :slight_smile: sent you a mail.

I was planning on using this product for a game using Mecanim, so I am very interested in seeing how this works out. ATM I do not need collision avoidance but it probably will be needed in the future. The main reason for needing Mecanim is for animation retargeting. I am stumbling through modifying AIPath as best I can to get a good result for now, but it would be great not to have to mess with base code that will be revised at some point. I am using AIpath as a base class but by default it does not have a usable velocity or angular velocity to feed to the animator. Getting the hound to follow the rabbit is not easy for us scripters:-) Thanks! Brett

I would love to look at whatever revised AIPath, RVO Agent / Controller you guys come up with because I am also getting dodgy behavior attempting to get mecanim root motion characters moving around.

It wasn’t hard to get it working with relatively simple movements but anything complicated and it falls apart fairly quickly.

1 Like

Sorry for resurrecting this post, but is there any new info about how to combine rvo with mecanim (root motion)? Is there anyone who has successfully combined the two? Or is the information in this post still up to date?

In our code base, we have this working properly, however, I do not use the root motion, but calculate that from script in OnAnimatorMove().

This way, the OnAnimatorMove() is called AFTER all of the movement caclulations & mechanim’s internal process has finished.

Might not work for everyone, but its also an option.

1 Like

I think i’m doing something similar.
Basically I do as follows in OnAnimatorMove:

Velocity = this.animator.deltaPosition / Time.deltaTime;
this.characterController.SimpleMove(this.velocity);
this.rvoController.Teleport(this.transform.position);

The solution works great for small amounts of agents. But in simulations where multiple agents collide (say 15 trying to reach the same point) they get stuck but they are still playing the move animation at full speed. This looks really weird and is unacceptable for our solution. (I think this is no different from the regular mecanim +unity navigator controller tho)

Well, in my implementation, I just got rid of the character controller due to how slow and expensive it is. I switched over to using a rigidbody with a torso collider, with a raycast for the ground plane.

This gives the added benefits that you can push other characters out of the way.

If you have the delta position you need to move to, why not just pass that out to CharacterController.Move() (don’t forget to add gravity).

This will move the character as much as the animator has moved.

I think your issue has a lot to do with the fact that you are trying to have mechanim tell you where to move. In our case, I calculate all the necessary velocities/directions/speeds in code, and pass those on to mecanim, which performs that animation, which in turn call OnAnimatorMove(), where I just update the position of my character.

If you are constantly calling Teleport though, you won’t really get any RVO.

1 Like

Hi Skalev,

thanks for your reply.
I based the code in OnAnimatorMove on unity’s example on how to integrate their navmesh agent with mecanim. I could indeed use the regular move - but that should not be such a great difference for the collision problem. Thanks for the tip anyway.

The reason that I teleport the rvo agent is that mecanim might have a different speed than the desired velocity I feed to mecanim (because of animation transitions). And that the position of the rvo agent gets out of sync with the actual transform. (I read that somewhere on this forum - I might be wrong of course)

I think that might also be the problem - I let RVO calculate the velocity - pass rvo’s desired velocity to mecanim - and then pass mecanims velocity to the character controller. But mecanim does not take physics into account.

Do you calculate if the next position is collision free before you pass any information to mecanim?

My setup is a bit different.

I have a character locomotion script I wrote, which is the entry point for all code that moves a character. This controls mecanim by passing in the proper animation vars. in fact this is what drives everything, i just wait for onAnimatorMove() to actually apply movement.

The second relevant part here, is my PathFollower, which is the one responsible for mitigating pathfinding/rvo with my locomotion.

This is how my Update() Function of the follower looks like:

if (m_isFollowingPath && !Paused)
{
   var realPos = DoAvoidance ? m_rvoAgent.InterpolatedPosition : m_locomotion.Position;
   m_nextStep = CalculateNextStep(realPos, out m_shouldArrive , out m_desiredSpeed);
                
                if (m_shouldArrive)
                {
                    m_rvoAgent.DesiredVelocity = m_nextStep * m_desiredSpeed;
                    if (DoAvoidance)
                    {
                        m_locomotion.DesiredDirection = m_rvoAgent.Velocity;
                        m_locomotion.DesiredSpeed = m_rvoAgent.Velocity.magnitude;
                    }
                    else
                    {
                        m_locomotion.DesiredDirection = m_nextStep;
                        m_locomotion.DesiredSpeed = m_desiredSpeed;
                    }
                        
                }
                else
                {
                    m_rvoAgent.DesiredVelocity = m_nextStep * m_lastRequestedSpeed;
                    if (DoAvoidance)
                    {
                        m_locomotion.DesiredDirection = m_rvoAgent.Velocity;
                        m_locomotion.DesiredSpeed = m_rvoAgent.Velocity.magnitude;
                    }
                    else
                    {
                        m_locomotion.DesiredDirection = m_nextStep;
                        m_locomotion.DesiredSpeed = m_lastRequestedSpeed;
                    }
                }
            }
            else
            {
                //update the RVO based on the locomotion
                m_rvoAgent.DesiredVelocity = m_locomotion.DesiredDirection * m_locomotion.DesiredSpeed;
            }
            m_frameMove = (DoAvoidance && !Paused ? m_rvoAgent.Velocity : 
                          (m_locomotion.ForwardVector * m_locomotion.CurrentSpeed)
                          ) * Time.deltaTime;

            //find the ground plane
            RaycastHit info;
            if (Physics.Raycast(m_locomotion.Position + halfUp + m_frameMove
                , Vector3.down, out info, Mathf.Infinity, GameManager.EnvironmentLayerMask))
            {
                m_frameMove.y = -(info.distance - halfUp.y);
            }

with that OnAnimatorMove() function looks like this:

if (ShouldMove) //some local checks omitted
{
             m_charCont.Move(m_frameMove);
 }
 if (m_rvoAgent.Position != m_locomotion.Position)
{
    //update the RVO sim with the correct current position
    m_rvoAgent.Teleport(m_locomotion.Position);
}
1 Like