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?
root motion Topic is solved
-
- Bronze Sponsor
- Posts: 77
- Joined: Fri May 23, 2025 5:04 pm
- x 2
root motion
-
- Bronze Sponsor
- Posts: 513
- Joined: Sun Jan 18, 2015 4:20 pm
- Location: Buenos Aires, Argentina
- x 188
Re: root motion
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;
}
}
}
}
-
- Bronze Sponsor
- Posts: 77
- Joined: Fri May 23, 2025 5:04 pm
- x 2
Re: root motion
Thanks a lot!
-
- Bronze Sponsor
- Posts: 77
- Joined: Fri May 23, 2025 5:04 pm
- x 2
Re: root motion
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...
-
- Bronze Sponsor
- Posts: 77
- Joined: Fri May 23, 2025 5:04 pm
- x 2
Re: root motion
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?
-
- Bronze Sponsor
- Posts: 513
- Joined: Sun Jan 18, 2015 4:20 pm
- Location: Buenos Aires, Argentina
- x 188
Re: root motion
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);
}
}
-
- Bronze Sponsor
- Posts: 77
- Joined: Fri May 23, 2025 5:04 pm
- x 2
Re: root motion
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.
-
- Bronze Sponsor
- Posts: 77
- Joined: Fri May 23, 2025 5:04 pm
- x 2
Re: root motion
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...
-
- Bronze Sponsor
- Posts: 77
- Joined: Fri May 23, 2025 5:04 pm
- x 2
Re: root motion
Actually looped animations when prepared as in code above are fine too and work very well.
-
- OGRE Team Member
- Posts: 2180
- Joined: Sun Mar 30, 2014 2:51 pm
- x 1168
Re: root motion
is there something that benefits both of you and we should put directly into Ogre, so everybody can share the same implementation?
-
- Bronze Sponsor
- Posts: 513
- Joined: Sun Jan 18, 2015 4:20 pm
- Location: Buenos Aires, Argentina
- x 188
Re: root motion
@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.
-
- Bronze Sponsor
- Posts: 77
- Joined: Fri May 23, 2025 5:04 pm
- x 2
Re: root motion
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.
-
- Bronze Sponsor
- Posts: 77
- Joined: Fri May 23, 2025 5:04 pm
- x 2
Re: root motion
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.
-
- OGRE Team Member
- Posts: 2180
- Joined: Sun Mar 30, 2014 2:51 pm
- x 1168
Re: root motion
slapin wrote: Fri May 30, 2025 5:34 pmWell, 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.
-
- Bronze Sponsor
- Posts: 77
- Joined: Fri May 23, 2025 5:04 pm
- x 2
Re: root motion
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...