Splitting Animations

Hi, i have a model and it has animations. But when i import fbx to project only one animation file it’s long include 120 animations.

Then (I have blend file) i import blend file to blender and deal with it some hours. My aim is seperate animations using Blenders Action editor, Nla editor. At last i export fbx again and upload to project nothing changes. I have no blender experiences.

How can i seperate animations programmatically? Or is any simple way do this in blender?

Sample animations
0 - 9 T-pose
10 - 40 walk-01
45 - 75 walk-02
80 - 110 walk-03

I’ve modified the animation system to do this in my code, it isn’t possible with the base animation stuff.

I “think” that all you should need to do to include it in your project is to put this code somewhere and then set .startFrame and .lastFrame on your animation instance or skeleton (either should be fine).

pc.Skeleton.prototype.addTime = function(delta) {
    if (this._animation !== null) {
        this.firstFrame =  this._animation.firstFrame !== undefined ? this._animation.firstFrame : this.firstFrame
        this.lastFrame = this._animation.lastFrame !== undefined ? this._animation.lastFrame : this.lastFrame
        this.last
        var i;
        var node, nodeName;
        var keys, interpKey;
        var k1, k2, alpha;
        var nodes = this._animation._nodes;
        var duration = this._animation.duration;
        if (this._time === duration && !this.looping) {
            return;
        }
        this._time += delta;
        if (this._time > duration) {
            this._time = this.looping ? 0.0 : duration;
            for (i = 0;i < nodes.length;i++) {
                node = nodes[i];
                nodeName = node._name;
                this._currKeyIndices[nodeName] = 0;
            }
        } else {
            if (this._time < 0) {
                this._time = this.looping ? duration : 0.0;
                for (i = 0;i < nodes.length;i++) {
                    node = nodes[i];
                    nodeName = node._name;
                    this._currKeyIndices[nodeName] = node._keys.length - 2;
                }
            }
        }
        var offset = delta >= 0 ? 1 : -1;
        var foundKey;
        for (i = 0;i < nodes.length;i++) {
            node = nodes[i];
            nodeName = node._name;
            keys = node._keys;
            interpKey = this._interpolatedKeyDict[nodeName];
            if(!interpKey) {
                console.warn("No key", nodeName)
                continue
            }
            foundKey = false;
            const length = this.lastFrame ? (this.lastFrame - this.firstFrame) : keys.length
            const start = this.firstFrame || 0
            // If there's only a single key, just copy the key to the interpolated key...
            const indices = this._currKeyIndices
            if (keys.length !== 1) {
                let startTime = keys[start].time
                for (let currKeyIndex = indices[nodeName] || 0, count = 0, l = length - 1; count < l; count++, currKeyIndex = (currKeyIndex + offset + length) % length) {
                    k1 = keys[currKeyIndex + start];
                    k2 = keys[currKeyIndex + 1 + start];
                    if (k1.time - startTime <= this._time && k2.time-startTime >= this._time) {
                        alpha = (this._time - k1.time + startTime) / (k2.time - k1.time);
                        interpKey._pos.lerp(k1.position, k2.position, alpha);
                        interpKey._quat.slerp(k1.rotation, k2.rotation, alpha);
                        interpKey._scale.lerp(k1.scale, k2.scale, alpha);
                        interpKey._written = true;
                        this._currKeyIndices[nodeName] = currKeyIndex;
                        foundKey = true;
                        break;
                    }
                }
            }
            if (keys.length === 1 || !foundKey && this._time === 0.0 && this.looping) {
                interpKey._pos.copy(keys[0 + start].position);
                interpKey._quat.copy(keys[0 + start].rotation);
                interpKey._scale.copy(keys[0 + start].scale);
                interpKey._written = true;
            }
        }
    }
}

You would get the animation with

var anim = this.entity.animation.getAnimation('myanim')
anim.firstFrame = 10
anim.lastFrame = 40
this.entity.animation.play("myanim")

Thanks for the answer. I test it but when i press W key it plays animation. But when hold W key it stopped animation. Is it related to moving entity or yours solution?

Don’t think it’s got anything to do with this code. If you wanted to blend together animations using this it might require a few more skeletons of your own. My full solution has lots of blend trees and a bucket load of skeletons.

If your project is public I can take a look.

Thanks.

https://playcanvas.com/project/500011/overview/alphabet

Character:
http://3drt.com/store/characters/cartoon-characters/chibii-people-kids.html

Hi, ok well a couple of things. First I had a bug or two which I’ve fixed. Next:

  • That list of animations is just wrong. There are 2368 frames in your animation not 7000. This project will help you step through frame by frame and work out where the animations are: https://playcanvas.com/project/503148. Press D and A to move through frames. Hold shift to go fast.

  • You are “Playing” the animation while the key is down, this has the effect of restarting it every frame.

This is the anim code now:


pc.Skeleton.prototype.setFrame = function(currKeyIndex) {
     if (this._animation !== null) {
        var i;
        var node, nodeName;
        var keys, interpKey;
        var k1;
        var nodes = this._animation._nodes;
        for (i = 0;i < nodes.length;i++) {
            node = nodes[i];
            nodeName = node._name;
            keys = node._keys;
            interpKey = this._interpolatedKeyDict[nodeName];
            if(!interpKey) {
                console.warn("No key", nodeName);
                continue;
            }
            currKeyIndex = (currKeyIndex + keys.length * 10000) % keys.length;
            // If there's only a single key, just copy the key to the interpolated key...
            if (keys.length !== 1) {
                k1 = keys[currKeyIndex];
                    interpKey._pos.copy(k1.position);
                    interpKey._quat.copy(k1.rotation);
                    interpKey._scale.copy(k1.scale);
                    interpKey._written = true;
                    
            }
        
            if (keys.length === 1) {
                interpKey._pos.copy(keys[0].position);
                interpKey._quat.copy(keys[0].rotation);
                interpKey._scale.copy(keys[0].scale);
                interpKey._written = true;
            }
        }
    }
};
pc.Skeleton.prototype.addTime = function(delta) {
     if (this._animation !== null) {
        this.firstFrame =  Math.floor((this._animation.firstFrame !== undefined ? this._animation.firstFrame : this.firstFrame)||0);
        this.lastFrame = Math.floor((this._animation.lastFrame !== undefined ? this._animation.lastFrame : this.lastFrame)||0);
        this.last = this._animation.last;
        var i;
        var node, nodeName;
        var keys, interpKey;
        var k1, k2, alpha;
        var nodes = this._animation._nodes;
        this._time += delta;
        var offset = delta >= 0 ? 1 : -1;
        var foundKey;
        for (i = 0;i < nodes.length;i++) {
            node = nodes[i];
            nodeName = node._name;
            keys = node._keys;
            interpKey = this._interpolatedKeyDict[nodeName];
            if(!interpKey) {
                console.warn("No key", nodeName);
                continue;
            }
            foundKey = false;
            var animStart = keys[this.firstFrame].time;
            var animEnd = keys[this.lastFrame].time;
            var duration = animEnd - animStart;
            var time = this._time % duration;
            var length = this.lastFrame ? (this.lastFrame - this.firstFrame) : keys.length;
            var start = this.firstFrame || 0;
            // If there's only a single key, just copy the key to the interpolated key...
            var indices = this._currKeyIndices;
            if (keys.length !== 1) {
                var startTime = keys[start].time;
                for (var currKeyIndex = indices[nodeName] || 0, count = 0, l = length - 1; count < l; count++, currKeyIndex = (currKeyIndex + offset + length) % length) {
                    k1 = keys[currKeyIndex + start];
                    k2 = keys[currKeyIndex + 1 + start];
                    if (k1.time - startTime <= time && k2.time-startTime >= time) {
                        alpha = (time - k1.time + startTime) / (k2.time - k1.time);
                        interpKey._pos.lerp(k1.position, k2.position, alpha);
                        interpKey._quat.slerp(k1.rotation, k2.rotation, alpha);
                        interpKey._scale.lerp(k1.scale, k2.scale, alpha);
                        interpKey._written = true;
                        this._currKeyIndices[nodeName] = currKeyIndex;
                        foundKey = true;
                        break;
                    }
                }
            }
            if (keys.length === 1 || !foundKey && this._time === 0.0 && this.looping) {
                interpKey._pos.copy(keys[0 ].position);
                interpKey._quat.copy(keys[0].rotation);
                interpKey._scale.copy(keys[0 ].scale);
                interpKey._written = true;
            }
        }
    }
};

I’ve added a setFrame method.

1 Like

Thank you for your effort. I think i understand it.

Maybe if I use global variable for triggering animation play and loop only first time when pressing a key and do nothing until keyup then stop it. Movement and animation must work same time on keydown.

But if i can’t do it, i find someone for split the animations on blender and play them separately.

That isn’t going to fix it… if you call “playAnimation” it will restart it even if they are split. The way I do this is to have multiple skeletons running at once and blend between them based on a state. It’s not easy though.

Just have a state machine maybe?

Put the character into walk mode when the key is press (.wasPressed(SOMEKEY)) etc. Then anytime the state changes you’d play a different animation. Use ‘.wasReleased’ to find out when the button is up.

Is it possible reduce the duration of restarting? I mean something like fast transition effect.

if (this.app.keyboard.wasPressed(pc.KEY_D)) {
         this.entity.animation.speed = 1;
         anim.firstFrame = 0;
         anim.lastFrame = 20;
         this.entity.animation.play("girl_anim_blender", "0.2");
     }
    
    if (this.app.keyboard.wasReleased(pc.KEY_D)) {
         this.entity.animation.speed = 0;
     }

Hi.
I add your js it’s ok. But it affect other entity animations in my game. How can i apply this only for my player entity?
Thanks.

I’ve never wanted to do that - what happens to the others?

One of entity has windmill animation not playing. It has no any script but i put Anim.js in scripts folder there’s “pc.Skeleton.prototype.addTime” when i delete this function it plays.

Ah ok, that makes sense. I guess it needs different versions of that for standard animations. I’ll take a look.

For standart animation last frame and first frame is undefined. Default first frame and last frame you give the 0. If i changed the only last frame to forexample 10 if it’s undefined, it’s ok for windmill animation. But if game will have many animation it will take time to deal with it.