(draft)Absolute beginners guide to Skeletal Animation

[DISCLAIMER (JAN 2017): THIS IS A DRAFT. IT IS FULL OF GUESSWORK, AND UNTESTED ASSERTIONS. I ESTIMATE (AT LEAST) 50% OF THE STUFF HERE TO BE INCORRECT]

BACKGROUND READING
Before we begin, it’s important to get an understanding of the basic concepts we’ll be talking about here, so I’ll assume you have read the wiki article.

Secondly, you need to watch “An Indie Approach to Procedural Animation” by David Rosen NOW. Here’s some more links:

http://gamedev.stackexchange.com/questions/28012/bones-animation-matrices-and-calculations

 

 

GLOSSARY
Ok, now there’s all sorts of jargon floating around, and often different words refer to THE SAME THING, so let’s agree/clarify the jargon I’ll be using:

skeleton = what blender calls an ‘armature’. Sometimes this is referred to as a ‘rig’, but to ‘rig’ (verb) usually refers to the process of attaching a skeleton to a model, deciding which vertices move, and by how much (the weightings)

frame = What Blender calls an ‘animation’

tweening = when the game engine creates poses ‘in between poses’, to smooth out animations. otherwise known as interpolation, blending.

linear interpolation = a method of interpolation/tweening/blending.

pose = What Blender calls a ‘frame’, yea, I know, I know.

WHERE DOES ANIMATION INFO COME FROM?
What are we doing? We’re animating. What’s an ‘animation’?Animations are a series of poses, which happen over a period of time. The animation info (ie what pose we will be playing/displaying/rendering at any particular time) will come from three sources:

1. Animations done in Blender, and recorded/embedded in the IQM. eg a running animation, which plays back the same way it did in Blender

2. Composite Animations which are combinations of 2-3 animations, blended in different amounts. eg An idle player (running the ‘idle animation’) starts to run, and instead of immediately playing the running animation (which will create an jerky look), we ‘blend’ the ‘idle’ into ‘run’, starting with 100% idle, and smoothly transitioning to (eg) 50% idle, and 50% run, and finally 100% run animation.

3. Animations generated in real time, to create ‘on the fly’ modifications of existing animations. during gameplay, by manipulating the skeleton, and creating new poses which weren’t recorded previously. eg. making the players head face where the player is facing. This is not strictly an ‘animation’, more like a ‘remote control’ over a part of the skeleton, but since it ends up in the final animation, I’ve included it as part of ‘the animation’

A BASIC OVERVIEW
As you know, CSQC allows you to take information received by the client and “do things” to it. As you also know, an Armature (which I’ll refer to as a ‘skeleton’ from here on) contains a collection of (named and numbered) bones, and information on how they change over time. We are going to create an alternative object (the skeleton), and just before we draw the model to the screen (PreDraw), we are going to copy the bones’ current positions to this seperate ‘skeletal object’ Think of this as a temporary copy of the bones ‘official’ position (as contained within the original animation. An example would be better. Lets say we want the models head to face wherever the player is facing. In the old days, you’d have to make (eg) 5 copies of every animation:
extreme left
diagonal left
forwards
diagonal right
extreme right

With skeletal animation, what we do now, is
1. Make a copy of the models bones, were going to refer to this copy as the ‘skeleton’
2. Just before every frame is rendered (in CSQC’s predraw), copy the current animation (ie the positions and angles of all the bones) to the skeleton
3. get the players current head angle
4. get access to the head bone,
5. change the head bone on the skeleton, to match where the player is facing
6. draw the ‘skeleton’ version of the armature!

This sounds complex, but each of these jobs can be done with one line of code, using one of the functions which are part of FTE_CSQC_SKELETONOBJECTS.

MAKING THE MODEL (Blender)

1. Start with a player model with a simple rig (Head, upper body, arms, lower body, legs)

2. Make a bone pointing straight in front of the head (we’ll need this to align to the players POV angle, for head turning) [Do we need this?]

3. Make sure the bones are numbered/ordered correctly [TODO: define ‘correctly’]* (so that different/specific parts of the skeleton can be addressed separately, eg upper body shoots/lower body runs)

4. Export as IQM, test w Noesis

GETTING THE MODEL INTO QC
5. Make each animation be represented by a global float:

float animIdle = frameforname(self.modelindex,”idle”);

 

6. Add player model to the CSQC

7. Make the ‘regular/embedded’ animations work (do we do this now, or do it after we make the skeleton? )

8. Blend the animations for when the player changes direction

9. Blend the animations between idle and walking

 

So far, we don’t need to access any of FTE’s skel_ stuff. Animation blending is done by using

frame, frame1time, frame2, frame2time, and lerpfrac.

WHAT DO THE SKEL FEATURES OFFER?

  1. seperate upper/lower body animations
  2. individual control over body parts/bones in real time

 

SETTING UP THE SKELETON

10a. Make the skeleton (this happens once):

self.skeletonindex = skel_create(self.modelindex);

10b. Make each bone be represented by a global float [UNVERIFIED]

bthighL = skel_find_bone(self.skeletonindex, “thigh_l”);

 

11.  In predraw(), call skel_build to
a. refresh the current position and angles of the bones
b. isolate the which part of the skeleton we are processing ( eg head, upper body, and lower body)
c. “calculate the new position/make it move correctly” (this is a ‘draft of a draft’, ok? :) )

12. Get the head to turn based on players POV (limiting yaw)  – use skel_set_bone_world = #283

13. Get the upper body to move when the (turning) head reaches its limit

14. Making the feet match the ground when walking =  “Note that you can set frameXtime to be dependant upon distance traveled. If your model animates a run speed of 320 qu per second, then self.frame1time += distancemoved/320; will keep your legs moving in sync with the ground.”

RAGDOLL
14 Make Ragdoll file (FTE ragdoll system)

 

 

FAQ:

Q: WHAT ARE FRAMEROUPS FILES?

These are useful if (a) you want to import (new) IQM replacements for (old) existing Quake models (ie *mdls) having to re-write qc. Old Quake mdls had AI embedded in the animation info, so rather than rewrite all the code for the replacement models, the framegroup file allows access to the individual poses, so it imitates the way *mdl animation works. The only other use for these is if your exporter can only export your animations as one BIG animation, and the framegroups file can tell your qc where each individual animation starts and stops.

 

REFERENCES
Spike speaks about skeletal animation

Insideqc links:
(Framegroups discussion).

2. Excellent Skeletal animation info (example code) is found within:
http://forums.insideqc.com/viewtopic.php?f=2&t=5523

Also,
http://forums.insideqc.com/viewtopic.php?f=16&t=5441

Also GOLD = toneddu2000’s working (!!?) skeletal animation (+ more) mod w links to his source code in
http://forums.insideqc.com/viewtopic.php?f=17&t=5686

3. Controlling the legs and the torso separately!
http://forums.insideqc.com/viewtopic.php?f=2&t=5704

4. ragdoll
http://forums.insideqc.com/viewtopic.php?f=16&t=5356

5. basics of animation

http://www.katsbits.com/tutorials/blender/character-7-animation.php

WISHLIST
4 . Controlling the speed of animations in qc/csqc dynamically (ie the faster you move the faster the animations are)!

5 . Controlling the animation so steps match stairs/ramps etc !
Bouncign boobs: https://www.youtube.com/watch?v=WNZw57nC7Cs

 

NOTES

Spike:

[[[[[[[[[[[

in theory, just create a skeletal object compatible with your model, set up the various frame fields on your entity and then call skel_build. then do it again with a different range of bones. yes, this means you need to ensure that your model editor does not re-order bones and stuff (or gives you control over that order).

think of the skeletal object as a load of matricies that express the orientation of each bone relative to its parent. you can interpolate those per-bone matricies individually, or you can completely replace them with a different animation, and because they’re relative to the parent bone, the torso stuff all stays attached to the legs, etc.

if you want more control, you can explicitly set a bone’s matrix, or multiply it (read: rotate it). a bone matrix is easy enough to calculate with makevectors and a displacement vector.

note that skel_build can be used to source animation data from a different model than the one that you’re rendering with, although if you do that then you’ll have to do lots of stuff to ensure that the skeletons is compatible.

if you want to go crazy with it, you can blend multiple animations depending on how far the entity has traveled, and in doing so you can get the animation to play at exactly the right speed, even diagonally, in order to keep the feet synced with the ground.
if you’re feeling lazy, you can instead twist the model at the hips (see the skel_mul_bone builtin that I aluded to earlier).
certain animations are more messy than others, like jumping…

using framegroups properly means that you get two-way interpolation for free, just update frame[1|2]time for the animation and then call skel_build. getting your exporter to behave is a different matter.

]]]]]]]]]]]]]]

 

when you break it down, skeletal animation is quite simple. you have a) the rig that says the default position of the bones. you have b) the verticies which say which bone matricies they’re affected by, and you have c) a set of inverted absolute bone matricies that say where each bone is relative to the rig’s default position for that bone.
<OneManClan> Good News: Hopefully they will never have to be answered again!
<Spoike> (the cool kids apparently use quaternions instead of matricies, but whatever)
<OneManClan> I get A
<Spoike> anyway, the renderer can then just blend the matricies according to the vertex weights, and this is what moves the vertex from its base position to the animated point
<Rich> You might just have a and b, with b in weighted-transform-local space
<Rich> (Doom 3)
<Rich> Or any number of interesting/terrible derivatives, I’ve seen a lot of weighting schemes in my travels
<OneManClan> I’m thinking of starting with a simple “how to make a models head turn based on the players POV”
<OneManClan> (using skel_set_bone_world)
<eukara> terrible derivatives… yes

<OneManClan> Q: TRUE or FALSE:
<OneManClan> “The purpose of ‘Framegroups’ files are to allow access to individual poses within IQM animations”
<Spoike> no, the purpose of ‘framegroups’ files is to completely stomp over the animation data that’s already present in the iqm
<OneManClan> yes, the existance of the file replaces any internal animation data
<OneManClan> ie isnt that an ‘effect’, not the ‘purpose’?
<Spoike> the only reason you’d use one of those files is to work around crappy exporters, or compat with crappy formats that don’t support framegroups.
<OneManClan> ah
<OneManClan> I originally thought the purpose of framegroups files were to allow direct IQM replacements for Quake models without having to re-write qc
<Spoike> everythinig that can be done with framegroups files can be done with the iqm format itself
<OneManClan> ie so IQM animation could imitate/respond to/’be contolled by’ the original qc animation(+embedded AI) code
<OneManClan> ah
<OneManClan> so… you dont *need* framegroups files?
<Spoike> but when blender spits out every single pose munged together in a single animation, using a framegroups file is easier than figuring out how to properly use blender’s iqm export options
<OneManClan> ahhhhhhh
<OneManClan> yes yes.. the framegroups file lets you ‘bunch together’ the relevant poses (if they are all on one time line)
<OneManClan> I thought framegroups offered some other advantage with controlling the speed of animations (?)
<OneManClan> IIRC?
* OneManClan checks insideqc
<Spoike> iqm supports per-animation framerates.
<eukara> IQM is solely based around that, poses aren’t really accessible from what I know.
<Spoike> that’s an engine thing rather than a format thing
<eukara> Alright
<Spoike> iqm has a single list of poses, with the animations specifying first-frame and num-frames within that list of poses.
<Spoike> name, firstframe, numframes, framerate, flags(loop=1). that’s the animation data inside iqms, stuff that is completely stomped over by .framegroups files, identical in fact.
<eukara> I haven’t been thinking straight
<eukara> Someone hit me over the head with a frying pan or something
<Spoike> qc using frame1time in seconds rather than as pose1a+pose1b+pose1aweight+pose1bweight means you don’t need so many fields, even though its a bit more limiting.
<Spoike> no need to worry about framerates in the qc either
<OneManClan> I successfully exported an IQM w animations which werent onthe same timelime
<OneManClan> ie made with the animation editor
* OneManClan tries to find the export command
<OneManClan> dammit i pasted it here the other day
<Spoike> 20 lines of gibberish? :s
<OneManClan> “Animations: run,stand,walk”
<OneManClan> ^ thats what you type in Blenders IQM export screen
<OneManClan> each animation exists on seperate timelimes
<OneManClan> (wihtin Blender)
<OneManClan> Noesis cant see them, it just says:
<OneManClan> Tools-> Data viewer->Animations “Frame Count 42″
<OneManClan> BUT.. in FTE
<OneManClan> this worked: http://pastebin.com/kxNXhKz0
<OneManClan> (lists the seperate animations embedded in the IQM)
<OneManClan> ^ does this mean framegroups files are ‘obsolete’ now? ;)
<Spoike> I’d ask why your HAT is animating, but I’m much too lazy.
<OneManClan> its just a test
<OneManClan> so, seriously, aside from the ‘direct replacement for a Quake model’ (using existing code)..
<OneManClan> .. and assuming Blender CAN export different animations (not all on one timelime)
<OneManClan> (which IIRC it can)
<OneManClan> are there any other uses/any point in using framegroups files?
<OneManClan> controlling the speed of an anination ie dynamically ..?
<Spoike> only reason is because of exporters that are too annoying to use.
<OneManClan> (or is that a job for skeletal animation?)
<OneManClan> to clarify, when I said “Noesis cant see them” (ie the animations) I meant listed in the data viewer
<OneManClan> Noesis plays the animations fine
<Spoike> the interval between poses within an animation is constant for that animation, as far as fte is concerned.
<Spoike> [side note: mdl has per-frame intervals…]
<OneManClan> Spoike: so how to make legs move faster/slower when running faster slower?
<OneManClan> (aside from ‘make different animations’)
<Spoike> frame1time is how far in to the animation you want the animation to be.
<Spoike> if you want to play it back at a different rate, just scale how fast it changes
<Spoike> vector moved = self.origin – self.oldorigin; self.frame1time += (v_forward * moved) / 320;
<OneManClan> ahh
<OneManClan> thanks, i keep forgetting that each pose is *interpolated*
<Spoike> so if your model is animated to run at 320qu per second (which is actually really fast), then (v_forward*moved) says how ‘big’ moved is in the direction of v_forward thanks to the magic of dotproducts with unit vectors.
<Spoike> and dividing it by 320 deals with how long it takes for the animation to cover the same distance.
<OneManClan> (gotta get out of the QME /10 frames per second mindset)
<eukara> Yeah, drop that stuff as fast as you can
<Spoike> blending in strafe etc animations is more fun…
<Spoike> and switching between run+walk depending on current speed can be fun too
<OneManClan> ok final question (for now)…
<OneManClan> can qc access keyframe info?
<OneManClan> eg the paths that Blender draws
<Spoike> there are no keyframes…
<OneManClan> because when David Rosen talks about keyframes in his vid(http://www.gdcvault.com/play/1020583/Animation-Bootcamp-An-Indie-Approach)
<Spoike> the engine has a constant (logical) interval between each pose.
<OneManClan> he talks about using curves to determine the rate of movement
* OneManClan looks for the precise spot
<eukara> I stopped associating terms/names with how things work a long time ago
<eukara> there are simply too many ways to call things something
<eukara> meanings shift etc.
<eukara> frame might mean one thing in a model editor
<eukara> different in a game engine
<eukara> trying to link them == bad idea
<OneManClan> ok its at 17:45
<OneManClan> um, no I think that bit is stuff done in Blender
<Spoike> eukara: life gets much much more fun when you just call everything ‘thing’.
<eukara> You get more shit done, too.
<eukara> I don’t know shit about terminologies but I sure as hell got more shit done than my programming teachers at college ever did
<OneManClan> ok its at 6:50
<OneManClan> hmm im not sure if hes talking about ‘stuff done in Blender’, or ‘stuff that skeletal animation does during gameplay’
<OneManClan> nm, i think i misunderstood.. it all seems ‘stuff (ie keyframes) done/predetermined in Blender’ now
<OneManClan> LOVE that vid!

========

eukara:

// Wheel spinning
	vector vSpineAngle;
	vSpineAngle_x = 0;
	vSpineAngle_y = 0;
	vSpineAngle_z = self.fTireSpin;

	makevectors( vSpineAngle );
	skel_mul_bone(self.skeletonindex, skel_find_bone(self.skeletonindex, "RRWheel"), '0 0 0', v_forward, v_right, v_up);
	skel_mul_bone(self.skeletonindex, skel_find_bone(self.skeletonindex, "RLWheel"), '0 0 0', v_forward, v_right, v_up);
	skel_mul_bone(self.skeletonindex, skel_find_bone(self.skeletonindex, "FLWheel"), '0 0 0', v_forward, v_right, v_up);
	skel_mul_bone(self.skeletonindex, skel_find_bone(self.skeletonindex, "FRWheel"), '0 0 0', v_forward, v_right, v_up);


========

float global_float;
vector global_theneck;

// replace mdls with iqms
// 'self' is the entity which is using the model
float(float isnew) UpdatePlayer =
{
 float dig = 1;
 
 setmodel(self, IQM_TEST_BOD);
 
 if(isnew)
 {
 // ************ START OF SKELETON TESTING
 
 // make the skeleton 
 self.skeletonindex = skel_create(self.modelindex);
 
 //PrintAnimInfo(self);
 PrintSkelInfo(self);
 
 
 global_theneck = skel_get_bonerel(self.skeletonindex, 24);
 /*
 float num_bones = skel_get_numbones(self.skeletonindex);
 
 for (int i = 1; i <= num_bones; i++) 
 {
 makevectors( randomv('-300 -300 -300', '300 300 300'));
 //makevectors( randomvec());
 skel_set_bone (self.skeletonindex, i, '0 0 0');
 }

 */
 
 
 // ************ END OF SKELETON TESTING
 //self.predraw = PlayerPredraw;
 
 }
 
 skel_build(self.skeletonindex, self, self.modelindex, 0, 0, 0, 1);
 
 
 
 if(global_float > 360)
 global_float = 0;
 else
 global_float+= 1;
 
 
 vector a_vec = global_theneck;
 a_vec_y += global_float; // I tried a_vec_y as well, no diff
 
 
 
 
 //makevectors( randomv('-300 -300 -300', '300 300 300'));
 
 
 //skel_set_bone (self.skeletonindex, 20, a_vec);
 
 
 // Wheel spinning
 a_vec_x = global_float;
 a_vec_y = 0;
 //a_vec_y = global_float; // side to side
 a_vec_z = 0;

 makevectors( a_vec);
 skel_mul_bone(self.skeletonindex, skel_find_bone(self.skeletonindex, "Head"), '0 0 0', v_forward, v_right, v_up);


Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Notify me of followup comments via e-mail. You can also subscribe without commenting.