ATTENTION READERS! Lucky's VB Gaming Site is no longer active. For updated game programming information and tutorials, please visit The Game Programming Wiki!
Skeletal Animation
by Thomas 'ThamasTah' van Dijk
I wrote some "Skeletal Animation" code in some spare time and I asked
on Lucky's board whether anyone would be interested in a tutorial on it. Lucky
wanted it! Way to go! So here it is, then.
Introduction
Sorry if I bore you, but its quite a sit to read though in one go. But it had
to be: it could be quite complicated and I wanted to make sure I lost only a
minimum of readers due to that, so perhaps I'm explaining a bit too much.
What is "Skeletal Animation" anyway? I hear you ask. Well, I don't
even know if it's called 'skeletal', but it animates, and you could build a
skeleton with it, so... ;)
For those of you who know Poser (formerly by Fractal Design, but recently bought
out by someone), it is easy to explain: it is what Poser does; for those who
don't know Poser:
I assume you know what vectors are (if not, Lucky has some tutorials on 'em:
go read them).
You take one coord(inate). That coord is the start of a line. That line is stored
as a vector: angle and length, or as in the code: cAngle and sLength. Using
the vector, we can calculate the ending point of the line (using goneometrics).
Now, we can link another line (or, bone, as I call it in the code) to the end
of the first line. We call it the child of the first line. The first line is
the parent of the second one.
If a line moves or changes angle, we re-calculate the ending point, set the
starting point of any children to the newly calculated coords, re-calculate
those children. The ending point of the children will then, of course, change
(its starting point has changed!): its children should also be re-calculated.
Just repeat until no children are left...
Another important note: the angle is relative to the angle of the parent, so
if the angle of the parent changes (the parent turns) the actual on-screen angle
of the child changes accordingly, so the angle between parent and child remains
the same.
This is most visually explained by an example involving the upper- and lower
arm:
Imagine (or you could actually do this if you don't believe it!) you
stand in front of a mirror (the computer screen). Bend you elbow to, say,
90 degrees (which equals 0.5pi radians). Keep this angle constant. Now, from
the shoulder, rotate your upper arm.
The lower arm doesn't remain floating in the air where it used to be (duh,
I hear you say). Instead, the starting point of your lower arm, has moved
along with the ending point of you upper arm.
Also, if you did as I said, the angle between the upper arm and the lower
arm has remained the same. Notice the angle between the lower arm and, for
example, the side of the mirror (the side of the computer screen, or: the
universal co-ordinate system) indeed has changed.
This is handled by my skeletal animation system. Note that the upper arm is
the parent of the lower arm, and the lower arm is the child of the upper arm.
A final note before I start to describe the (my) implementation: this is my
first tutorial larger than a few paragraphs and my writing tends to be rather
chaotic. Sorry for that. Comments on my writing -or something else :)-
are very welcome.
Implementation
Firstly, a few things you need to know:
All angles are in radians (2pi radians equals 360 degrees).
All lengths are in pixels, but this should, for a better version, be converted
to WU (world units) or something else that is resolution independent
I'm going to build a class: bone. The prefix 'b' before a variable (e.g.
'bParent') indicates the variable is a pointer to a bone. I know this might
conflict with the 'b' prefix for Boolean :( . Therefor, I use 'bool'
to indicate booleans.
Instead of using a prefix to indicate the data type, I use prefixes (prefices?)
to indicate what type of information I store in it: 'c' means angle (you know,
that little 'c'-like angle symbol you use to indicate angles...) and I use
's' to indicate length/distance (s stands for 'spatie', which is Latin for
space/distance -or something like that anyway; in Physics, it indicates total
distance, not knowing in which direction, or even whether it's in a straight
line).
Okay, let's start. I will just build the code alongside the explanation.
(I'll talk in 1st person plural)
The member variables
We make a class to hold the information for the bones (lines). We call it bone.
The bone will need to store: starting coords; ending coords; angle relative
to its parent; length:
Public x1 as Double, y1 as Double
Public x2 as Double, y2 as Double
Public cAngle as Double
Public sLength as Double
We'll also need a value to store the 'world'angle, for drawing -that is, the
angle relative not to its parent, but to the x and y axis of the screen (needed
for drawing).
Public cRealAngle as Double
We'll calculate x2, y2 and cRealAnlge in bone::render
Like this we can store a single bone. If we want to add the 'skeletal' stuff,
we'll need to be able to identify our parent and children. We do that using
a pointer for our parent and a collection containing our children.
Public bParent as bone
Public bChildren as New Collection
Notice we don't say 'New bone', just 'bone': we don't want a new instance of
a bone, just a pointer to a (any) bone... We do want a New Collection.
The first bone in a series doesn't have a parent. Its starting point has to
be explicitly specified. Its bParent pointer will be NULL --what's that called
in VB? It won't let me say: If bParent = Nothing! Therefor I added a boolean
for a bone to indicate it has no parent.
Public boolAbsoluteCoords as Boolean
I also have a Boolean for indicating the angle of a bone should always be the
same, relative to to the universal coord-system. I could explain this as follows:
Use the example as above. Now, add to that a book (or something else flat).
Lay the book on your hand. Your hand will have to be horizontal (cRealAnlge
= 0 or pi, in my code), otherwise the book will fall off.
Now turn your elbow and shoulder, but make sure the book doesn't fall off!
What you have to do, is turn you wrist --changing the angle between your lower
arm (the parent) and your hand (the child).
Instead of letting the client app calculate that, it is way easier to provide
it in the class (as we will see ;))
Public boolAbsoluteAngle as Boolean
Also, for drawing and client-side editing, we use a boolean to indicate whether
the bone is selected.
Public boolSelected as Boolean
The Member Funtions
So, we can store the information. Now, let's actually do something with it.
I'll explain most important one: bone::render. This will calculate x2 and y2,
using x1, y1, cAngle and sLength.
n.B. Comments are italicized.
Public Sub render(Optional boolChildren as Boolean = True, Optional boolCheckParent
as Boolean = False) -- boolChildren indicates whether this bone's children's bone::render should
also be called -- boolCheckParent indicates whether we should check x2 and y2 of the parent
(usually not needed, because its parent will have set its x1 and y1.
Dim var as Variant Used in the 'for each'-loops.
If boolAbsoluteCoords Then If it hasn't a parent...
cRealAngle = cAngle The real angle has to be angle (since it has not parent that could have
another angle!)
If cRealAngle > 2 * PI Then Correct cRealAngle if its value has unneedily high or low values...
cRealAngle = cRealAngle - 2 * PI
ElseIf cRealAngle < 0 Then
cRealAngle = cRealAngle + 2 * PI
End If
Else If the bone does have a parent...
If boolCheckParent Then If the parent should be checked, copy its end coords to my start coords...
x1 = bParent.x2
y1 = bParent.y2
End If
If boolAbsoluteAngle Then If my real angle should be the same as my cAngle...
cRealAngle = cAngle
Else Otherwise...
cRealAngle = bParent.cRealAngle + cAngle My real angle is my parent's real angle, plus my angle
End If
If cRealAngle > 2 * PI Then Correct cRealAngle...
cRealAngle = cRealAngle - 2 * PI
ElseIf cRealAngle < 0 Then
cRealAngle = cRealAngle + 2 * PI
End If
End If
This is where some math comes in:
x2 = x1 + (sLength * Cos(cRealAngle)) Using sLength and cAngle, we can calculate the x component of the vector
y2 = y1 + (sLength * Sin(cRealAngle)) Similarly (but now using sine, not cosine) we calculate the y component
For Each var In bChildren For all of my children, set their start coords to my ending coords
var.x1 = x2
var.y1 = y2
Next
If boolChildren Then If I should also render my children (an argument in the call)
For Each var In bChildren For each of my children...
var.render Render it!
Next
End If
End Sub
Actually not so difficult, now is it?!
I added some more functions for governing the bones and stuff. It can be found
in the sample project: it isn't difficult, y'all should be able to figure it
out by yourselves. See the bottom of this page[?] to download it.
Conclusion
I'd been thinking about this for some weeks now. I thought it would be very
difficult to implement, but when I finally sat down to write the code, I just
did it. It just worked, which was very satisfying, as you might imagine :D"The rest is ancient history..."
"You haven't yet discussed animation!" Indeed I have not, but that
shouldn't be too much of a trouble: 'just' change the values... Okay, that could
actually prove to be quite difficult. Perhaps I should have called this Skeletal
Modelling. I'm working on an animation thingy though; if I ever finish it --and
if there's demand--, I'll write a tutorial on that too.
That animation-system should be time-based, keyframe-based, with (linear)
interpolation and animation interruption:
Time-based - The animation is based on time (using timeGetTime()
or getTickCount() or something like that...)
Keyframe-based - The animation is not specified per frame or cycle,
but using keyframes --a collection of sets of variables describing one state
of the object.
(Linear) Interpolation - will make sure the animation doesn't just
'jerks' from one keyframe directly into the other, but the values of the keyframe
gradually (linearly) fade/morph into the next.
Animation Interruption - This will allow the client to interrupt
a playing animation in mid-sequence and morph it into anther (specified) animation.
This should be great for action game, like done in Oni, the great game by
Bungie Software, which is due for shipping in januari in the US.
A note: this system is quite easily adaptable to 3D: just add another angle
vector (or also a third if you want to be able to turn a line alone its own
axis).
I'm also working on a skinning system using Skeletal Modelling.
As a final note, I'm also contemplating Inverse Kinematics (IK). At first,
I thought this was IK, but there in nothing Inverse about this. What I'm now
thinking about is being able to 'pull' or 'apply a force to' the end of a bone,
the parents changing accordingly.
Again imagine the example. Now try to reach something above you with your hand.
By just turning your wrist, you won't reach it. You have to turn/move your lower
arm. Still can't reach it? Move/turn your upper arm!
This might prove quite a bit more difficult that Skeletal Animation, but it
is rather interesting.