How to use AnimationClip.CreateFromMorphTargetSequence to create animation for standard model

In my application, I have a standard gltf model of a face that has a few dozen morph targets as part of the model. Users generate these models - they do not have any animations baked in. I want to animate the face to make a smile.

It looks like AnimationClip.CreateFromMorphTargetSequence is capable of what I’m trying to do - give a starting keyframe and an end keyframe of all of the morph target values, and make an animation clip from those, then play the clip. I can’t figure it out for the life of me though, and it doesn’t look like the source really matches the types. The types say that morphTargetSequence is an array of objects which each have an array of Vector3 vertices, but there’s nothing named vertices in the source.

Is it possible to do what I’m trying to do? My naive assumptions was it would work something like this. Here I’m setting a single blend shape/morph target’s value to 1 for a single keyframe, then back to 0.

const clip = AnimationClip.CreateFromMorphTargetSequence(
  "winking",
    [
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    ],
    [
      0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    ],
    [
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    ],
  ],
  30,
  false
)

I ended up writing my own version. I’m pretty sure AnimationClip.CreateFromMorphTargetSequence is broken, the values it makes are literally just [0,1,0]:thinking: And it’s also doing some extra sorting that you don’t need to do if your times and values are already sorted.

Here’s my simple version:

import { AnimationClip, NumberKeyframeTrack } from "three"

export interface MorphTargetSequence {
  name: string
  times: number[]
  values: number[]
}

export function createMorphTargetSequenceAnimationClip(
  name: string,
  morphTargetSequences: MorphTargetSequence[],
  fps: number
) {
  const tracks = morphTargetSequences.map((sequence) => {
    return new NumberKeyframeTrack(
      ".morphTargetInfluences[" + sequence.name + "]",
      sequence.times,
      sequence.values
    ).scale(1.0 / fps)
  })
  return new AnimationClip(name, -1, tracks)
}

And using it looks like this, for a one-shot animation of blinking twice:

const blinkClip = createMorphTargetSequenceAnimationClip(
  "blinking",
  [
    {
      name: "eyeBlinkLeft",
      times: [0, 1],
      values: [0, 1],
    },
    {
      name: "eyeBlinkRight",
      times: [0, 1],
      values: [0, 1],
    },
  ],
  30
)
const animMixer = new AnimationMixer(skinnedMesh)
animMixer.clipAction(blinkClip)
  .setLoop(LoopPingPong, 4)
  .setDuration(0.15)
  .play()