Possible bug (or perhaps weirdness) in AnimatedModelProcessor

Topics: Developer Forum
Feb 18, 2007 at 10:38 PM
Hi Dastle,

I've been encountering some weird issues with two out of the 20ish animations I have imported from my .x files. My main .x file has about 15 animations in it. Two of the ones in the middle have a strange behavior where they seemed to "stall". I have been trying to figure out what's going on, so I turned the Animation.Content into a project where I could compile it and see what it was doing.

Anyway, I noticed that AnimatedModelProcessor.SubdivideAnimations grossly overestimates (by a factor of over 2) the length of those two animations. I have been trying to understand how the method does its thing, and it's wrecking my head.

Dastle -- I got permission from the guy who made the .x file to send it along to you if you were willing to look at it for debugging purposes. (I could also send along the animation.xml file as well). If you create a ModelViewer and set it to one of the two animations, you'll see right away what's happening (and I can also send along my test code for the Animation.Content library if you'd like as well.

Would you be willing to have a look at this and see if you can help me diagnose it? This x file was something I used in a previous (Managed DirectX) system, so I know it's not "malformed" in any way. I'd be very much obliged if you would have a look at this. Let me know if (and how) I could send it to you somewhere.
Feb 18, 2007 at 10:44 PM
Also can I just say that I've really enjoyed exploring this library and XNA this weekend!! I am well and truly addicted at this point! :)
Coordinator
Feb 18, 2007 at 11:20 PM
Edited Feb 18, 2007 at 11:28 PM
Sure you can send it. My email is david_astle@hotmail.com. I know the library better than the back of my hand so I can probably find exactly what's wrong in a minute or so.

The SubdivideAnimations method will only be called if there is an xml file associated with the model.

I already have a good idea of what's going on. You probably have a track in your animation for a bone that runs twice as long as the other tracks, but doesn't do much or anything. The processor sets the duration to equal the longest running track.

Edit: Whoops, I misread that you had in fact included an xml file.
Coordinator
Feb 18, 2007 at 11:35 PM
Edited Feb 18, 2007 at 11:37 PM
Anyways, since you are using an xml file, I can tell you what's going on without you sending me the file (although if you do send it I can probably find a solution faster). The SubdivideAnimations method will try to find the nearest keyframe for your animation for each bone track. Since you have stored all your animations inside one big animation, it is possible that the keyframes will not be aligned. Thus, if one of the bones has an animation with no keyframes between your start and end time, the animation will last longer than you intend because it will pad the entire animation to fit the keyframes.

There are lots of ways to try to solve this, but the best way is situational. Since you got these animations to run with managed directx, the best way would probably be to split up the animations in the way that managed directx does. However, I don't know how managed directx splits them up.

Do you know the times for the animation or are you splitting it up by keyframe?

Feb 18, 2007 at 11:45 PM
Edited Feb 18, 2007 at 11:52 PM
David, it's on its way... I will send the xml file as well. Please let me know when you get it - as together with the texture maps it's about 2MB.
Feb 18, 2007 at 11:58 PM
Edited Feb 18, 2007 at 11:58 PM
Also - regarding Managed DirectX, I rolled my own importer, and it resolved (and then interpolated between) every keyframe on a 60fps basis, so it's probably not the right yardstick. I would be happy to send it along to you if you wanted to see the relevant code, but I don't think it would be particuarly useful here. The behavior you describe above is probably what's causing the issue... but I think you could debug it about 1000% faster than I could :)
Coordinator
Feb 19, 2007 at 1:37 AM
Edited Feb 19, 2007 at 7:32 AM
Basically, what I said before:
"The SubdivideAnimations method will try to find the nearest keyframe for your animation for each bone track. Since you have stored all your animations inside one big animation, it is possible that the keyframes will not be aligned. Thus, if one of the bones has an animation with no keyframes between your start and end time, the animation will last longer than you intend because it will pad the entire animation to fit the keyframes."

Is exactly what is happening. in the totterLeft2 animation, the start time is around 5 seconds and end time around 7 seconds. The bone head_front has a keyframe at 2 seconds followed by a keyframe at 9 seconds... so the animation includes both those keyframes in the animation and pads the duration to about 7 seconds instead of two.

Unfortunately the pipeline doesn't give the processor access to the actual keyframe number.
Coordinator
Feb 19, 2007 at 2:07 AM
Edited Feb 19, 2007 at 7:37 AM
Ok, I found a general fix and checked in the code. It works by compacting rogue frames into the time restrictions specified in the xml file. I tested it on your model and all animations run well.

I have a question for you though since you have worked with managed directx a lot :)

How many of these models could you render at once for managed directx before noticing the frame rate drop? In full screen mode running 100 animations drops the frame rate to 37 fps, which is borderline passable. This is actually due to the hardware because you can get rid of all the software animation code and still get this frame rate. I get the feeling that managed directx was faster... maybe I'm wrong.

Of course this model is a bit complex since it has multiple meshes and a 5000 vert skinned primary mesh.
Feb 19, 2007 at 11:09 AM
Yeah, the Mawg is beastly :) I never tried stress-testing MDX with multiple Mawgs, although we once envisioned doing a Lord of the Rings-style affair with hundreds of them storming towards you. I was going to do it by rendering to texture (the way that games like 99 Nights work). But the time to do that never showed up.

If you'd like, I could fire up the old engine one of these nights and give it a try. As you can see here: http://heroicsalmonleap.net/mle/mindbalance/index.html -- speed was of the essense for us. This was about 2 or 3 years ago and we needed to suck down 60fps, which was no problem with the character, his environment, the skydome and a few other bits and bobs.

When you say running 100 animations at once, do you mean 100 AnimationControllers having Update called on them - or 100 characters on screen, each being animated? I've taken to setting Enabled=false on all the AnimationControllers that are not currently active. But I do have a nagging concern about that architecture's scalability. Say I had 50 characters, each of whom had 20 animations. Would that mean instantiating 50*20 AnimationControllers?
Coordinator
Feb 19, 2007 at 11:37 AM
Edited Feb 19, 2007 at 11:49 AM
Just woke up here and don't have much time now but -
I mean having 100 characters on the screen, each being animated.

"Say I had 50 characters, each of whom had 20 animations. Would that mean instantiating 50*20 AnimationControllers?"

Yes, but an AnimationController object is light weight - it only uses up 40 bytes or so. Since their life times are long this is a non factor on Windows. If you have a strong aversion to using that many objects, you can use the IAnimationController interface and make your own controller that just stores a reference to the animation source that you currently want to run.

You might be surprised by this, but the library is currently far, far more restricted by hardware than it is by software. 100 Mawg animated characters would require on the order of 5000 Matrix operations per frame in software computation. My 3 GHz computer can easily handle 50,000 matrix operations per frame at 60 fps, and the xbox has three 3.2 GHz cpus. So, the hardware becomes an issue before even 10% of the cpu power is used up.


Edit: whoops, my #s were a bit off, i changed the post - i meant 5000 matrix operations per frame
Feb 19, 2007 at 11:48 AM
I'm not surprised by this. Our apps were always, always limited by hardware... until we had heaps of signal processing going on.

On a separate note, could I just raise a flag about the decision to rename the AnimationInfo class to Animation? This causes havoc with namespaces. I can't refer to it as Animation in my code which is in another namespace; I have to use Animation.Animation, which looks nasty. I thought AnimationInfo was good. It's actually one of the .NET style guidelines that you're not meant to have a class in a namespace which has the same name as the namespace itself.

Of course, your call at the end of the day, but at one point I got really uptight about following those guidelines in my own code.
Coordinator
Feb 19, 2007 at 11:51 AM
"I can't refer to it as Animation in my code which is in another namespace; I have to use Animation.Animation, which looks nasty."

Yeah, we can't have any of that. I guess I did that without thinking about it enough :) I'll change it back.
Coordinator
Feb 19, 2007 at 11:54 AM
Edited Feb 19, 2007 at 11:55 AM
Alright, one last question before I head out - Do you have any suggestions for the library class/code design? I always end up spending far more time on design than actual implementation, and find it to be quite interesting. I am very glad to have someone experienced in C#/managed directx interested in the library :)
Feb 19, 2007 at 11:57 AM
Edited Feb 19, 2007 at 11:57 AM
Just updated my code and those two animations are looking great now! Great update - thanks!!

I am still having issues getting the root node to integrate properly. For instance, try getting him to play his walk animation, followed by standToTotterRight1. It should be perfectly smooth. I need to figure out how to change the World transform (or however else) after his walk cycle (animation called "walk"), so that "standToTotterRight1" (or "standToTotterLeft1") can subsequently play without glitching.
Feb 19, 2007 at 11:58 AM
Yes - I will drop you a mail about class design. I also have to head out so chat later on!
Feb 19, 2007 at 3:20 PM
Edited Feb 19, 2007 at 3:32 PM
Just so you know, here's what I'm doing right now after the walk cycle, where animInfoStart and animInfoEnd are both the Animation (nee AnimationInfo) for the walk cycle. Works fine when just walking along, but when I try to then play standToTotterLeft1 after a walk, it glitches.

Matrix start = animInfoStart.AnimationChannels"root"0.Transform;
Matrix end = animInfoEnd.AnimationChannels"root"[
animInfoEnd.AnimationChannels"root".Count - 1].Transform;

Matrix startInv = Matrix.Invert(start);
Matrix totalTransform = end * startInv;

Matrix totalTransformTranslation = Matrix.CreateTranslation(totalTransform.Translation);

mawgAnimator.World = totalTransformTranslation * mawgAnimator.World;

Coordinator
Feb 19, 2007 at 5:29 PM
You should use the inverse of the start of the target animation (like standToTotterLeft1) as startInv. Still has a miniscule glitch if you do that, but I gotta go for a few hours atm.
Feb 19, 2007 at 6:46 PM
Ah - good idea. I will experiment with that in a bit myself. Have to run to dinner now. Took a break for an hour to add audio to the game. Very impressed with the simplicity of XNA's audio pipeline.
Coordinator
Feb 19, 2007 at 9:40 PM
Edited Feb 19, 2007 at 9:40 PM
I found a simpler solution. To go from walk to totter:

mawgAnimator.World = mawgAnimator.World;

Hehe...
Coordinator
Feb 19, 2007 at 10:04 PM
Edited Feb 19, 2007 at 10:07 PM
There was also bug in AnimationController.AnimationEnded, due to update order (which I fixed in the last checkin).

Excuse these pesky bugs; the library was completely overhauled about 2 weeks ago and hasn't gone through any external testing till recently.

Walk to walk and walk to totter should work without any glitching now.
Feb 19, 2007 at 10:55 PM
haha.. yes, I was thinking about it tonight and realized that World=World would work for that one. So you're right, from what you propose above - what I need to do isn't so much to integrate any change that happens along the animation that just occurred -- but just to make sure that when I start the new animation, the root node hasn't moved.

No problem about the pesky bugs, I am happy to help firefight these whenever possible. And delighted that this is coming along so quickly! I was staring down the barrel at writing something like this myself, and it was keeping me off really diving into XNA. I know I owe you a longer message on library design, but for now suffice to say I'm very impressed by what you're doing, and I am happy to help however I can to progress and polish it.

Just to confirm - did you do another check-in today? Should I get the latest version?
Coordinator
Feb 19, 2007 at 11:13 PM
Edited Feb 19, 2007 at 11:14 PM
I indeed did another check in. You should probably get the latest version if you are using the AnimationEnded event.