Loading animations dynamically from JSON

I am wondering if anyone can help me with this. What I would like to do is stringify individual animations, and save them to the server in mysql or as files, that can be called up dynamically.

What I have is code like this… it loads the model… it works… the animation plays fine.

loader.load( ‘models/new/myAvatar.glb’, function ( gltf ) {
loader.load( ‘models/new/default_anis.glb’, function ( defanis ) {
var avatar=gltf.scene;
animations = defanis.animations;
/// ^^ This is an avatar that loads, works, animations are working…

What I want to do…

var theSavedAnimation = JSON.stringify( animations[0] );

//… does stuff to store the string and calls it back later…

animations[0]=JSON.parse( theSavedAnimation );

If I do this … just stringify… then immediately parse the same string. The animation is not usable. If I console.log and compare… all the data is there. But I am thinking it is coming back as a structure that the mixer does not like?

Here is the error:
TypeError: a[e].createInterpolant is not a function at new Kf (three.min.js:297) at be.clipAction (three.min.js:863)

The way I am doing animations now. I have several avatar characters with the same skeleton. And I export them as GLTF, with one small animation. Then I have another master avatar, that has the entire collection of animations.

So I copy…
animations[0] = masterAvatar.animations[2];

and that system works fine… but I will potentially have hundreds, and I need to load them dynamically from a URL later. I can’t load them all at startup, the way I am doing it now.

Any insights would be appreciated. :slight_smile:

Each animation is a THREE.AnimationClip instance. JavaScript’s JSON.stringify() is really only useful for primitive objects with keys and values, not for instances with methods and behavior and inheritance. Instead, use:

var jsonObject = animations[ 0 ].toJSON(); // creates a primitive object with basic animation data
var jsonString = JSON.stringify( jsonObject ); // serializes the object as a string

To recreate the AnimationClip later, you’ll need to use var clip = AnimationClip.parse( jsonObject ).

Hmm. As far as I can tell. That is what I am doing. I am copying the animation data, and starting a new instance with it.

If I do a hot-swap…

animations[0] = fromAnimations[whatever];

… and start a new clipAction instance from it… it works fine. It is the process of stringify and un-stringify that seems to produce a different type of array structure from the original, that seems to be the problem. Maybe I will have to really look at how the gltf loader constructs that animation data. I’ll keep hacking at it. I have just wanted to keep my mitts off of the core code, so I can keep getting updates from you guys. :slight_smile:

animations[ 0 ].toJSON();

Oh and I tried that… It doesn’t work. The animation doesn’t have a .toJSON method like the other objects do, unfortunately. I was thinking toJSON() would make the difference.

Could you show your complete code or a demo, including toJSON()? You’ll definitely need toJSON(), and if the method isn’t there something is wrong…

Thank you Don for getting back to me. I really love these forums and how helpful people are :slight_smile:

The problem turned out to be the createInterpolant function was not being stringified, so when I over-wrote the “tracks” part, the function was lost.

I got it working somewhat, by iterating through the animation, and just replacing the times and values. But found my approach is fraught with all kinds of other perils. I think what I need to do is write a function to re-target, so I can use the normal BVH loader, and it will match the bones in the BVH to the ones in the Makehuman skeleton. I spent about 10 hours trying to avoid 3 or 4 hours of work. lol

1 Like

Did you succeed doing that? :slight_smile:

Hi @donmccurdy,

I got the same error here with ani.toJSON()

m.animations.forEach((ani) => {

   var jsonObject = ani.toJSON();
   // var jsonString = JSON.stringify( jsonObject );
   var clip = new THREE.AnimationClip.parse( jsonObject );
   console.warn(clip);
   obj.animations.push(clip);

});

Am I wrong?

I also experienced the error:

createInterpolant is not a function

when I had accidentally forgot to use AnimationClip.parse and instead was passing the JSON object directly instead of a real AnimationClip instance.

I was able to get it working using AnimationClip.toJSON and AnimationClip.parse:


// Find your clip named "SomeClipName"
const origClip = THREE.AnimationClip.findByName(animations, 'SomeClipName');

// Convert to JSON object, stringify, and parse
const clonedClipJSON = JSON.parse(JSON.stringify(origClip));
// OR, this also works although it is not necessary:
// const clonedClipJSON = JSON.parse(JSON.stringify(THREE.AnimationClip.toJSON(origClip)));

// Parse with optional changes applied, in my case I had a method "applySomeChange"
const clipWithoutPosition = THREE.AnimationClip.parse(
  applySomeChange(clonedClipJSON)
);

// Bonus
// I'm personally using React-Three-Fiber and drei
// See "useAnimations": https://github.com/pmndrs/drei#useanimations
const { actions } = useAnimations([ clipWithoutPosition ], group);
actions['SomeClipName'].play();

Thanks for starting this post and recommending AnimationClip docs: three.js docs
I hope my code snippet is able to help others who get to the finish line :slight_smile:

1 Like