Evaluate specific time of Animation?

With a Animation is there a way like in Unity with an AnimationCurve to get the value at a specific time of the animation?

For example in unity if I want to make a curve in the editor and then use it in the script as a easing control I can do something like animationCurve.Evaluate(time) and get the value for that specific time. I know the systems are not the exact same but couldn’t we or do we support something like this already with Animations?

Internally we have the animatable._interpolate which is doing that but we do not (yet) expose a way to get the value.

I could do that if you want…(the price for it: creates an issue for me to track the need ;))

Babylon Scene Manager has BABYLON.Utilities.SampleAnimation functions… One line of code to handle :slight_smile:

1 Like

The details of my Animation evaluation functions

        /** Gets the float "result" as the sampled key frame value for the specfied animation track. */
        public static SampleAnimationFloat(animation:BABYLON.Animation, time: number):number {
            let result:number = 0;
            if (animation != null && animation.dataType === BABYLON.Animation.ANIMATIONTYPE_FLOAT) {
                const keys:BABYLON.IAnimationKey[] = animation.getKeys();
                if (time < keys[0].frame) {
                    time = keys[0].frame;
                } else if (time > keys[keys.length - 1].frame) {
                    time = keys[keys.length - 1].frame;
                }
                // ..
                if ((<any>animation)._state == null) (<any>animation)._state = { key: 0, repeatCount: 0, loopMode: BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE, workValue: BABYLON.Matrix.Zero() };
                result = BABYLON.Utilities.InterpolateAnimation<number>(animation, time, (<any>animation)._state);
            }
            return result;
        }
        /** Set the passed vector2 "result" as the sampled key frame value for the specfied animation track. */
        public static SampleAnimationVector2(animation:BABYLON.Animation, time: number):BABYLON.Vector2 {
            let result:BABYLON.Vector2 = null;
            if (animation != null && animation.dataType === BABYLON.Animation.ANIMATIONTYPE_VECTOR2) {
                const keys:BABYLON.IAnimationKey[] = animation.getKeys();
                if (time < keys[0].frame) {
                    time = keys[0].frame;
                } else if (time > keys[keys.length - 1].frame) {
                    time = keys[keys.length - 1].frame;
                }
                if ((<any>animation)._state == null) (<any>animation)._state = { key: 0, repeatCount: 0, loopMode: BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE, workValue: BABYLON.Matrix.Zero() };
                result = BABYLON.Utilities.InterpolateAnimation<BABYLON.Vector2>(animation, time, (<any>animation)._state);
            }
            return result;
        }
        /** Set the passed vector3 "result" as the sampled key frame value for the specfied animation track. */
        public static SampleAnimationVector3(animation:BABYLON.Animation, time: number):BABYLON.Vector3 {
            let result:BABYLON.Vector3 = null;
            if (animation != null && animation.dataType === BABYLON.Animation.ANIMATIONTYPE_VECTOR3) {
                const keys:BABYLON.IAnimationKey[] = animation.getKeys();
                if (time < keys[0].frame) {
                    time = keys[0].frame;
                } else if (time > keys[keys.length - 1].frame) {
                    time = keys[keys.length - 1].frame;
                }
                if ((<any>animation)._state == null) (<any>animation)._state = { key: 0, repeatCount: 0, loopMode: BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE, workValue: BABYLON.Matrix.Zero() };
                result = BABYLON.Utilities.InterpolateAnimation<BABYLON.Vector3>(animation, time, (<any>animation)._state);
            }
            return result;
        }
        /** Set the passed quaternion "result" as the sampled key frame value for the specfied animation track. */
        public static SampleAnimationQuaternion(animation:BABYLON.Animation, time: number):BABYLON.Quaternion {
            let result:BABYLON.Quaternion = null;
            if (animation != null && animation.dataType === BABYLON.Animation.ANIMATIONTYPE_QUATERNION) {
                const keys:BABYLON.IAnimationKey[] = animation.getKeys();
                if (time < keys[0].frame) {
                    time = keys[0].frame;
                } else if (time > keys[keys.length - 1].frame) {
                    time = keys[keys.length - 1].frame;
                }
                if ((<any>animation)._state == null) (<any>animation)._state = { key: 0, repeatCount: 0, loopMode: BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE, workValue: BABYLON.Matrix.Zero() };
                result = BABYLON.Utilities.InterpolateAnimation<BABYLON.Quaternion>(animation, time, (<any>animation)._state);
            }
            return result;
        }
        /** Set the passed matrix "result" as the sampled key frame value for the specfied animation track. */
        public static SampleAnimationMatrix(animation:BABYLON.Animation, time: number):BABYLON.Matrix {
            let result:BABYLON.Matrix = null;
            if (animation != null && animation.dataType === BABYLON.Animation.ANIMATIONTYPE_MATRIX) {
                const keys:BABYLON.IAnimationKey[] = animation.getKeys();
                if (time < keys[0].frame) {
                    time = keys[0].frame;
                } else if (time > keys[keys.length - 1].frame) {
                    time = keys[keys.length - 1].frame;
                }
                if ((<any>animation)._state == null) (<any>animation)._state = { key: 0, repeatCount: 0, loopMode: BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE, workValue: BABYLON.Matrix.Zero() };
                result = BABYLON.Utilities.InterpolateAnimation<BABYLON.Matrix>(animation, time, (<any>animation)._state);
            }
            return result;
        }
        /** Creates a targeted float animation for tweening.  */
        public static CreateTweenAnimation(name:string, targetProperty:string, startValue:number, endValue:number, frameRate:number = 30, loopMode:number = BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT):BABYLON.Animation {
            const keyFrames:any[] = [];
            keyFrames.push({
                frame: 0,
                value: startValue,
            });
            keyFrames.push({
                frame: frameRate,
                value: endValue,
            });
            const result = new BABYLON.Animation(name, targetProperty, frameRate, BABYLON.Animation.ANIMATIONTYPE_FLOAT, loopMode);
            result.setKeys(keyFrames);
            return result;
        }
        /** Gets the last key frame index value. */
        public static GetLastKeyFrameIndex(animation:BABYLON.Animation):number {
            let result:number = 0;
            if (animation != null) {
                const keys:BABYLON.IAnimationKey[] = animation.getKeys();
                if (keys != null && keys.length > 0) {
                    const lastKey:BABYLON.IAnimationKey = keys[keys.length - 1];
                    if (lastKey != null) {
                        result = lastKey.frame;
                    }
                }
            }
            return result;
        }
        /** Private internal frame interpolation helper */
        private static InterpolateAnimation<T>(animation:BABYLON.Animation, frame: number, state: BABYLON._IAnimationState): T {
            return animation._interpolate(frame, state) as T;
        }

1 Like

https://playground.babylonjs.com/#7V0Y1I#240

Something like this, but that takes the tangents and all the good sauce into consideration.

So @Deltakosh … are you gonna make some sort of animation.getValueAtTime ?

Yes sir!

Yo @Deltakosh … can you please let us know when you made that PR for animation.getValueAtTime function :slight_smile:

just track this one: Animation.Evaluate (Likeness to Unity.AnimationCurve.Evaluate) · Issue #9920 · BabylonJS/Babylon.js (github.com)

Good news people!

It is in!

This code can be run with the upcoming nightly:

const xSlide = new BABYLON.Animation("xSlide", "position.x", frameRate, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
    const keyFrames = []; 
    keyFrames.push({
        frame: 0,
        value: 2
    });
    keyFrames.push({
        frame: frameRate,
        value: -2
    });
    keyFrames.push({
        frame: 2 * frameRate,
        value: 2
    });
    xSlide.setKeys(keyFrames);
    console.log(xSlide)
     let t = 0
    scene.onBeforeRenderObservable.add(()=>{
        t += engine.getDeltaTime() *0.001
        let d = Math.sin(t)*frameRate
        box.position.x = xSlide.evaluate(d);
    })

More specifically this: xSlide.evaluate(d);

Enjoy!

1 Like

You are a gentleman and a scholar! Thank you sir, lots of uses for this.

1 Like

Yo @Pryme8 … Its is basically what i was doing above (Example: SampleAnimationFloat function Above)… Only my version test frame time is with the key frame limits… But still just uses animation._interpolation just like i am doing above already :slight_smile:

Expect you could not pass a 0-1 value to your method and get the whole animation I thought? Like wasn’t the problem that yours was frame specific? We tried it and it did not work which is why we posted this on the forum?

Ohh @Deltakosh looking at this it’s not what we really where looking for. The method is still taking frames not a normalized 0-1 value. Like I’d I pass the method a 2.1 it should see that as 0.1 but the second loop of the animation.

This is kinda just the same thing that we had already just a new access point it seems.

No… it works. The problem was missing tangents on the actual export of the curve.

Ask @bruuuuuno

Besides if you look at my interpolation function above, I am literally calling animation._interpolate

Just like the new animation.evaluate

1 Like

Why are you awake!?!?

Still using frames, you are missing the point of what it’s supposed to do. Not frames a normalized 0-1 value for the entire animation.

Like if I pass it 6272.523 the evaluation method should only worry about the .523 and go to that % of the animation.

I guess just multiply the fractional value by the frame rate and just use your stuff?

For normalized 0 to 1 values of course you can just demoralize using the last key frame value

That’s what I do for the whole mechanim type animation system

It is driven by a base normalee time from 0 to 1 and all the blended anim tracks denormalize this value with the animation to value

1 Like

See I was just asking for a method that handled that, so you did not have to do things like * frameRate.

Because I agree this is exactly what you were doing already.

If it was as simple as interpolate but scale your normalized value by frameRate then we did not really need anything :sweat_smile:

Maybe @Deltakosh can make the evaluate function use normalized 0 to 1 values so we don’t have to manually calculate using framerate

Then everyone is happy :blush:

Im happy for sure now that I know we had it the whole time cause of the Mac man! If I just gotta times it by the frameRate then that’s that, sounds good to me!