root motion Topic is solved

Problems building or running the engine, queries about how to use features etc.
slapin
Bronze Sponsor
Bronze Sponsor
Posts: 77
Joined: Fri May 23, 2025 5:04 pm
x 2

root motion

Post by slapin »

Hi, all!
I want to implement root motion for my character. For that I need to extract root bone transform delta at each frame and set that to identity to clear motion. Any ideas about where to look?

User avatar
sercero
Bronze Sponsor
Bronze Sponsor
Posts: 513
Joined: Sun Jan 18, 2015 4:20 pm
Location: Buenos Aires, Argentina
x 188

Re: root motion

Post by sercero »

I'm not sure my solution is the best, but I did this:

Code: Select all

    // Cambiar los desplazamientos absolutos del root bone por deltas asi puedo llamara a node->translate() directamente.
    // El esqueleto contiene los tracks de las animaciones de los hueso (cada hueso es un track)
    // La clase hueso hereda de Node y por lo tanto las animaciones de los huesos son del tipo nodo
    // Los KeyFrames tambien son KeyFrames especializados en nodos, no la clase KeyFrame base.
    for(int i = 0; i < State::NUM_STATES - 1; i++) {

    // Si no es una animacion estatica ignorar
    if(mStates[i]->mLoop)
        continue;

    //Ogre::Animation::NodeTrackIterator it = baseSkeleton->getAnimation(animacionesEstaticas[i])->getNodeTrackIterator();
    Ogre::Animation::NodeTrackIterator it = baseSkeleton->getAnimation(mStates[i]->mAnimationState->getAnimationName())->getNodeTrackIterator();

    while (it.hasMoreElements()) {
        Ogre::NodeAnimationTrack* track = it.getNext();
        if(track->getAssociatedNode()->getName() == "root") {
            //std::cout << "Track: " << baseSkeleton->getAnimation(animacionesEstaticas[i])->getName() << std::endl;
            Ogre::Vector3 delta = Ogre::Vector3::ZERO;
            for(int j = 0; j < track->getNumKeyFrames(); j++) {
                Ogre::Vector3 trans = track->getNodeKeyFrame(j)->getTranslate();
                //std::cout << "KF (" << j << ") -> " << track->getNodeKeyFrame(j)->getRotation() << std::endl;
                track->getNodeKeyFrame(j)->setTranslate(trans - delta);
                delta = trans;
            }
        }
    }
}
slapin
Bronze Sponsor
Bronze Sponsor
Posts: 77
Joined: Fri May 23, 2025 5:04 pm
x 2

Re: root motion

Post by slapin »

Thanks a lot!

slapin
Bronze Sponsor
Bronze Sponsor
Posts: 77
Joined: Fri May 23, 2025 5:04 pm
x 2

Re: root motion

Post by slapin »

Thanks a lot for the example, I see it creates track full of deltas; but how do you apply this track to the body?
I need to understand this part too...

slapin
Bronze Sponsor
Bronze Sponsor
Posts: 77
Joined: Fri May 23, 2025 5:04 pm
x 2

Re: root motion

Post by slapin »

I implemented this using Ogre master branch

Code: Select all

       for (i = 0; i < NUM_ANIMS; i++) {
            mAnims[i] = mBodyEnt->getAnimationState(animNames[i]);
            mAnims[i]->setLoop(true);
            mAnims[i]->setEnabled(true);
            mAnims[i]->setWeight(0);
            mFadingIn[i] = false;
            mFadingOut[i] = false;
            mSkelAnimations[i] = mSkeleton->getAnimation(animNames[i]);
            for (const auto& it : mSkelAnimations[i]->_getNodeTrackList()) {
                       Ogre::NodeAnimationTrack* track = it.second;
                        Ogre::String trackName = track->getAssociatedNode()->getName();
                        if (trackName == "mixamorig:Hips") {
                                mHipsTracks[i] = track;
                        } else if (trackName == "Root") {
                                mRootTracks[i] = track;
                        }
            }
            Ogre::Vector3 delta = Ogre::Vector3::ZERO;
            Ogre::Vector3 motion = Ogre::Vector3::ZERO;
            for(j = 0; j < mRootTracks[i]->getNumKeyFrames(); j++) {
                        Ogre::Vector3 trans = mRootTracks[i]->getNodeKeyFrame(j)->getTranslate();
                        trans.y = 0.0f;
                        delta = trans - motion;
                        if (delta.z > 0.03f)
                                delta.z = 0.03f;
                        if (delta.z < 0.02f)
                                delta.z = 0.03f;
                        mRootTracks[i]->getNodeKeyFrame(j)->setTranslate(delta);
                        motion = trans;
            }
        }

Now I somehow need to transform "Root" track motion into btRigidBody motion. Should I just reference "Root" node and use its position each frame after animations to move btRigidBody or is there some better way?

User avatar
sercero
Bronze Sponsor
Bronze Sponsor
Posts: 513
Joined: Sun Jan 18, 2015 4:20 pm
Location: Buenos Aires, Argentina
x 188

Re: root motion

Post by sercero »

Hello, this is the code to move the player:
(please take into account that this is probably not the best way to do this, I'm a lousy coder)
(another thing: I'm using something called "AnimationBlender" you can find that in the forum)

Code: Select all


//------------------------------------------------------------------------------------
// Metodo que actualiza la posicion y orientacion del jugador segun el tipo de camara
// -----------------------------------------------------------------------------------
void Player::updateTransform(btVector3 &ghostPositionGoal, float timeSinceLastFrame)
{
    Ogre::Vector3 src = mPlayerNode->getOrientation().zAxis(); // * Ogre::Vector3::UNIT_Z;
    Ogre::Quaternion quat = Ogre::Quaternion::IDENTITY;

if(mCameraManager->getStyle() == CameraMan3::CS_LOCK) {
    // En el caso de Strafe, si se presiona W+S / W+A / D+S / D+A entonces hay que rotar un poco mas
    Ogre::Radian rotAngle = Ogre::Radian(0);

    if(mInputDirection.x > 0 && mInputDirection.y > 0)
        rotAngle = Ogre::Radian(Ogre::Math::PI / 4);

    if(mInputDirection.x < 0 && mInputDirection.y > 0)
        rotAngle = -1 * Ogre::Radian(Ogre::Math::PI / 4);

    if(mInputDirection.x < 0 && mInputDirection.y > 0)
        rotAngle = -1 * Ogre::Radian(Ogre::Math::PI / 4);

    if(mInputDirection.x < 0 && mInputDirection.y < 0)
        rotAngle = Ogre::Radian(Ogre::Math::PI / 4);

    quat = src.getRotationTo(mCameraManager->getFacing()) * Ogre::Quaternion(rotAngle, Ogre::Vector3::UNIT_Y);

    // Ademas corregir la direccion de la cabeza
    /*
    mAnimationState[mStateCurrent]->setBlendMaskEntry(mNeckBone->getHandle(), 0);
    mNeckBone->setManuallyControlled(true);

    Ogre::Radian delta = quat.getYaw() - mNeckBone->getOrientation().getYaw() + rotAngle;

    // Ambos tienen que ser iguales sino hace zig zag en el ajuste
    Ogre::Radian adjust = Ogre::Radian(1.0 * timeSinceLastFrame / 1000);

    if((Ogre::Math::Abs(delta) > 2 * adjust) && (Ogre::Math::Abs(mNeckBone->getOrientation().getYaw()) < Ogre::Radian(0.4 * Ogre::Math::PI))) {
        mNeckBone->yaw(adjust * Ogre::Math::Sign(delta));
    }
    */

    //mAnimationState[mStateCurrent]->setBlendMaskEntry(mNeckBone->getHandle(), 0);
    //mNeckBone->setManuallyControlled(true);
    //mNeckBone->setOrientation(mCameraManager->getFacing() * Ogre::Quaternion(rotAngle, Ogre::Vector3::UNIT_Y));
    //mNeckBone->yaw();
}

if(mCameraManager->getStyle() == CameraMan3::CS_ORBIT) {
    quat = src.getRotationTo(mTranslateVector);	// Get a quaternion rotation operation
}

Ogre::Quaternion delta = Ogre::Quaternion::nlerp(timeSinceLastFrame / 1000 * TURN_SPEED, mPlayerNode->getOrientation(), quat * mPlayerNode->getOrientation(), true);
mPlayerNode->setOrientation(delta);

/*
std::cout << "quat = " << quat << std::endl;
if(quat.y * quat.y > .9) {
    mStateNext = State::TURNAROUND;
    mStateTimeout.init(mAnimationState[State::TURNAROUND]->getLength() / TIMESCALE);
    mStateTimeout.start();
}
*/

// Traslada el nodo del jugador teniendo en cuenta el blending actual
if(quat.w > 0.9) {
    //mPlayerNode->translate(mTranslateVector * timeSinceLastFrame * TIMESCALE * (1 - mAnimationBlender->getProgress()));
    Ogre::Vector3 nodeTranslation = mTranslateVector * timeSinceLastFrame * TIMESCALE * (1 - mAnimationBlender->getProgress());
    ghostPositionGoal += btVector3(nodeTranslation.x, nodeTranslation.y, nodeTranslation.z);
}
}
slapin
Bronze Sponsor
Bronze Sponsor
Posts: 77
Joined: Fri May 23, 2025 5:04 pm
x 2

Re: root motion

Post by slapin »

Thanks a lot for the code!

Reading the code I don't quite understand how the offset is calculated. I see how the rotation is handled, but not the motion.
Can't get where mTranslateVector goes from and what is ghostPositionGoal.
Will look at AnimationBlender.

slapin
Bronze Sponsor
Bronze Sponsor
Posts: 77
Joined: Fri May 23, 2025 5:04 pm
x 2

Re: root motion

Post by slapin »

After some time struggling I managed to make the whole thing work.
First I modified animation preparation code to this:

Code: Select all

       for (i = 0; i < NUM_ANIMS; i++) {
            mAnims[i] = mBodyEnt->getAnimationState(animNames[i]);
            mAnims[i]->setLoop(true);
            mAnims[i]->setEnabled(true);
            mAnims[i]->setWeight(0);
            mFadingIn[i] = false;
            mFadingOut[i] = false;
            mSkelAnimations[i] = mSkeleton->getAnimation(animNames[i]);
            for (const auto& it : mSkelAnimations[i]->_getNodeTrackList()) {
                        Ogre::NodeAnimationTrack* track = it.second;
                        Ogre::String trackName = track->getAssociatedNode()->getName();
                        if (trackName == "mixamorig:Hips") {
                                mHipsTracks[i] = track;
                        } else if (trackName == "Root") {
                                mRootTracks[i] = track;
                        }
            }
            Ogre::Vector3 delta = Ogre::Vector3::ZERO;
            Ogre::Vector3 motion = Ogre::Vector3::ZERO;
            for(j = 0; j < mRootTracks[i]->getNumKeyFrames(); j++) {
                        Ogre::Vector3 trans = mRootTracks[i]->getNodeKeyFrame(j)->getTranslate();
                        if (j == 0)
                                delta = trans;
                        else
                                delta = trans - motion;
                        mRootTracks[i]->getNodeKeyFrame(j)->setTranslate(delta);
                        motion = trans;
            }
        }

Then the actual motion code I do after animations played for this frame.

Code: Select all

void CharacterController::updateRootMotion(Real delta)
{
        Ogre::Vector3 boneMotion = mRootBone->getPosition();
        /* Kinematic motion */
        Ogre::Quaternion rot = mBodyNode->getOrientation();
//        Ogre::Vector3 gravity(0, -9.8, 0);
        Ogre::Vector3 rotMotion = rot * boneMotion;
//        mBodyNode->setPosition(mBodyNode->getPosition() + rotMotion + gravity);
        mBodyNode->setPosition(mBodyNode->getPosition() + rotMotion);
}

mBodyNode is top SceneNode of character where both physics and entity are.

What is missing is node rotation update from root bone rotation and kinematic collisions (another topic).
After collisions implementation I will be able to add gravity and implement jumping...

slapin
Bronze Sponsor
Bronze Sponsor
Posts: 77
Joined: Fri May 23, 2025 5:04 pm
x 2

Re: root motion

Post by slapin »

Actually looped animations when prepared as in code above are fine too and work very well.

paroj
OGRE Team Member
OGRE Team Member
Posts: 2180
Joined: Sun Mar 30, 2014 2:51 pm
x 1168

Re: root motion

Post by paroj »

is there something that benefits both of you and we should put directly into Ogre, so everybody can share the same implementation?

User avatar
sercero
Bronze Sponsor
Bronze Sponsor
Posts: 513
Joined: Sun Jan 18, 2015 4:20 pm
Location: Buenos Aires, Argentina
x 188

Re: root motion

Post by sercero »

@paroj one thing that would be cool is for OGRE to have some simplification in terms of animations is an animation blending system.
I was looking at this: https://www.ogre3d.org/forums/viewtopic.php?t=80704, but never got to implement it.
There is also the famous technofreak animation system which is pretty elaborate.
All of this integrated with a phyisics character controller would go a long way towards simplifying things for people who want to implement a game engine.
Although it would drift away from OGREs purpose which is to be a Graphics engine.

slapin
Bronze Sponsor
Bronze Sponsor
Posts: 77
Joined: Fri May 23, 2025 5:04 pm
x 2

Re: root motion

Post by slapin »

Well, my solution heavily depends on how character model is oriented.
The animation patch-up is universal thing and is much better than playing with manual bone and applying animation tracks to it.
Probably that part can be shared, but actual use of the transform might depend on how the model is made and other factors.
I.e. for Mixamo rig with not split hips the root bone will be hips but you will have to process the transform to filter-out high frequency motions
which will complicate things. I do that using Blender python script for exporting separate Root and Hips bones.
To make this more flexible I guess the animation system could be extended for easier keyframe processing and filtering,
but I have no idea how.
If animation system supported bones postprocessing that could just apply changes after physics and animation processing
so there would be no need for manual bones and track application so one could just apply bone pose after all animations, that would solve both this problem and would allow for activge ragdall and IK easily implemented. Otherwise the thing is doable even today but a bit complicated and requires direct engine guts access.

slapin
Bronze Sponsor
Bronze Sponsor
Posts: 77
Joined: Fri May 23, 2025 5:04 pm
x 2

Re: root motion

Post by slapin »

I think it is at least not in a way of Ogre to be a graphics engine as making animations easier to use adds to benefits of Ogre as whole.

paroj
OGRE Team Member
OGRE Team Member
Posts: 2180
Joined: Sun Mar 30, 2014 2:51 pm
x 1168

Re: root motion

Post by paroj »

slapin wrote: Fri May 30, 2025 5:34 pm

Well, my solution heavily depends on how character model is oriented.

we could start by adding the code that works for you/ the mixamo format. Then the next guy can start there if more is needed.

slapin
Bronze Sponsor
Bronze Sponsor
Posts: 77
Joined: Fri May 23, 2025 5:04 pm
x 2

Re: root motion

Post by slapin »

Looks like my approach to root motion I did per @sercero advice doesn't work too well.
It works only when each frame we have new animation frame. However when we go with unlocked FPS, there is a lot
of repeat frames which leads to lots of additive errors... (3000 FPS!!!11)

So one have to check current animation position for changes or just avoid animation patching and use full transform
calculating previous/current delta. Need to think about it...