How to subscribe to a keyframe in an AnimationGroup?

I want to implement a feature that lets the user to subscribe to keyframes of any animation, whether 2D or 3D.
The goal is to invoke a callback everytime the animation reachs 1 or N keyframes appointed by the user.

In 2D animations I do it using timeouts.

What would be the proper way to subscribe to a keyframe in a mesh which is using an AnimationGroup imported from a .glb file?

Is there something in Babylon for this purpose?

Probably you may use

  • goToFrame(frame: number, useWeight?: boolean): [AnimationGroup](Babylon.js docs

Note that the animation group must be in playing or paused status.

To check the current frame one may use

getCurrentFrame(): number

So I’d need to check the current frame, frame by frame, to know if the animation has reached a certain frame?

Looks like I’d better use timeouts as well for 3D animations.

Yes, this seems logical and doesn’t depend on animation speed, fps etc.

1 Like

Yep but doesn’t look good for performance. The user can add as many subscriptions as desired to N number of actors.

I will go for timeouts then.

Thanks for the info.

1 Like

You can try AnimationEvent:
https://doc.babylonjs.com/features/featuresDeepDive/animation/advanced_animations/#attach-events-to-animations

2 Likes

Hey, that seems to be the feature!

But I’m finding a problem… I created a .glb using this tutorial: Babylon.js docs

This is how it looks like in Blender:

Link to the file: [Link]

So I want to add the events to the targeted animation of the animation group (I’d expect it would be just one targeted animation per animation group), but when I log the targeted animations of the Walking grouped animation, I find hundreds of them, all of them similar with 32 keyframes (some scale, some rotation, some position):


I don’t have idea how to use addEvent in that animationGroup, either why are there that amount of similar targeted animations. The same is happening for animatables array, same amount of elements.

I also looks like a big amount of memory usage per grouped animation. That’s not good in case the user spawns several actors. I’m using assetContainer.instantiateModelsToScene to spawn each new actor, this method clones all the grouped animations to allow individual animation per actor.

There is a lot on this topic already in the forum.

In a nutshell: normalize and cleanup your animations in Blender. Also have a look @labris optimizer! This will drastically reduce animation file size.

Blender uses a different timeline than Babylon. A marker on keyframe 3 in Blender, does not readily translate to Babylon getCurrentFrame(). I cannot quite remember whether GLTF supports animation events.

In terms of the amount of data. There ususally is 1 rotation per Mixamo bone (~70) per animation frame. That will be 2100 rotations for a 30 frames animation.

3 Likes

Understood. The optimizer is pretty cool.

Looks like I could use any of the targeted animations considering all of them cover the whole timeline and keyframes.

1 Like

I am using AnimationEvent with AnimationGroup in my project too.

Not sure if it is the intended way, but here is how I do it:

function insertEvent(aniGroup: AnimationGroup, frame: number) {
  const eventAni = createEventAni(aniGroup);
  const aniEvent = new AnimationEvent(frame, () => {
    // your logic
  });
  eventAni.addEvent(aniEvent);
}

function createEventAni(aniGroup: AnimationGroup): Animation {
  const eventAni = aniGroup.targetedAnimations[0].animation.clone();
  eventAni.name = `${aniGroup.name}_events`;
  const tmpTarget = createTmpTarget(eventAni);
  aniGroup.addTargetedAnimation(eventAni, {
    name: "events",
    ...tmpTarget,
  });
  return eventAni;
}

function createTmpTarget(ani: Animation) {
  switch (ani.dataType) {
    case Animation.ANIMATIONTYPE_COLOR3:
      return { [ani.targetPropertyPath[0]]: Color3.White() };
    case Animation.ANIMATIONTYPE_COLOR4:
      return {
        [ani.targetPropertyPath[0]]: Color4.FromInts(0, 0, 0, 0),
      };
    case Animation.ANIMATIONTYPE_FLOAT:
      return { [ani.targetPropertyPath[0]]: 0 };
    case Animation.ANIMATIONTYPE_MATRIX:
      return { [ani.targetPropertyPath[0]]: Matrix.Zero() };
    case Animation.ANIMATIONTYPE_QUATERNION:
      return { [ani.targetPropertyPath[0]]: Quaternion.Zero() };
    case Animation.ANIMATIONTYPE_SIZE:
      return { [ani.targetPropertyPath[0]]: Vector3.Zero() };
    case Animation.ANIMATIONTYPE_VECTOR2:
      return { [ani.targetPropertyPath[0]]: Vector2.Zero() };
    case Animation.ANIMATIONTYPE_VECTOR3:
      return { [ani.targetPropertyPath[0]]: Vector3.Zero() };
    default:
      return {};
  }
}
3 Likes

I was implementing right now something really similar lol

Thanks, you gave me some solution.
I was getting an error that will be solved using that createTmpTarget :slight_smile: