Procedural bone animation
by John Klima · in Torque Game Engine · 08/13/2005 (2:15 pm) · 38 replies
Hi all,
i'm working on a procedural skeletal animator (with ken perlin at NYU). actually its more or less done in java, and i want to port it to torque. what it does is allow the user to set a wide variety of movement parameters at run-time which modify the motion of a bipedal character. it basically allows characters to dance in a reactive way, on the fly, without having to make multitudes of animation sequences.
so, my question is one of approach. i'm not exactly sure the best place to "slip" my code in. i have a character model, it will be an aiPlayer, but it will not have any animation sequences. every frame i calculate and update the bone positions. i see that in tsShapeInstance class i could iterate the bones and set their positions something like this:
however, i'm not sure if tsShapeInstance is the right place to be messing with the bones. what i think i should be doing is replacing the existing animator with my own instance, or subclass the existing animation class to override the dts sequences with my calculated bone transforms.
naturally i'm having a little trouble finding the classes that control the animation of an aiPlayer. any pointers would be greatly appreciated.
thanks,
j
i'm working on a procedural skeletal animator (with ken perlin at NYU). actually its more or less done in java, and i want to port it to torque. what it does is allow the user to set a wide variety of movement parameters at run-time which modify the motion of a bipedal character. it basically allows characters to dance in a reactive way, on the fly, without having to make multitudes of animation sequences.
so, my question is one of approach. i'm not exactly sure the best place to "slip" my code in. i have a character model, it will be an aiPlayer, but it will not have any animation sequences. every frame i calculate and update the bone positions. i see that in tsShapeInstance class i could iterate the bones and set their positions something like this:
for (U32 i=0;i<mShape->nodes.size();i++){
Point3F pos;
pos = mNodeTransforms[i].getPosition();
pos.x++;pos.y++;pos.z++; //just a dumb change
mNodeTransforms[i].setPosition(pos);
}however, i'm not sure if tsShapeInstance is the right place to be messing with the bones. what i think i should be doing is replacing the existing animator with my own instance, or subclass the existing animation class to override the dts sequences with my calculated bone transforms.
naturally i'm having a little trouble finding the classes that control the animation of an aiPlayer. any pointers would be greatly appreciated.
thanks,
j
#2
08/13/2005 (5:46 pm)
Actually, since TSShapeInstance class is used by so many things, I'd say that's not a good place to put your code. For procedural animation code, look in Player::processTick() and how it calls updateLookAnimation(). processTIck() is virtual, so you can place it in your AIPlayer, call the Parent::processTick() then update your animation threads.
#3
08/13/2005 (5:57 pm)
Thanks guys, this is very helpful.
#4
so if anyone is interested toggling between keyframe and procedural animation, here's how you can do it.
first, i put a boolean in player.h- PlayerData block:
my flag is called isPuppet, cause that's what we call our procedural animator
next, down at the bottom of Player.h, add a method declaration that will be called to update the animation, a method to set it in procedural mode, and a third that returns it to keyframe. here are mine, call yours what you like
you will prolly want to make isPuppet a persistant field, so add it to Player.cc (i hope i'm doing this right :)
pack data:
unpack data:
if you want the setters accessable from the console, you need the console functions, so in Player.cc, after all the other console calls, these worked for me (not sure what the "2,2" is for, someday i'll check the docs :):
08/23/2005 (1:51 pm)
So i basically fingered out what i need to do and my procedural dancer is working great! i hoped it would take two days to resolve, it took five to fully integrate all my code. oh well, thats pretty darn good when it comes to software. much of the job involved transposing matrices and differing representations world space between torque and my existing java code.so if anyone is interested toggling between keyframe and procedural animation, here's how you can do it.
first, i put a boolean in player.h- PlayerData block:
struct PlayerData: public ShapeBaseData {
typedef ShapeBaseData Parent;
enum Constants {
RecoverDelayBits = 7,
JumpDelayBits = 7,
NumSpineNodes = 6,
ImpactBits = 3,
NUM_SPLASH_EMITTERS = 3,
BUBBLE_EMITTER = 2,
};
bool renderFirstPerson; ///< Render the player shape in first person
[b]bool isPuppet; ///< is it procedural? [/b]my flag is called isPuppet, cause that's what we call our procedural animator
next, down at the bottom of Player.h, add a method declaration that will be called to update the animation, a method to set it in procedural mode, and a third that returns it to keyframe. here are mine, call yours what you like
void setPuppet(); //make procedural void setKeyAnim(); //make keyframe void updatePuppet(F32 dt); //takes delta time
you will prolly want to make isPuppet a persistant field, so add it to Player.cc (i hope i'm doing this right :)
void PlayerData::initPersistFields()
{
Parent::initPersistFields();
addField("renderFirstPerson", TypeBool, Offset(renderFirstPerson, PlayerData));
[b]addField("isPuppet", TypeBool, Offset(isPuppet, PlayerData));[/b]pack data:
void PlayerData::packData(BitStream* stream)
{
Parent::packData(stream);
stream->writeFlag(renderFirstPerson);
[b]stream->writeFlag(isPuppet);[/b]unpack data:
void PlayerData::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
renderFirstPerson = stream->readFlag();
[b]isPuppet = stream->readFlag();[/b]if you want the setters accessable from the console, you need the console functions, so in Player.cc, after all the other console calls, these worked for me (not sure what the "2,2" is for, someday i'll check the docs :):
ConsoleMethod( Player, setPuppet, bool, 2, 2, "()") {
object->setPuppet();
return true;
}
ConsoleMethod( Player, setKeyAnim, bool, 2, 2, "()") {
object->setKeyAnim();
return true;
}
#5
that was all pretty standard stuff, but i thought i'd put it in the post to be thorough.
now for the meat of the matter, the settter bodies, in Player.cc:
finally, make a quick change to Player.cc, ::advanceTime:
oh and of course you will want your version of updatePuppet, here's a sample method that will spin all the joints of a model by a time constant. play with matrix math to get different speeds. i think dt is a decimal millisecond, so i think this will result in 360 degrees per sec, though i'm prolly wrong :)
hope folks might find this useful!!
best,
j
08/23/2005 (1:52 pm)
...continuedthat was all pretty standard stuff, but i thought i'd put it in the post to be thorough.
now for the meat of the matter, the settter bodies, in Player.cc:
void Player::setPuppet(){
//this method turns off node animations by setting
//the animation state to "handsoff"
TSShape * shape = mShapeInstance->getShape();
//turn off node animations
for (U32 i=0;i<shape->nodes.size();i++){
mShapeInstance->setNodeAnimationState(i, mShapeInstance->MaskNodeHandsOff);
}
//do other stuff specific to your needs
mDataBlock->isPuppet=true; //set the datablock flag
}
void Player::setKeyAnim(){
//this method restores animation states. it assumes you are not selectively setting
//the mask for your own purposes. the default is MaskNodeAll (I think :)
TSShape * shape = mShapeInstance->getShape();
//turn back on the node animations
for (U32 i=0;i<shape->nodes.size();i++){
mShapeInstance->setNodeAnimationState(i, mShapeInstance->MaskNodeAll);
}
//do other stuff specific to your needs
mDataBlock->isPuppet=false; //un-set the datablock flag
}finally, make a quick change to Player.cc, ::advanceTime:
void Player::advanceTime(F32 dt)
{
// Client side animations
Parent::advanceTime(dt);
updateActionThread();
[b]
if(mDataBlock->isPuppet){
updatePuppet(dt); //call your custom animator
}
else{
updateAnimation(dt); //call the normal animation process
}
[/b]oh and of course you will want your version of updatePuppet, here's a sample method that will spin all the joints of a model by a time constant. play with matrix math to get different speeds. i think dt is a decimal millisecond, so i think this will result in 360 degrees per sec, though i'm prolly wrong :)
void Player::updatePuppet(F32 dt)
{
TSShape * shape = mShapeInstance->getShape();
MatrixF mr;
mr.set(EulerF (2*PI*dt*10, 0, 0)); //rotate 360 degs per sec??
for (U32 i=0;i<shape->nodes.size();i++){
mShapeInstance->mNodeTransforms[i].mul (mr);
}hope folks might find this useful!!
best,
j
#6
08/23/2005 (4:50 pm)
Very cool. You should post this as a resource!
#7
08/23/2005 (8:40 pm)
Very very cool. Thanks for sharing!
#8
updatePuppet()?, just a quick thinking, Thanks!.
08/23/2005 (10:20 pm)
Interesting, very interesting, we may put some "reaction animation" code in updatePuppet()?, just a quick thinking, Thanks!.
#9
08/23/2005 (11:08 pm)
This should definetly be resourced. Looks very cool.
#10
glad to contribute!
i'll maybe tidy a few things up and post to the resource section. any comments regarding the "right" way to do any of the bookkeeping (datablock stuff, etc..)?
j
08/24/2005 (3:54 pm)
Thanks all,glad to contribute!
i'll maybe tidy a few things up and post to the resource section. any comments regarding the "right" way to do any of the bookkeeping (datablock stuff, etc..)?
j
#11
Any more work been done with this?
I'm trying to do some similiar stuff. Was curious if you've expanded on this.
08/26/2006 (7:13 pm)
@ JohnAny more work been done with this?
I'm trying to do some similiar stuff. Was curious if you've expanded on this.
#12
[Ishbuu]
08/26/2006 (7:31 pm)
Definately should be a resource IMO. People could adapt this to so many things, you're pretty damn cool my friend ;)[Ishbuu]
#13
08/26/2006 (7:35 pm)
I'm curious in adapting this to work for simple cloth/hair/softbody physics. By simply modifying node positions/rotations based on velocity.
#14
08/26/2006 (8:47 pm)
Oddly, what i have done subsequently, is work with the existing animation system to allow for procedural work without disabling the normal functionality. you see, torque 1.4 has a wonderfully robust animation blending system, and i didnt want to reinvent that wheel. so i cooked up a method to do all my procedural work in conjunction with standard animations. so for example, i can take a typical walk cycle, and alter it on the fly to be a "walk drunk" cycle. this unfortunately i can not post as a resource. gotta keep this to myself at this point in time.
#15
08/29/2006 (8:06 am)
I believe the 2,2 is for minimumarguments, maximumarguments.
#16
I used tree() in the console to try and find the id of the player and found two references to a player object one with id 1453 under ServerConnection - GameConnection and another with id 1452 under ServerGroup - SimGroup/MissionCleanup - SimGroup.
So I tried a dump on both abjects in the console 1453.dump() and 1452.dump() and found both had a setPuppet() method. Then I called setPuppet() for both ie. 1452.setPuppet() and 1453.setPuppet().
When I called for 1452 the same happened as with $player.setpuppet() ie. nothing. When I called for 1453 then some weird stuff happened. The screen starts jumping and constantly strafing to the right. Not sure why this happens.
Any ideas on why it's not working? Thanks in advance. :)
09/03/2006 (2:53 pm)
Hi. I'm having trouble getting this to work for my player. I have set $player = %this.player in game.cs in server/scripts inside the function GameConnection::createPlayer(%this, %spawnPoint) so I can refer to the player object on the fly. But when I call setPuppet() from the console for $player nothing happens. I used tree() in the console to try and find the id of the player and found two references to a player object one with id 1453 under ServerConnection - GameConnection and another with id 1452 under ServerGroup - SimGroup/MissionCleanup - SimGroup.
So I tried a dump on both abjects in the console 1453.dump() and 1452.dump() and found both had a setPuppet() method. Then I called setPuppet() for both ie. 1452.setPuppet() and 1453.setPuppet().
When I called for 1452 the same happened as with $player.setpuppet() ie. nothing. When I called for 1453 then some weird stuff happened. The screen starts jumping and constantly strafing to the right. Not sure why this happens.
Any ideas on why it's not working? Thanks in advance. :)
#17
09/03/2006 (3:33 pm)
Just used 'alt c' to release control of the player and now what I see is all the vertices aside from a few on the head and upper body have moved weirdly away from the models original position. :(
#18
I was able to get a staticshape to work as expected. I ran into issues with
"mShapeInstance->setNodeAnimationState(i, mShapeInstance->MaskNodeHandsOff);"
when i called this, the node i was trying to rotate would no long be positioned where it should be.
So i just made a function to rotate a node, and didn't use that line of code for my staticshape, and it worked ok.
09/03/2006 (3:55 pm)
I've done some tests with this as well.I was able to get a staticshape to work as expected. I ran into issues with
"mShapeInstance->setNodeAnimationState(i, mShapeInstance->MaskNodeHandsOff);"
when i called this, the node i was trying to rotate would no long be positioned where it should be.
So i just made a function to rotate a node, and didn't use that line of code for my staticshape, and it worked ok.
#19
But there must be some way of making Johns method work. By making sure the nodes don't get repositioned some how. I'm not sure how to go about fixing this. Does anybody have any example code or something to this effect? Thanks :)
09/03/2006 (4:27 pm)
Hmm. Interesting. I'll try that. Thanks RamenBut there must be some way of making Johns method work. By making sure the nodes don't get repositioned some how. I'm not sure how to go about fixing this. Does anybody have any example code or something to this effect? Thanks :)
#20
"mShapeInstance->setNodeAnimationState(i, mShapeInstance->MaskNodeHandsOff);"
and recompiled and no rotation occured that i can see :( Sorry but I'm new to this and am trying to figure out the engine code for animation control...Thanks
09/03/2006 (5:14 pm)
Do you mean use a function like the updatePuppet() function for rotating the nodes or is something else extra needed? cause I commented out the "mShapeInstance->setNodeAnimationState(i, mShapeInstance->MaskNodeHandsOff);"
and recompiled and no rotation occured that i can see :( Sorry but I'm new to this and am trying to figure out the engine code for animation control...Thanks
Associate Kyle Carter
There are mechanisms in place to allow procedural control of 3space animation... There are some flags to control it and then you can reach in and set the transforms yourself. HandsOff, I think, is what you want. If you're going to take total control of all animation, then things are pretty straightforward - just never call animate on the TSShapeInstance and stuff your own transforms into the system. If you want to coexist with the existing animation system, then things are a little more complicated but still not too bad.