Exporting animated dummy (transform nodes) from Max

I would think this possible, but having trouble.

Can someone point me to a workflow to export animated transformNodes (keyframe animated), created in 3DS max, so that the animation can be played in a scene. Both the exporting part and the code for calling the animation.

I would assume the workflow would be similar for Blender so any help is good help!

This should be super simple, but for the life of me can’t find an example.

Pinging @drigax

For exporting from 3dsMax, we have documentation on the Max2Babylon tool, and the different options:
https://doc.babylonjs.com/resources/3dsmax#the-exporter-window

In your case, you may or may not want to use the “export animations only” option to ensure that you are only exporting a glTF scene full of animated dummy nodes.

Once you import your glTF, you can play the animation group/groups imported via something similar to:

animationGroup.play(true);

as the glTF animations will be imported as animation groups. Here’s a similar example from the babylon resources that invokes these animations after creating the groups manually, but the syntax should be similar to invoke glTF animations.

https://www.babylonjs-playground.com/#CBGEQX#1

1 Like

How do I know what animationGroup is targeted with animationGroup.play()? or for that matter, is there a way of queing multiple animations into assetManager to better instance / speed up things at runtime.

I have been playing with this scene and a few like it in the demos to gain a better understanding

https://www.babylonjs-playground.com/#UGD0Q0#2

but don’t see the link between a specific animation (the character) and where that gets linked to the imported gITF mesh loaded.

So this checks to make sure animationGroup is loaded and if so grabs whatever one happens to be in the array, but I would ideally love to call specific animations depending on circumstance.

if (scene.animationGroups.length > 0) {
scene.animationGroups[scene.animationGroups.length - 1].play(true);
}

Hope that makes sense…Sorry the extent of my animations to date has been basic property (position.x+) movements…

Totally valid questions, unfortunately I’m not sure if I have the best solution:

https://www.babylonjs-playground.com/#UGD0Q0#23

You can try my pattern in this playground, where I hook into the animationGroup’s loop event (or end event if you have non-looping animation groups), then import the other animation.

I’ll see if I can load the animations cleanly, I don’t think this is the best way of handling importing or animation sequencing, ideally we should load once, then configure the animation groups to play each other on loop callback.

the animation group that we have the reference to is what gets played when we call <AnimationGroup>.play();

@Calsa, here’s an improved playground:
https://www.babylonjs-playground.com/#UGD0Q0#27

In this playground I was able to append the animations to the scene, our animation importer then retargets the loaded animation to any existing animated meshes in scene, and we’re off and working with both animations.

If you’re a code buff, you can always check out the retargeting implementation here, we call this as a result of BABYLON.SceneLoader.ImportAnimations (It’s important to specify NOT using BABYLON.SceneLoaderAnimationGroupLoadingMode.Clean for this usecase, as it will remove any animation groups in scene!):

Our default implementation searches the scene for any bones or nodes that matches the imported animation’s target, so multiple clips exported from the same rig should work seamlessly, I’m not sure how well multiple objects in scene with the same rig would work however.

We expose a callback to implement your own retargeting function as well, in order to handle such cases.

Afterwards, similarily to the previous playground, I was able to hook into my animation group’s end event, and trigger the next animation I wanted to sequence. So, we’re able to go from run->death.

@Deltakosh i did notice that my animations no longer looped properly after changing my import method, did I do something wrong here?

But no way to say make scene.animationGroups[scene.animationGroups.length - 1] an associative array? So scene.animationGroups[“idle”] or something? Idle correlating to the animation groups I build in max (see below). Do those names I give them mean anything, or is that just for artist reference? They seem to transfer in when inspecting the imported node, but I don’t know how to control them.

Likewise I am still not following where you are binding the animation to a specific mesh in the example when loading animations as external gITF.

I have many instanced meshes in an assetManager array. I then clone the meshe and play either that same animation playing on the source mesh, or a different animation saved within the gITF, depending on conditions, at least that is the dream.

The meshes currently load fine into assetArray, and the initial mesh autoplays it’s animations fine. It’s when I clone the mesh, the animations don’t clone too. That seems to be normal behaviour, but I don’t know how to hook it back up so a clone plays the animations and clone.animation = source.animation or clone.animationgroup = source.animationgroup did not seem to work.

Sorry, having a noob moment, but this can’t be that hard. I don’t know how I missed this in the docs and existing playgrounds.

      The cutscene attempt uses associative arrays, I will play more...

fanRunning = scene.getAnimationGroupByName(“startFan”);

1 Like

I guess in simple terms…
How do I add a transformNode, to an existing animationGroup?

There has to be a way to copy the animations[] from the copy in assets array, to the copy on screen or otherwise add a transformNode to an animation.

I’ll see if I can unpack this:

Likewise I am still not following where you are binding the animation to a specific mesh in the example when loading animations as external gITF.

I have many instanced meshes in an assetManager array. I then clone the meshe and play either that same animation playing on the source mesh, or a different animation saved within the gITF, depending on conditions, at least that is the dream.

The meshes currently load fine into assetArray, and the initial mesh autoplays it’s animations fine. It’s when I clone the mesh, the animations don’t clone too. That seems to be normal behaviour, but I don’t know how to hook it back up so a clone plays the animations and clone.animation = source.animation or clone.animationgroup = source.animationgroup did not seem to work.

Let’s try to forget the concept of “binding an animation to a mesh”, as its a little high level for what’s happening here.

Animations can only be applied to nodes (and morph target weights). When you import your mesh, your rig and is composed of of nodes, which the mesh is skinned and/or parented and that is what these animations target. Looking at the API, we can see that our AnimationGroup is composed of TargetedAnimations, which are composed of an Animation track and a target node this poorly drawn diagram represents the rough concept of what data is associated here in the glTF we import and how we represent this in Babylon:

Each Targeted animation is associated to a node in the scene that it targets. You can only have a single target per TargetedAnimation.

When we load our animation from the glTF into the scene, the resulting AnimationGroup in scene has a similar structure, the only difference now is via ImportAnimations/mergeAnimationsTo, we retarget the Animation tracks to nodes in the scene. The default method we use is:

let _targetConverter = targetConverter ? targetConverter : (target: any) => { return scene.getBoneByName(target.name) || scene.getNodeByName(target.name); };

Which searches the scene for the first node that matches the name of the original target in the glTF, and assigns it as the new target for the animation track. We would then see something like this after using the default retargeting implementation:

All of our animations retarget to the first matching node we find in the scene tree!

This constraint on the TargetedAnimation and AnimationGroup makes it so that a single TargetedAnimation CANNOT be shared between multiple nodes. For your case, you may need to manually duplicate your animation group and TargetedAnimations, and implement your own retargeting method to then target the nodes on your other meshes such that each mesh/rig has its own animation group for a particular action. You can also import the same animation file multiple times, and implement your own retargeting callback for greater control of the retargeting behavior. (parameter targetConverter of SceneLoader.ImportAnimations: SceneLoader - Babylon.js Documentation)

3 Likes

Well you earned your title as Babylon legend today.
Well explained and loving the visuals.
I’ll see what I can do, seems odd no one has created that functionality since Babylon has been around for years, guess I will see if I am up to the task :worried:

2 Likes

I agree, I think this is a common usecase in games where one would want to have a shared “library” of animations to share between characters that have the same rig, like easily populating a crowd, or having a customizable clothing system, and the current implementation doesn’t quite make it very easy or intuitive to do so.

Maybe we can improve this by tightening up the default retargeting implementation to instead take a given node as an input, and reduce the search space to only children of that given node? In this example, we can instead import the animation twice, and provide the roots of elves 1 and 2 to our retargeter? As another spitball, we might want to have a standalone method in AnimationGroup that instead does the retargeting given a retargeting function like in the SceneLoader entrypoint?

If you’re interested in approaching this, we always welcome and would be happy to help with a PR :slight_smile:

I think being able to que them into AssetManager class, with a very easy method to target a top level node/mesh container instance/clone, automatically search children for nodes of matching name structure, then auto binding animations to nodes that match a structure would be logical. Though even just a simple remap targetNode function would suffice.

I already do this for how I am building my scenes, where I have a top level gITF that is basically only specifically prefixed transformNodes to denote assets to auto pull from my assetManager (_tn = one class containing major mesh asset, .nk = callouts, fx = particle fx, then others). When you load the top level scene (in pic below drots_root) it starts a recursive scan for prefixed transformNodes of that initial gITF, then searches for, instances and parents corresponding items from my assets array. So the prefix… tn_razana from master scene will auto load the razana asset in my assetManager. This is recursively done on all gITF loaded until no new prefixed transform nodes are left to populate. This way the entire scene can basically be built and animated in blender/max very quickly by just animating a few top level transformNodes, while allowing me to instance meshes / materials / ext. Only problem right now is the animations themselves don’t survive my instancing.

I’m going to dive into trying and figuring that out tonight / tomorrow/weekend as I need a better system. I don’t know if I what I hack will be up to par for committing though, my code tends to stay in my own classes, I hate to break Babylon :stuck_out_tongue: . I can write whatever I need to for the most part but it may not be the pretty enough to commit. We will see.

1 Like