Game Development Community

[1.1b] Sequences in character with different proportions (notquiteabug) - LOGGED

by Manoel Neto · in Torque 3D Professional · 02/26/2010 (11:41 am) · 28 replies

When porting the game I'm working on from beta 5 to 1.1 beta, my characters look strange when animated. They are using sequences which were designed for a character with different body proportions (aka: different node positions) and T3D 1.1 just decided to use translation animation for all nodes, which overrides my character proportions and make them look shorter/taller/stumpier/etc. Digging through the code, I found this is actually done on purpose by new code in TSShape::addSequence():

...
else if (defaultTranslations[nodeMap[i]] != srcShape->defaultTranslations[i])
{
   seq.translationMatters.set(nodeMap[i]);
   S32 dest = seq.baseTranslation + seq.numKeyframes * seq.translationMatters.count(nodeMap[i]);
   for (S32 j = 0; j < seq.numKeyframes; j++)
      nodeTranslations.insert(dest, srcShape->defaultTranslations[i]);
}
...
This is adding translation keys to the animation when the target shape and the source shape have different default translations (bind poses). I don't know why someone would want this, since it prevents animations from being re-used on characters which use joint positioning to change skeleton proportions over different characters. I commented it out and now my characters look correct.

Right bellow there is another code block that does the same for rotations, which I actually found useful because it actually fixes incoherences with joints which don't animate in a sequence and makes sure the character is positioned as it should in the animation without changing body proportions.
Page «Previous 1 2
#1
02/26/2010 (2:58 pm)
Manoel, could you elaborate on this: characters which use joint positioning to change skeleton proportions over different characters

thx
#2
02/27/2010 (9:24 pm)
@Eb: sure. Let's say you have a character model, complete with a skeleton and animations. Now you want to make a slightly different character with wider shoulders and shorter legs than the first one. The original character's skeleton won't fit on the new body, and you need to move the joints a little to make it fit. But since you're using the same hierarchy, animations made for the first character should map on the new character: if the animation contains only rotation keyframes, only the joints rotations will animate and their positions will remain the same as the one stored in each character (which is different for each character).

The problem is that the loader is creating translation keyframes when loading an animation where the "bind pose" (the default non-animated skeleton pose) differs between source and target shapes. These keyframes contain the translation from the source animation, and will override the translation in the target shape.
#3
02/28/2010 (10:53 am)
Why would anyone create that on purpose ? I can not think of a sane reason as to why someone would force that.

Perhaps the code author could give us some insight on the decision...if they read this post.
#4
02/28/2010 (1:55 pm)
Quote:Why would anyone create that on purpose ? I can not think of a sane reason as to why someone would force that.

For exactly the same reason Manoel found the auto-adding of rotation keys useful. The code is there to handle the case when the bind-pose (node rotation AND translation) of the original shape does not match that of the animation data. In Manoel's case, he wants to ignore translation mismatches, but handle any rotation mismatch (eg. if one character was in the T pose, and another had arms at the side).

In the bug report that prompted this code change, the user needed both rotation and translation mismatches handled (IIRC the character origin was animated to have it crouch down or something).

There's a good argument to be made that control of both should be be made optional via the collada import gui.
#5
02/28/2010 (3:06 pm)
Thanks for the explanation/POV.
- While I now understand the reasoning, I think that it hurts more people than it helps. Then again, my opinion is of no bearing to this. ;)

I am happy that Manoel shared this and that you see the argument for both. I have not had the opportunity to track through the T3D source just yet...so these little insights are quite intriguing to me.

thx
#6
02/28/2010 (4:54 pm)
As Chris pointed out, having an option for both is the better choice.

I fully understand where the original reported came from: when a node's translation or rotation doesn't change during the animation, the translation and/or rotation of the destination shape is retained. This was actually a convenient way of setting up layered animations, but caused unexpected results when bind poses are different.

I actually experienced problems similar to the "crouch animation" example where an animation where the character root node was offset form the original position, but did not animate, caused the character to be in the incorrect position when animated. I remember having to get around this by introducing very small translation movement to the animation.

However, having T3D silently generate keyframe data from where there is none is not a good solution for overall usage. As example, this would make it impossible to load only facial animations, since T3D will generate keyframes for the entire body as well. This should be opt-in, and node-based (as in the crouch animation example, only the origin node would need translation keys, not the entire model).

The best way to implement this would be adding a "Node animations" pane in the "Sequences" tab. It should list all nodes contained in the selected sequence and have 3 check boxes for each node: "T" (translation), "R" (rotation) and "S" (scale). If a node contains only rotation keys on the selected sequence, only the "R" box would be checked. Checking the "T" box would make T3D generate translation keys (only if they are not present) and set the "translation matters" flag for that node.

Unchecking a checked box would only set the translation/rotation/scale bits without deleting the information so it isn't lost, but can be ignored. This can be useful for disabling animation in selected nodes, which is good for creating animations that affect only part of the hierarchy (like facial animations).
#7
04/07/2010 (5:55 am)
Excellent, excellent suggestion Manoel. I use a lot of blends; the feature you suggest would be really helpful.

There is also some masking in the player class for spine nodes/etc, would be good to add an interface to take advantage of that as it allows early-outs without running through the entire bone hierarchy, would be really useful on rigs with a lot of bones or for over-riding animations.

I think a testing framework would be extremely helpful in catching this stuff before release. Of course this is visual and would prob not be caught in an automatic test, but a single run with a pre-built journal and assets built specifically for testing would have caught this. Hopefully the QA work with FullSail will put something like this together.

Great catch Manoel... your posts are very valuable!
#8
04/29/2010 (9:58 am)
I have an apparently related issue.

As Manoel noted, the approach of arbitrarily adding keys that are not intended to exist causes overlapping animation threads to function incorrectly. And, yes, facial expressions and body animations in separate threads just got thrown out the window with this change.

Anyway, Manoel's solution of commenting out the code in TSShape::addSequence() is not a complete fix. Does anyone have more info on what changes were made to the animation system from Alpha to Beta? Changelog? Everything is broken, need help!
#9
04/29/2010 (11:37 am)
I've even tried copying in the "ts" directory from the Alpha (then fixing all of the various compile errors that resulted from lots of definitions moving between cpp files).

No luck, facial animations are still garbled because the model (based off of Player) has an idle animation (needed). I tried bringing in the Alpha version of Player, again, no help.

Something else must have changed related to animation...
#10
04/29/2010 (12:41 pm)
@Aaron: did you comment the code block that generates the rotation keys as well?
#11
04/29/2010 (12:49 pm)
@Manoel, Thanks for the help

Yes. Also tried every combination of those 2 blocks (un)commented, just in case I was misinterpreting their functionality.

Like I said, I've also essentially reverted the ts functionality back to the Alpha and that didn't solve the problem.

I reconfirmed that the same models, animations, and scripts work fine in Alpha.

I have to think that there were add'l code changes that are impacting this.
#12
04/29/2010 (3:16 pm)
There's definitely something else going on. If I take a lip sync sequence (which I'm told by the artist only includes facial bones) exported from a standing character, and throw it on a sitting character (same skeleton), it makes the sitting character stand up. As though nodes not included in the animation are being included when the sequence is generated.

I'm 90% sure we ran similar tests w/ the Alpha and did not have this problem. If there are no other known changes to the animation system, maybe changes to the Collada loader?
#13
04/29/2010 (9:53 pm)
@Aaron: Your last test (with the sitting->standing character) sounds like you have not commented the correct blocks of code. The relevant section of TSShape::addSequence should look like this:

if (seq.translationMatters.test(nodeMap[i]))
{
   S32 src = srcSeq->baseTranslation + srcSeq->numKeyframes * srcSeq->translationMatters.count(i) + startFrame;
   S32 dest = seq.baseTranslation + seq.numKeyframes * seq.translationMatters.count(nodeMap[i]);
   dCopyArray(&nodeTranslations[dest], &srcShape->nodeTranslations[src], seq.numKeyframes);
}
/*else if (defaultTranslations[nodeMap[i]] != srcShape->defaultTranslations[i])
{
   seq.translationMatters.set(nodeMap[i]);
   S32 dest = seq.baseTranslation + seq.numKeyframes * seq.translationMatters.count(nodeMap[i]);
   for (S32 j = 0; j < seq.numKeyframes; j++)
      nodeTranslations.insert(dest, srcShape->defaultTranslations[i]);
}*/

if (seq.rotationMatters.test(nodeMap[i]))
{
   S32 src = srcSeq->baseRotation + srcSeq->numKeyframes * srcSeq->rotationMatters.count(i) + startFrame;
   S32 dest = seq.baseRotation + seq.numKeyframes * seq.rotationMatters.count(nodeMap[i]);
   dCopyArray(&nodeRotations[dest], &srcShape->nodeRotations[src], seq.numKeyframes);
}
/*else if (defaultRotations[nodeMap[i]] != srcShape->defaultRotations[i])
{
   seq.rotationMatters.set(nodeMap[i]);
   S32 dest = seq.baseRotation + seq.numKeyframes * seq.rotationMatters.count(nodeMap[i]);
   for (S32 j = 0; j < seq.numKeyframes; j++)
      nodeRotations.insert(dest, srcShape->defaultRotations[i]);
}*/

You could also have a quick look in the DAE file to confirm that it does not contain any animation keyframes for unwanted bones.
#14
04/30/2010 (5:34 am)
@Aaron: after commenting out the code blocks, you *must* delete your cached.dts files, otherwise nothing will change!
#15
04/30/2010 (6:51 am)
@Manoel: Yes, did that. Started from a fresh version of the Beta (w/ relevant code fixes), brought over my scripts and collada models & animations.

@Chris: Looks just like the code you posted.

I looked at a few of the collada files and they contain an initial matrix for all bones, but only have keyframes for the facial bones. My understanding is that this is expected behavior for the collada exporter, and (if I have those 2 blocks of code commented out) the importer will ignore the bones without keyframe data. Does that sound right?
#16
04/30/2010 (7:23 am)
Ok. I think I have 2 issues here.

1) Keyframe position/rotation is not being made relative to the root. I had an animator re-export the animation with just the jaw bone and just rotation. Playing this animation causes the head bone (jaw's parent) to completely flip out. The export pose of the character (sitting or standing) had no effect on this behavior. Standing is the bind pose, so it was not a matter of the bone returning to bind pose.

2) Concurrent threads do not animate correctly. This is in-game (I don't know how to run 2 threads in shape editor) - Independent of the export pose of the character, when I try to play one of the facial animations (neck bone and up) concurrently with the character's ambient animation (spine03 and down), I do not get the correct facial movements. This was the problem supposedly related to the new code in the Beta.


This morning we've been doing testing in Alpha and Beta:

If we take the jaw-only animation from the standing position and throw it on the sitting character in the Alpha, it works fine. Throw it on the sitting character in Beta, and it spins his head around.

Take the jaw-only animation from the sitting position and throw it on the sitting character in the Alpha and it works fine. Throw it on the sitting character in the Beta and his head spins around.

The only thing that is working in the Beta is to export all of the bones in the animation. I swear that code is commented out! There must be something else...
#17
04/30/2010 (8:07 am)
Ok, here's an update and a shorter post.

If I load the dae in the Alpha version, copy over the cached.dts into my Beta, and then load the cached.dts in the shape editor in Beta, it solves issue #1! So there is definitely something screwy w/ the collada and/or shape loader in the Beta (again, the supposedly offending code was removed).

I still have issue #2 - interpolation of concurrent threads is not working correctly.
#18
04/30/2010 (3:03 pm)
Heh, so this is interesting:

Issue #2 doesn't seem to be an issue if you use sequence priorities correctly :-P. Concurrent sequences that are same priority seem to conflict in the Beta. Without setting any priorities, concurrent sequences worked fine in Alpha. That may be the "other code" that I've been looking for.

Issue #1 is the real issue. Something changed in the collada or shape loader from Alpha to Beta. The solution for us is to load the dae in Alpha and copy over the cached.dts to the Beta.

In any case, everything is working again, so I'm happy.

Manoel and Chris, thanks a ton for the help and suggestions
#19
05/04/2010 (4:01 pm)
Quote:Issue #2 doesn't seem to be an issue if you use sequence priorities correctly

This is very strange. Sequence priority should only be relevant when two threads are trying to animate the same node. In that case, the sequence with the highest priority, or the last added thread if priorities are equal should animate the node.

So for example, you could have a root animation that animated the whole shape, then play a facial only animation on top (same priority) and that should work fine. Playing the root animation on top (unless it had lower priority) would obviously mask out the facial animation in this case.


I know you have a workaround now, but is there any chance you could send me the files that were causing problems in beta1? I can have it all fixed up for the next beta, but it's hard to tell what is wrong without a good test case.
#20
05/05/2010 (10:41 am)
I'd like to, but I'm unable to send you assets or code :-(. I'd certainly appreciate if the use cases you described in paragraph #2 were tested more, because I'm not sure the beta functions as described.

We had Sequence A w/ default priority that animated the whole skeleton and *later added* Sequence B w/ default priority that animated only the face. This worked in Alpha but did not work correctly in Beta.

However, it worked correctly in Beta if Sequence A was lower priority than Sequence B.

If I have some free time, I can try to mockup a similar set of assets that you could test, but I can't promise that will happen.


Slight change of topic:

I noticed you said "masked out"... you don't mean that a higher priority animation prevents a lower priority animation from playing at all do you? Why isn't Torque using a hierarchical animation system like many other game engines... and if you're not, then why universally have priorities? (if the Player class wants to randomly pick a sequence based on priority, that functionality should not exist higher up).

A hierarchical animation system would fit with the universally used paradigm of the hierarchical skeleton.

At run-time the engine only has to: (1) Sort active sequences by ascending priority. This is very cheap, O(lg #active_seqs) per insert or remove. (2) Each frame, apply each sequence's current transform in sorted order.

Leave it up to the artists to ensure that only the affected bones are keyed (and the exporter/importer to make sure that all transforms are relative to the parent). Leave it up to the game dev to ensure the priorities are set corresponding to the affected bones' position in the skeletal hierarchy - or have the engine pre-process each sequence at load time to determine, based on knowing the skeleton, a good priority for each sequence (all nodes at the same level in the hierarchy have the same priority; a sequence's priority is the max of the priority of it's active nodes).
Page «Previous 1 2