Jump to content

Root Motion vs In Place Animations


MackeyK24
 Share

Recommended Posts

15 hours ago, Deltakosh said:

Can you help me understand what root motion does?

https://docs.unity3d.com/Manual/RootMotion.html

Basically they allow the translation on the 'Root Bone' ... So if your animation moves the root bone position like a walking animation (instead of doing the walking animation while staying at position zero)... they allow that... they must some how use that forward motion each frame... If you 'UNCHECK' apply root motion... they keep the mesh at position zero at runtime giving you an in place animation even if the animation has root bone motion...

My little light weight 'Apply Root Motion' detect for false... if that off... I zero the X and Z RIGHT BEFORE I compute the translation matrix for each bone... that way I don't have to constantly check each frame to zero the X and Y positions... I just do that at runtime (I may event put an options to not actually zero but offset by the original x and z ... that we just take off whatever the animation put on from starting position so the X and Z might not actually be 0 but will be the start position ... maybe is started at z 0.1 or something like that... anyways thats seems to be working so far)

In C# code in the loop that goes thru each bone... I compute the matrix for each frame of the animations building keys from the cupped matrix and stick on that bone.animation ... Since I am fixing and offsetting the X and Z during the actual Animation Frame Sampling... They just going the scene already on the bone with no runtime code need to fix/offset to remove and root bone motion... But as you stated before ... all this could be avoided if you use IN-PLACE animations... but they are harder to find ALL in-place animation for a complete character setup :)

 

BTW... Here is how I do that in C# before it ever gets serialized into the scene:

AnimationMode.BeginSampling();
for (var i = 0; i < clipFrameCount; i++)
{
    Matrix4x4 local;
    clip.SampleAnimation(source, i * frameTime);
    if (applyRootMotion == false && transform == skinnedMesh.rootBone) {
        float positionX = transform.localPosition.x;
        float positionY = transform.localPosition.y;
        float positionZ = transform.localPosition.z;
        if (animationState != null) {
            positionY = Mathf.Clamp(positionY, 0.0f, animationState.clampRootPositionY);
            if (animationState.zeroRootPositionsXZ) {
                positionX = 0.0f;
                positionZ = 0.0f;
            }
        }
        local = Matrix4x4.TRS(new Vector3(positionX, positionY, positionZ), transform.localRotation, transform.localScale);
    } else {
        local = Matrix4x4.TRS(transform.localPosition, transform.localRotation, transform.localScale);
    }
    float[] matrix = new[] {
        local[0, 0], local[1, 0], local[2, 0], local[3, 0],
        local[0, 1], local[1, 1], local[2, 1], local[3, 1],
        local[0, 2], local[1, 2], local[2, 2], local[3, 2],
        local[0, 3], local[1, 3], local[2, 3], local[3, 3]
    };
    var key = new BabylonAnimationKey
    {
        frame = (i + frameOffest),
        values = matrix
    };
    keys.Add(key);
}
AnimationMode.EndSampling();

 

Link to comment
Share on other sites

14 hours ago, Deltakosh said:

 Im sorry @Deltakosh ... but I don't get what your trying to show here... the movement in the z direction by adding 0.05 :(

Does this anyway have to do with how unity is applying root motion to the animation... basically I think the motion from the animation is supposed to drive the actual movement of the character... I think that what Root Motion actually does.

Link to comment
Share on other sites

On 8/31/2017 at 6:21 AM, Deltakosh said:

Yo @Deltakosh 

I think that might work... It looks like you just take "Runspeed" to the Z  and use turning for the Root Motion (I guess just rotation) and just always go forward on Z or backwards if negative value on Z

Take a look at this: https://docs.unity3d.com/Manual/ScriptingRootMotion.html

Link to comment
Share on other sites

2 hours ago, Deltakosh said:

Ok I understand. Root motion is kind of animation for the mesh. The bones will deform the mesh and the root motion animation will move / rotate the mesh itself. 

If this is correct then we just need to export mesh root motion animations alongside bones animations right?

Yo thanks @Deltakosh

That is exactly what I am doing now...

During the encoding of the transformation matrix of each bone, I do the following:

1... Check of is root bone transform, if so encode the position and rotation of the root transform to babylon animation key and REMOVE/RESET (via animator clip options) existing position and rotation values for that bone in the matrix calculations... So we will end up the the full animation (baked or zero out root bone translation with a copy of the real translation for the root bone store in another set of animation key)

Take a look a the c# Code, this code 'SAMPLES' each animation clip frame for each bone:

AnimationMode.BeginSampling();
for (var i = 0; i < clipFrameCount; i++)
{
    Matrix4x4 local;
    int frameIndex = (i + frameOffest);
    clip.SampleAnimation(source, i * frameTime);
    if (transform == skinnedMesh.rootBone) {
        float positionX = transform.localPosition.x;
        float positionY = transform.localPosition.y;
        float positionZ = transform.localPosition.z;
        float rotationX = transform.localRotation.eulerAngles.x * (float)Math.PI / 180;
        float rotationY = transform.localRotation.eulerAngles.y * (float)Math.PI / 180;
        float rotationZ = transform.localRotation.eulerAngles.z * (float)Math.PI / 180;
        Quaternion rotationQT = transform.localRotation;         
        if (encodeRootMotions) {
            // Build Transform Root Motion Keys
            if (cycled == false) {
                pxkeys.Add(new BabylonAnimationKey {
                    frame = frameIndex,
                    values = new[] { positionX }
                });
                pykeys.Add(new BabylonAnimationKey {
                    frame = frameIndex,
                    values = new[] { positionY }
                });
                pzkeys.Add(new BabylonAnimationKey {
                    frame = frameIndex,
                    values = new[] { positionZ }
                });
                rxkeys.Add(new BabylonAnimationKey {
                    frame = frameIndex,
                    values = new[] { rotationX }
                });
                rykeys.Add(new BabylonAnimationKey {
                    frame = frameIndex,
                    values = new[] { rotationY }
                });
                rzkeys.Add(new BabylonAnimationKey {
                    frame = frameIndex,
                    values = new[] { rotationZ }
                });
            }
        }
        // Bake Root Motion Translations
        if (settings.loopBlendOrientation || settings.keepOriginalOrientation) {
            if (settings.keepOriginalOrientation) {
                rotationQT = Quaternion.Euler(originalRX, originalRY, originalRZ);
            } else {
                rotationQT = Quaternion.Euler(rotationQT.eulerAngles.x, settings.orientationOffsetY, rotationQT.eulerAngles.z);
            }
        }
        if (settings.loopBlendPositionY || settings.keepOriginalPositionY) {
            if (settings.heightFromFeet) {
                positionY = Mathf.Clamp(positionY, 0.0f, settings.level + clampFeetPositionY);
            } else {
                positionY = originalPY + clampFeetPositionY;
            }
        }
        if (zeroRootPositionsXZ) {
            positionX = 0.0f;
            positionZ = 0.0f;
        } else {
            if (settings.loopBlendPositionXZ || settings.keepOriginalPositionXZ) {
                if (settings.keepOriginalPositionXZ) {
                    positionX = originalPX;
                    positionZ = originalPZ;
                } else {
                    positionX = 0.0f;
                    positionZ = 0.0f;
                }
            }
        }
        local = Matrix4x4.TRS(new Vector3(positionX, positionY, positionZ), rotationQT, transform.localScale);
    } else {
        local = Matrix4x4.TRS(transform.localPosition, transform.localRotation, transform.localScale);
    }
    float[] matrix = new[] {
        local[0, 0], local[1, 0], local[2, 0], local[3, 0],
        local[0, 1], local[1, 1], local[2, 1], local[3, 1],
        local[0, 2], local[1, 2], local[2, 2], local[3, 2],
        local[0, 3], local[1, 3], local[2, 3], local[3, 3]
    };
    var key = new BabylonAnimationKey
    {
        frame = frameIndex,
        values = matrix
    };
    keys.Add(key);
}
AnimationMode.EndSampling();
frameOffest += clipFrameCount;
totalFrameCount += clipFrameCount;

The new Root animation keys encoding like this:

//
// Format Root Motion Keys
//
string property = "none";
if (pxkeys.Count > 0)
{
    property = "metadata.state.animPosition.x";
    anims.Add(new BabylonAnimation
    {
        dataType = (int)BabylonAnimation.DataType.Float,
        name = property + " animation",
        keys = pxkeys.ToArray(),
        framePerSecond = frameRate,
        enableBlending = false,
        blendingSpeed = 0.0f,
        loopBehavior = (animationState != null) ? (int)animationState.loopBehavior : (int)BabylonAnimation.LoopBehavior.Relative,
        property = property
    });
}
property = "none";
if (pykeys.Count > 0)
{
    property = "metadata.state.animPosition.y";
    anims.Add(new BabylonAnimation
    {
        dataType = (int)BabylonAnimation.DataType.Float,
        name = property + " animation",
        keys = pykeys.ToArray(),
        framePerSecond = frameRate,
        enableBlending = false,
        blendingSpeed = 0.0f,
        loopBehavior = (animationState != null) ? (int)animationState.loopBehavior : (int)BabylonAnimation.LoopBehavior.Relative,
        property = property
    });
}
property = "none";
if (pzkeys.Count > 0)
{
    property = "metadata.state.animPosition.z";
    anims.Add(new BabylonAnimation
    {
        dataType = (int)BabylonAnimation.DataType.Float,
        name = property + " animation",
        keys = pzkeys.ToArray(),
        framePerSecond = frameRate,
        enableBlending = false,
        blendingSpeed = 0.0f,
        loopBehavior = (animationState != null) ? (int)animationState.loopBehavior : (int)BabylonAnimation.LoopBehavior.Relative,
        property = property
    });
}
property = "none";
if (rxkeys.Count > 0)
{
    property = "metadata.state.animRotation.x";
    anims.Add(new BabylonAnimation
    {
        dataType = (int)BabylonAnimation.DataType.Float,
        name = property + " animation",
        keys = rxkeys.ToArray(),
        framePerSecond = frameRate,
        enableBlending = false,
        blendingSpeed = 0.0f,
        loopBehavior = (animationState != null) ? (int)animationState.loopBehavior : (int)BabylonAnimation.LoopBehavior.Relative,
        property = property
    });
}
property = "none";
if (rykeys.Count > 0)
{
    property = "metadata.state.animRotation.y";
    anims.Add(new BabylonAnimation
    {
        dataType = (int)BabylonAnimation.DataType.Float,
        name = property + " animation",
        keys = rykeys.ToArray(),
        framePerSecond = frameRate,
        enableBlending = false,
        blendingSpeed = 0.0f,
        loopBehavior = (animationState != null) ? (int)animationState.loopBehavior : (int)BabylonAnimation.LoopBehavior.Relative,
        property = property
    });
}
property = "none";
if (rzkeys.Count > 0)
{
    property = "metadata.state.animRotation.z";
    anims.Add(new BabylonAnimation
    {
        dataType = (int)BabylonAnimation.DataType.Float,
        name = property + " animation",
        keys = rzkeys.ToArray(),
        framePerSecond = frameRate,
        enableBlending = false,
        blendingSpeed = 0.0f,
        loopBehavior = (animationState != null) ? (int)animationState.loopBehavior : (int)BabylonAnimation.LoopBehavior.Relative,
        property = property
    });
}
//
// Cache Babylon Animation Keys
//
if (anims.Count > 0)
{
    List<BabylonAnimation> sourceAnimiamtions = null;
    if (SceneBuilder.AnimationCurveKeys.ContainsKey(sourceId)) {
        sourceAnimiamtions = SceneBuilder.AnimationCurveKeys[sourceId];
    } else {
        sourceAnimiamtions = new List<BabylonAnimation>();
        SceneBuilder.AnimationCurveKeys.Add(sourceId, sourceAnimiamtions);
    }
    foreach (var anim in anims) {
        sourceAnimiamtions.Add(anim);
    }
}

This works great so far... Then on the client side

1... I use these to function to update the current root motion for the animPosition keys frames encoded for for the root transform/bone

private updateRootMotion():void {
    if (this.owned.metadata != null && this.owned.metadata.state != null) {
        if (this.owned.metadata.state.animPosition != null) {
            this._rootPosition = this.owned.metadata.state.animPosition;
            this._rootPosition.subtractToRef(this._lastPosition, this._deltaPosition);
            this._lastPosition.x = this._rootPosition.x;
            this._lastPosition.y = this._rootPosition.y;
            this._lastPosition.z = this._rootPosition.z;
        }
        if (this.owned.metadata.state.animRotation != null) {
            this._rootRotation = this.owned.metadata.state.animRotation;
            this._rootRotation.subtractToRef(this._lastRotation, this._deltaRotation);
            this._lastRotation.x = this._rootRotation.x;
            this._lastRotation.y = this._rootRotation.y;
            this._lastRotation.z = this._rootRotation.z;
        }
    }
}
private resetRootMotion():void {
    this._lastPosition.x = 0.0;
    this._lastPosition.y = 0.0;
    this._lastPosition.z = 0.0;
    this._lastRotation.x = 0.0;
    this._lastRotation.y = 0.0;
    this._lastRotation.z = 0.0;
}

 

The update root motion gets called every frame... I use a 'LastPosition' holder to calculate the DELTA changes of the position and rotation

and expose as a few function on the new animator component... So can at anytime call:

animation.getRootPosition() - Get the original rootPosition vector recorded into the animations

animation.getRootRotation() - Same as above but for rotations

animation.getDeltaPosition() - The calculated delta changes from the last frame

animation.getDeltaRotation() - Same as above but for rotations

I can then use regular movement code to move the 'Container' game object ... not the actual skinned mesh transform but its parent.

So in a TestPlayer.ts controller that is attach to the parent object... I get a reference to the 'Animator Component' ... Just like you would do in Unity:

var mannequinn:BABYLON.AbstractMesh = this.getChildMesh("Mannequinn");
if (mannequinn != null) {
    this._animator = this.manager.findSceneComponent("BABYLON.AnimationState", mannequinn);
    if (this._animator != null) {

        // Note All loaded and ready

    } else {
        console.log("Failed to locate animator for mannequinn: " + mannequinn.name);
    }
} else {
    console.log("Failed to locate mannequin mesh: " + mannequinn.name);
}

 

I can then make call it its public functions like, getDeltaPosition() and use that delta to move the player with velocity:

 

if (this._animator != null) {
    var deltaPosition:BABYLON.Vector3 = this._animator.getDeltaPosition();
    var deltaRotation:BABYLON.Vector3 = this._animator.getDeltaRotation();
    if (this._character != null) {
        var delta:number = this.manager.deltaTime;          

        //var ang:number = (deltaRotation.y * (this.rotateSpeed * 0.5)) / delta;
        //this.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,-ang,0));
        //this.mesh.rotation.y += -ang;

        this._inputVelocity.x = (deltaPosition.x * this.moveSpeed) / delta;
        this._inputVelocity.y = 0.0;
        this._inputVelocity.z = (deltaPosition.z * this.moveSpeed) / delta;
        
        var jumped:boolean = (this.manager.getKeyInput(this.keyboardJump) || this.manager.getButtonInput(this.buttonJump));
        if (jumped === true) this._inputVelocity.y = this.jumpForce;
        
        this._character.move(this._inputVelocity, jumped);
    }
}

 

Dud everything so far is working great... EXCEPT FOR MY LACK OF 3D-Game stuff... For example ... I am TOTALLY shitty at rotation and quaternions and smoothly rotating.... I don't know that part is always so 'Fuzzy' for me... Especially compared to ALL the other stuff I can do BOTH in Babylon Typscript and Unity C# (as you can tell by the toolkit itself)... But rotations ALWAYS fuck me up :(

So I have my current rotation code remarked in the example about.... What I need is to SMOOTHLY rotate the parent game object with the Root Bone rotation delta.... With the code I am use above, trying to rotate with angular velocity as well as regular mesh.rotation... it SANPS around too fast and looks funny.:

 

//var ang:number = (deltaRotation.y * (this.rotateSpeed * 0.5)) / delta;
//this.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,-ang,0));
//this.mesh.rotation.y += -ang;

One last part is when switching from walk to run... you can see a 'Hard Jump' because I also don't really now HOW to use enableBlending and BlendingSpeed... I red the docs ... but I still can't get the desired smoothness in between animation clip changes

 

But that is my progress so far... I am going to make a video so you can actually see what I am talking about and see the animation system action.... Maybe you can then tell me what I need to do to go further :)

 

Link to comment
Share on other sites

  • 8 months later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...