How play different animation in fbx?

I look into the https://github.com/HeapsIO/heaps/wiki/FBX-Models I can’t seem to find the where you can play example Idle, walk, and other animations.

I am using blender to export them. Not sure there is correct way to setup. Need help on how to code it.

I’m not sure is it the best way but for example I’m using one object and I’m loading animations into this object based on my unit state like (in this case every animation is a separated FBX file):

public var view:Object;

view = cache.loadModel(config.idleModel);

state.observe().bind(function(v)
{
	switch(v)
	{
		case Idle:
			view.playAnimation(cache.loadAnimation(config.idleModel));

		case MoveTo | AttackMoveTo | AttackRequested:
			view.playAnimation(cache.loadAnimation(config.runModel));
// etc...

The only problem here what I don’t know yet - How can we make a transisition between these animations.

Oh that how to play animation.


After looking to the API I found the animation api.

https://heaps.io/api/h3d/anim/Animation.html

The hard part is how the event trigger works. As well bending two animation is tricky.

#newbee
Hello to all,
I am asking myself the same questions.

Are we obliged to fix each animation to separated objects, how to make transitions,
can we “browse” inside the fbx, from the code, to know each animation “name”.

thanks in advance for your help.

It’s a good question, but I think yes you have to separate them. Btw I think it’s a common solution I saw it in unreal engine too and there you can create easily transitions between them.

With Heaps it’s a little bit more tricky, I have tried it several times without luck but now finally works!

How I solved the animation blending finally…
(First of all sorry it’s a little bit long :D)
There is the h3d.anim.Transition class which probably will be the solution. I have tried it several times but I had no luck with this. Let’s check it one more time.
Based on the Transition class I have to pass 3 params: name, anim1 and anim2. Name param looks not interesting - could be anything, anim 1 and 2 should be “from” and “to” animations. Transition class extends the Aniamtions so It looks I have to play this one on the model.
Ok, soo looks easy, let’s try it:

// Create the base model
view = AssetCache.instance.getModel(config.assetGroup + ".idle");
view.scale(config.modelScale);
parent.addChild(view);
		
// Play the idle animation
view.playAnimation(AssetCache.instance.getAnimation(config.assetGroup + ".idle"));
		
// Wait 5 sec and change the animation with a transition
Timer.delay(() ->
{
	trace("TRANSITION START");

	var newAnim = AssetCache.instance.getAnimation(config.assetGroup + ".walk");
	newAnim.onAnimEnd = () -> trace("ON ANIM END");
			
	var animTransition = new Transition("idle-to-walk", view.currentAnimation, newAnim);
	animTransition.onAnimEnd = () -> trace("ON TRANSITION END");
	view.playAnimation(animTransition);	
}, 5000);

Hm there is an error:

You must instanciate this animation first

But if I’m using the current animation it shouldn’t be a problem, or at least I thought. No problem than maybe it’s not possible to reuse this animation. Let’s create a new one.

var animTransition = new Transition("idle-to-walk", view.currentAnimation.createInstance(view), newAnim);

The error is the same. How does it possible?
When I call the playAnimation on the view it will create a new instance instead of using what you passed and it creates clones from the object’s properties which means the anim1 and anim2 will be not what you passed, both of them will be just clones.

public function playAnimation( a : h3d.anim.Animation ) {
	return currentAnimation = a.createInstance(this);
}

Let’s make a hack here, modify the heaps source code, clone the isInstance property too in the Animation.hx’s clone function.

a.isInstance = isInstance;

Now I can revert my first modification, but I have to create an instance from the 2nd animation because it was never created.

var animTransition = new Transition("idle-to-walk", view.currentAnimation, newAnim.createInstance(view));

Great, no more errors but there is no transition between the 2 animations. It changes immediatly from the current animation to the new one just like when you simple call a new playAnimation with a new animation.
What’s next, where is the transition? Basically it was a transition but without blending. You can find one other transition in Heaps - h3d.anim.SmoothTransition.hx. It has 3 params too: anim1, anim2 and speed. Ok it looks this is what I need but what is the speed param? Based on the source code it looks this is the speed of blending but who knows what is the expected value here. Let’s change the h3d.anim.Transition to h3d.anim.SmootTransition and try it with speed: 1000, maybe it means it is 1000ms duration.

Timer.delay(() ->
{
	trace("TRANSITION START");

	var newAnim = AssetCache.instance.getAnimation(config.assetGroup + ".walk");
	newAnim.onAnimEnd = () -> trace("ON ANIM END");

	var animTransition = new SmoothTransition(view.currentAnimation, newAnim.createInstance(view), 1000);
	animTransition.onAnimEnd = () -> trace("ON TRANSITION END");

	view.playAnimation(animTransition);

}, 1000);

The result is the same, maybe there was a problem with the time, let’s check the source code.

blendFactor += st * tspeed;
if( blendFactor >= 1 ) {
	blendFactor = 1;
	onAnimEnd();
}

I see, blend factor is the percentage (0-1) and blending is based on the elapsed time multiply with the speed. Ok than it should be much smaller, try it with 0.01.

var animTransition = new SmoothTransition(view.currentAnimation, newAnim.createInstance(view), 0.01);

No luck, still no transition and one more interesting thing: There is no console log related the transition end. Check again the source code. Now I see the problem, it’s again the view.playAnimation(animTransition); It creates a new instance and during this process the onAnimEnd will be dissapeard. Based on that I have to add the onAnimEnd to view’s currentAnimation right after I call the playAnimation.

view.playAnimation(animTransition);
view.currentAnimation.onAnimEnd = () -> trace("ON TRANSITION END");

Now I have information related the transition end, I can move forward, what’s happened with the transition? Let’s play a little bit with the speed maybe there is a problem with that one. Try it with extreme small and extreme huge values. The result is the same and I just realized doesnt matter the value, the result is the same and I see the console log with the same delay, doesn’t matter is it huge or small value. What’s happening here? Add a console log into SmoothTransition’s update function.

trace(tspeed);

Hm! There is no any log. Check the code again. The SmoothTransition has NO clone function which means when I call view.playAnimation(animTransition); It creates a clone from it but the clone will be just a simple Transition not a SmoothTransition. Okkkey. Let’s create a new clone function in the SmoothTransition class.

override function clone(?a : Animation) : Animation {
	var a : SmoothTransition = cast a;
	if( a == null )
		a = new SmoothTransition(anim1, anim2, tspeed);
	super.clone(a);
	return a;
}

New build, new error.

VM1278:23586 Uncaught TypeError: Cannot read property '_12' of undefined

It looks there is no tmpMatrix in the sync function. I don’t really want to understand it’s behaviour so let’s try to solve it with a simple null check.

// ...
for ( o in objects ) {
	if (o.tmpMatrix == null) continue;
// ...

Amazing! Something is happening! Not perfect but now at least it looks the animation was mixed. Let’s play a little bit with the speed, increase it to 1.

var animTransition = new SmoothTransition(view.currentAnimation, newAnim.createInstance(view), 1);

Much better! Still not the best, There are 2 remaining issues,
1, There are a tons of trace related to on transition end.
2, The current animation jumps a little bit before it starts to blend.

Let’s start with the first problem. Based on the source code it’s normal because.

if( blendFactor >= 1 ) {
	blendFactor = 1;
	onAnimEnd();
}

If this is the expected behaviour it means the transition instance class has to be temporary. After the anim end I have to change the animation to the proper one. Let’s modify my onAnimEnd function.

view.currentAnimation.onAnimEnd = () -> {
	trace("ON TRANSITION END");
	view.playAnimation(newAnim);
});

The first problem was solved, no more unnecessary trace but there is a problem again with the play animation, similiar to the 2nd issue. The problem is because when I call a playAnimation it creates new instances and every animations current frame will be 0 again. Fortunately we can get and set that property. After a little modification here is my code:

// Create the base model
view = AssetCache.instance.getModel(config.assetGroup + ".idle");
view.scale(config.modelScale);
parent.addChild(view);

// Play the idle animation
view.playAnimation(AssetCache.instance.getAnimation(config.assetGroup + ".idle"));

// Wait 5 sec and change the animation with a transition
Timer.delay(() ->
{
	var newAnim = AssetCache.instance.getAnimation(config.assetGroup + ".walk");
	newAnim.onAnimEnd = () -> trace("ON ANIM END");

	var lastFrame = view.currentAnimation.frame;
	var animTransition = new SmoothTransition(view.currentAnimation, newAnim.createInstance(view), 1);

	view.playAnimation(animTransition);
	var currentTransition:SmoothTransition = cast view.currentAnimation;
	currentTransition.anim1.setFrame(lastFrame);

	view.currentAnimation.onAnimEnd = () -> {
		var lastFrame = currentTransition.anim2.frame;
		view.playAnimation(newAnim);
		view.currentAnimation.setFrame(lastFrame);
	};

}, 5000);

I can’t believe, it works well! Let me share the result (idle to walk than walk to attack animations)
ezgif.com-optimize

What’s next? I found a few issues and I’m not sure it’s because I’m using this transition in a wrong way or because it was not implemented well, so I’m creating now a pull request for heaps related these changes and I will ask a little bit more info from @ncannasse maybe there is a better way.

1 Like

[NewKrok]
Thank you for your kind and slightly complicated explanation. That’s super nice.
Before that, I stumble on to a FBX export problem. I can’t export any animation from Blender. I shared my problem on multiple places, the Discord of Blender, of Heaps, Blender artists forum.
Something to do with “Unknown curve AnimCurveNode”
an other user here encountered this problem.
[Lightnet]Is also having the same problem.

I am puzzled !

@tsurubaso I’m using FBX models from asset stores and I never used Blender soo I’m not sure what can be the problem :confused:

Based on the code it looks it happens when the animation curve is not an TRS curve (Transform/Rotation/Scale)

Do you have any animation curve in your FBX ascii with different property names?

Here is an example for T

AnimationCurveNode: 733819952, "AnimCurveNode::T", "" {
        Properties70:  {
            P: "d", "Compound", "", ""
            P: "d|X", "Number", "", "A+",0
            P: "d|Y", "Number", "", "A+",0
            P: "d|Z", "Number", "", "A+",0
        }
    }
1 Like

Hello there, about this problem, I spoke about it on Discord, and one Code Samurai, Yanrishatum san solved it out. He included it into a revision of the source code, I just don’t know how to implement that in my local code, if you do a research on my internet name tsurubaso you will find fast the discussion. I am waiting silently that it is implemented to the source code, meanwhile I are doing basics on HAXE, and finding others bugs in the Armory community.