Version 2.0!
Features
Tutorials
Files
Glossary
Projects
Contact
Links
Message Board
Extras
LuckyCam
Old News
Sign Guestbook
View Guestbook
VB Horoscope
VB Photo Album
.
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.

 

[download source]

Thomas 'ThamasTah' van Dijk
nov 30 2000