Help extracting a batch of animations from a packed animation track

I have many animated NPC models I’ve downloaded where the model has all of its animations packed into one long track.

So usually I have to manually extract each animation one at a time (using @sgold 's Maud editor), which works well but takes a lot of time when I have a model with many animations, since I have to go through frame by frame and re-watch an animation multiple times at a slow speed to determine where each animation starts and ends for 20+ animations per model.

So I’ve only actually done this a few times since it takes a while, and now I have a bunch of NPC models that still need their animations split before I can use them, so now I need to find a more efficient way to do this in bulk.

I recently found out that all of these animated NPC models came with additional files, which includes a .txt file listing every animation with their start and end frame, and also a bunch of individual .fbx files for each animation.

So now I am wondering what would be the easiest way to extract all of these animations in bulk quickly?

Am I better off trying to write some code to extract all of the animations based on the list of start/end frames, or should I try to combine all of the individual animations from the folder containing the individual animations’ .fbx files? (either way I might need some help getting pointed in the right direction to get started)

Thanks in advance for any help :slightly_smiling_face:

1 Like

If it were me, and I wanted to use the separate FBX files then I’d use Blender. If I can’t get it to work in Blender then there is no way I’d get it to work in code.

So, given that, I would go with the txt index route. I think the code is probably not overly difficult. If it gets really tricky then you can always look to see how Maud does it.

…wouldn’t surprise me if Maud already has support for something like this (I’ve never used it I just know sgold is pretty thorough.)

2 Likes

I think Maud only lets you extract one animation at a time (although I could be wrong, hopefully @sgold will correct me if I’m overlooking a feature)

Usually I use Maud’s ExtractTool shown in this screenshot where you can just put the start/end frames and it extracts a new animation:

Now that I at least am at least aware of the fact that these models come with a list of start/end frames that does make it much faster to extract animations one at a time. But that adds up with more animations and especially with more models, so being able to input a list of animations to extract in bulk would certainly be a great feature for Maud (if it doesn’t already exist in the case I’m overlooking something). Especially since I’ve noticed some of the start/end frames in that list are actually off by a frame, so in that case it is very useful to have Maud open to tweak the 1 or 2 animations that usually have an issue.

1 Like

Yes, the Maud editor provides a tool to extract sections out of an animation clip, but as you’ve discovered, it quickly becomes tedious.

Animation editing in Maud is based on the Wes library. If I expected to extract more than a few clips, I would probably write a short application using AnimationEdit.extractAnimation() from the Wes library. Add some code to parse the txt file, and you’d have a versatile tool. For a starting point, see the TrimAnimation example.

AnimationEdit.extractAnimation() uses time (in seconds) to define the start and end of the desired clip, but most of the animated models use a fixed number of frames per second, making it trivial to convert frames to seconds.

Another approach to automating model import would be to use the ECMA scripting (i.e. JavaScript) built into Maud. I haven’t done much with it, but I’m willing to work with you if you’re motivated to make it work.

It’s also possible to import FBX files to JMonkeyEngine. The direct FBX loader in jme3-plugins unfortunately is very out of date; that’s one of the many reasons I’m experimenting with Assimp. I’m told the best route for importing FBX is actually to use FacebookIncubator/FBX2glTF to convert them to glTF, then load the glTF using jme3-plugins.

3 Likes

In my case, I am doing it via a script and Wes Library.

I am using JmeConvert for my asset-importing pipeline which lets me run custom scripts on the models when converting to j3o.

Just in case, here is an example groovy script I am using for splitting and simplifying animations on my animals model pack. Hope you find it helpful :slightly_smiling_face:

package scripts

import com.dalwiestudio.jmeutils.util.AnimUtils
import com.jme3.anim.AnimClip
import com.jme3.anim.AnimComposer
import com.jme3.anim.AnimTrack
import com.jme3.anim.TransformTrack
import com.jme3.math.FastMath
import jme3utilities.MyAnimation
import jme3utilities.wes.AnimationEdit
import jme3utilities.wes.TrackEdit
import jme3utilities.wes.TweenTransforms

/**
 *
 * @author Ali-RS 
 */

// The following bindings are setup by default:
// convert - the JME Convert instance
// model - the loaded ModelInfo.  model.modelRoot is the loaded spatial.
println "Applying ChibiPetsAnim script"

/*
Key Frames:

0 - 5  T-pose
10 - 180  idle_01
180 - 280  idle_02
280 - 380  idle_03
390 - 490  sitting
500 - 600  sleeping
610 - 625  hit
625 - 660  attack
660 - 700  death
710 - 830  wash
830 - 900  eat
900 - 980  happy
980 - 1060  glum
1060 - 1130  voice
1130 - 1190  licking
1190 - 1240  shaking
1240 - 1300  digging
1310 - 1350  walk
1355 - 1395  walk_back
1400 - 1440  walk_left
1440 - 1480  walk_right
1485 - 1505  run
1510 - 1540  jump
1545 - 1585  swim
1590 - 1630  jumpy
*/

AnimComposer composer = AnimUtils.findAnimRoot(model.modelRoot).getControl(AnimComposer.class);
AnimClip animPack = composer.getAnimClip("anim-pack");
TweenTransforms techniques = new TweenTransforms();

extract("T-pose", 0, 5,composer, animPack, techniques);
extract("idle_01", 10, 180, composer, animPack, techniques);
extract("idle_02", 180, 280,composer, animPack, techniques);
extract("idle_03", 280, 380,composer, animPack, techniques);
extract("sitting", 390, 490,composer, animPack, techniques);
extract("sleeping", 500, 600,composer, animPack, techniques);
extract("hit", 610, 625,composer, animPack, techniques);
extract("attack", 625, 660,composer, animPack, techniques);
extract("death", 660, 700,composer, animPack, techniques);
extract("wash", 710, 830,composer, animPack, techniques);
extract("eat", 830, 900,composer, animPack, techniques);
extract("happy", 900, 980,composer, animPack, techniques);
extract("glum", 980, 1060,composer, animPack, techniques);
extract("voice", 1060, 1130,composer, animPack, techniques);
extract("licking", 1130, 1190,composer, animPack, techniques);
extract("shaking", 1190, 1240,composer, animPack, techniques);
extract("digging", 1240, 1300,composer, animPack, techniques);
extract("walk", 1310, 1350,composer, animPack, techniques);
extract("walk_back", 1355, 1395,composer, animPack, techniques);
extract("walk_left", 1400, 1440,composer, animPack, techniques);
extract("walk_right", 1440, 1480,composer, animPack, techniques);
extract("run", 1485, 1505,composer, animPack, techniques);
extract("jump", 1510, 1540,composer, animPack, techniques);
extract("swim", 1545, 1585,composer, animPack, techniques);
extract("jumpy", 1590, 1630,composer, animPack, techniques);

composer.removeAnimClip(animPack);

void extract(String clipName, int startFrame, int endFrame, AnimComposer composer, AnimClip animPack, TweenTransforms techniques) {
    float[] times = MyAnimation.getKeyFrameTimes(animPack.getTracks()[0]);
    float startTime = times[startFrame];
    float endTime = times[endFrame];

    AnimClip extractedClip = AnimationEdit.extractAnimation(animPack, startTime, endTime, techniques, clipName);
    AnimTrack[] oldTracks = extractedClip.getTracks()
    AnimTrack[] newTracks = new AnimTrack[oldTracks.length]; // Simplified

    for (int i = 0; i < oldTracks.length; i++) {
        AnimTrack track = oldTracks[i];
        if (track instanceof TransformTrack) {
            TransformTrack simplified = TrackEdit.simplify(track, FastMath.ZERO_TOLERANCE);
            simplified = TrackEdit.resampleToNumber(simplified, (int) (simplified.getTimes().length * 0.5f), (float) extractedClip.getLength());
            newTracks[i] = simplified;
        } else {
            // Do not simplify
            newTracks[i] = track;
        }
    }

    // Set simplified tracks
    extractedClip.setTracks(newTracks);
    composer.addAnimClip(extractedClip);
}

9 Likes

I asked that question a long time ago on Blender StackExchange, and keep coming back to look at it every now and then. Excellent reply:

3 Likes

Thank you all for the great replies and all the useful information!

I will likely use Wes to make a utility similar to @Ali_RS’s script and adjust it so it reads from the text file with the animation list. It will be a while until I get a chance to do this, but I will post back if I have anymore trouble. Although I think I have more than enough information to figure things out now so I hope it should go smoothly. I appreciate all the help :slightly_smiling_face:

2 Likes

I was finally able to work on this, and everything worked for my first model, but I think I found a potential bug in Wes that appeared after extracting the animations from the next model I tried.

When I convert the model to j3o and load it in the SDK or my own custom editor, it plays the packed “Take 001” animation correctly.

However, after I run my utility to extract animations with Wes, all of the new animations (as well as the old packed animation) become bugged causing the head to implode on the center of the model.

The issue also occurs if I use Maud to load this model from its original .gltf file. And when I tried using Maud to load the .j3o version I converted in the SDK, then Maud crashed with no error. So I think Wes/Maud do not like something about this specific model.

Here’s some screenshots showing the way it looks when its bugged in Maud and my own editor (the top 2 screenshots), compared to how it should look when playing the packed animation in the SDK prior to extracting anything (the last screenshot) :

I am using the latest version of Wes (I think) being Wes 0.7.5, and also tested the model with the same issue in Maud v1.0.0-beta1. I can also upload a copy of this model if that would help to investigate the issue further.

1 Like

That’s an interesting issue. I can look into it tomorrow. Since the issue appears to be model-specific, I’ll probably need a copy of the model, including the .j3o version.

1 Like

I just sent you a message with a link to a google drive folder containing the model I’m having trouble with. (I’d typically post it publicly in case anyone else is curious to mess with models I’ve made, but since this is a paid model from an asset store and I didn’t make it, it would be unethical and likely illegal to share publicly in its fully textured and game-ready form I think)

I uploaded a folder titled “Telluropod Model” that contains the original source files titled “scene.gltf” and “scene.j3o” as well as a version of the model titled “tellurpod_brokenAnimations.j3o” that demonstrates the issue after it has been loaded in Maud or edited by Wes.

I also made sure to place the model with its textures in their correct location in that zipped directory (Models/Enemies/tellurpod/) using the stock pbr shader, so I hopefully shouldn’t cause you any errors loading them like I have before, but let me know if so.

And thanks for your help, its always greatly appreciated :slightly_smiling_face:

1 Like

The model has a number of unusual properties.

For instance, all the tracks in the animation clip are the same length except for one. However, that track isn’t involved in the issue you reported.

The root cause appears to be in TrackEdit.truncate(TransformTrack, float, Transform). If any track components (translation, rotation, or scale) are null, Wes v0.7.5 fills them with identities. This behavior was harmless in the old animation system. However, the new animation system treats null components differently, so this is a bug.

I’ve been itching to publish a new version of Wes, so the timing is good. I’ll implement a fix and report back here when it’s ready for testing.

EDIT: Wes version 0.8.0 is ready for testing.

2 Likes

The issue appears to be resolved now that I’ve updated to wes 0.8.0, thanks for the speedy fix! I’m always impressed by how quickly you manage to solve any issue I’ve had with Maud/Minie/Wes. Thank you for all your troubleshooting support and for the useful libraries you’ve created!

1 Like

You’re welcome.

“The Afflicted Forest” is a cool project, and I’m glad to contribute to it in my small way.

Besides, I really enjoy troubleshooting and tinkering with code!

3 Likes