Home   Donators   Contact Us       

<< Previous Tutorial     Next Tutorial >>

Tutorial 13 - Skeletal animation



Theory

In this tutorial you will learn about more advanced rotation techniques. Skeletal animation is a powerful technique to add advanced animation effects to your models.

The first step in skeletal animation is to build a bone structure by making use of joints. The image below illustrates a bone structure that you could possibly use to animate a hand model.

The joints in the image above are represented by the colored dots. The black lines represents the "bones" between the joints. Each joint in the bone structure has two important properties, a vector that defines its position in 3D space, and a quaternion that defines the rotation of the joint. For more information about quaternions, have a look at Tutorial 11 - Quaternions.

When you look closely at the bone structure given above, you will notice that the joints form a tree structure. The red joint is known as the root joint and is the parent joint for its child joints. Except for the root joint, each joint in the bone structure has a parent joint, and possibly child joints.

After the the bone structure is in place, we rig the bone structure with the vertices found in a model, by attaching selected vertices to each joint. Rigging a hand model to a bone structure could be done as follow:

Once we've rigged the bone structure with a model, we can easily apply animation dynamics to the model by calling the Rotate method on any given joint's quaternion, using appropriate pitch, yaw and roll values. Rotating a joint will automatically rotate all the vertices attached to the joint, as well as apply the appropriate rotations to the child joints. E.g. if you rotate any yellow joint, the corresponding blue and green child joints will also be rotated. However, when you rotate a blue joint, only the corresponding green child joint will be rotated.

The images below illustrates different hand positions that can easily be implemented with only a few Rotate instructions. As you can see, skeletal animation provides an easy way to perform advanced animation routines.

         

It is important to note that the rotation of the root joint has no effect on the final transformation of a model. Only child joint rotations affect the transformation of the model.

Tutorial Steps
Tutorial created with Real Studio 2011 Release 4.3.
1. Open Real Studio.
2. Choose the "Desktop" project template.
3. Tick the MaximizeButton property of Window1.
4. Add an OpenGLSurface control to Window1.
5. Resize and position OpenGLSurface1 to fill the whole form.
6. Tick the LockRight and LockBottom properties of OpenGLSurface1.
7. Save your project.
8. Download the following files below and save them next to your project file:
9. Import the classes and modules into your new project file. (Select File > Import... from the main menu)
10. Add a Timer control to Window1.
11. Change the Period property of Timer1 from 1000 to 50.
12. Add a property named "DrawBones" of type Boolean with a default value of False to Window1.
13. Add a property array named "FingerErected()" of type Boolean to Window1.
14. Add a property named "FingerIndex" of type Integer to Window1.
15. Add a property named "FrameCount" of type Integer to Window1.
16. Add a property named "MousePrevX" of type Integer to Window1.
17. Add a property named "MousePrevY" of type Integer to Window1.
18. Add a property named "Scene" of type R3Scene to Window1.
19. Add the following code to the Action event of Timer1:
Dim model As R3Model ' the model

' do we have a finger selected?
if FingerIndex >= 0 then

  ' yes, so rotate the joints of the selected finger

  model = Scene.Model(0) ' get the hand model

  select case FingerIndex
  
  case 0, 1, 2, 3 ' pinky, index finger, middle finger and ring finger
  
    if FingerErected(FingerIndex) then
      model.RootJoint.Child(FingerIndex).Rotation.Rotate(5, 0, 0)
      model.RootJoint.Child(FingerIndex).Child(0).Rotation.Rotate(6, 0, 0)
      model.RootJoint.Child(FingerIndex).Child(0).Child(0).Rotation.Rotate(7, 0, 0)
    else
      model.RootJoint.Child(FingerIndex).Rotation.Rotate(-5, 0, 0)
      model.RootJoint.Child(FingerIndex).Child(0).Rotation.Rotate(-6, 0, 0)
      model.RootJoint.Child(FingerIndex).Child(0).Child(0).Rotation.Rotate(-7, 0, 0)
    end if
  
  case 4 ' thumb
  
    if FingerErected(4) then
      model.RootJoint.Child(4).Rotation.Rotate(7, 0, 0)
      model.RootJoint.Child(4).Rotation.Rotate(0, -2.2, 0)
      model.RootJoint.Child(4).Child(0).Rotation.Rotate(8, 0, 0)
      model.RootJoint.Child(4).Child(0).Rotation.Rotate(0, -2.2, 0)
    else
      model.RootJoint.Child(4).Rotation.Rotate(0, 2.2, 0)
      model.RootJoint.Child(4).Rotation.Rotate(-7, 0, 0)
      model.RootJoint.Child(4).Child(0).Rotation.Rotate(0, 2.2, 0)
      model.RootJoint.Child(4).Child(0).Rotation.Rotate(-8, 0, 0)
    end if
  
  end select

  FrameCount = FrameCount + 1 ' increase the frame count

  ' have we shown all frames
  if FrameCount >= 10 then
  
    ' yes, we have shown all frames
  
    Timer1.Enabled = False '  stop the animation timer
    FingerErected(FingerIndex) = not FingerErected(FingerIndex) ' toggle the status of finger
    FingerIndex = -1 ' indicate that we have no finger selected
  
  end if

  OpenGLSurface1.Refresh ' render OpenGL surface

else

  ' no finger selected

  Timer1.Enabled = False ' stop animation timer
  FingerIndex = -1 ' indicate that we have no finger selected

end if
20. Add the following code to the Open event of Window1:
Dim model As R3Model ' tempory model object

me.Maximize ' maximize the window
me.MouseCursor = REALbasic.System.Cursors.StandardPointer ' set mouse cursor

' instantiate the Scene object

Scene = new R3Scene
Scene.BackgroundColor.SetValue(1, 1, 1) ' white background

' instantiate a new hand model

model = R3_Model_Hand
Scene.Model.Append model ' add the model to our scene

' starting status of all fingers are erected

FingerErected.Append true  ' pinky
FingerErected.Append true  ' index finger
FingerErected.Append true  ' middle finger
FingerErected.Append true  ' ring finger
FingerErected.Append true  ' thumb

' disable animation timer for now

Timer1.Enabled = false

FingerIndex = -1 ' no finger selected

OpenGLSurface1.Render ' refresh OpenGL surface
21. Add the following code to the Paint event of Window1:
' refresh the OpenGL surface

OpenGLSurface1.Render
22. Add the following code to the KeyDown event of Window1:
' did the user press space?
if Key =" " then
  ' toggle the drawing method
  DrawBones = not DrawBones
  OpenGLSurface1.Render
end if
23. Add the following code to the Open event of OpenGLSurface1:
R3_OGLInitialize ' initalize OpenGL environment

' instantiate memory blocks used to configure our light with

Dim light_position As new MemoryBlock(16)
Dim light_ambience As new MemoryBlock(16)
Dim light_diffANDspec As new MemoryBlock(16)

' define position of the light

light_position.SingleValue(0) = -1.0
light_position.SingleValue(4) = 0.0
light_position.SingleValue(8) = 3.0
light_position.SingleValue(12) = 0.0

' define ambience of the light

light_ambience.SingleValue(0) = 0.1
light_ambience.SingleValue(4) = 0.1
light_ambience.SingleValue(8) = 0.1
light_ambience.SingleValue(12) = 1.0

' define diffuse and specular of the light

light_diffANDspec.SingleValue(0) = 0.5
light_diffANDspec.SingleValue(4) = 0.5
light_diffANDspec.SingleValue(8) = 0.5
light_diffANDspec.SingleValue(12) = 1

' apply light settings

OpenGL.glLightfv OpenGL.GL_LIGHT0, OpenGL.GL_POSITION, light_position ' set position
OpenGL.glLightfv OpenGL.GL_LIGHT0, OpenGL.GL_AMBIENT, light_ambience ' set ambience
OpenGL.glLightfv OpenGL.GL_LIGHT0, OpenGL.GL_DIFFUSE, light_diffANDspec ' set diffuse
OpenGL.glLightfv OpenGL.GL_LIGHT0, OpenGL.GL_SPECULAR, light_diffANDspec ' set specular

OpenGL.glEnable OpenGL.GL_LIGHT0 ' enable our light source

' enable overwriting of material properties with vertex colors

OpenGL.glEnable OpenGL.GL_COLOR_MATERIAL
24. Add the following code to the Resized event of OpenGLSurface1:
' set the viewport rectangle

OpenGL.glViewport 0, 0, OpenGLSurface1.Width, OpenGLSurface1.Height

' set up the perspective projection settings

OpenGL.glMatrixMode OpenGL.GL_PROJECTION
OpenGL.glLoadIdentity
OpenGL.gluPerspective 60.0, OpenGLSurface1.Width / OpenGLSurface1.Height, 1, 100.0

' select and reset the modelview matrix

OpenGL.glMatrixMode OpenGL.GL_MODELVIEW
OpenGL.glLoadIdentity
25. Add the following code to the Render event of OpenGLSurface1:
' is the scene object instantiated?
if Scene <> nil then

  if DrawBones then
  
    ' render the bone structures of the models
    R3_OGLRenderScene Scene, False, False, True
  
  else
  
    ' render the modal normally
    R3_OGLRenderScene Scene
  
  end if

end if
26. Add the following code to the MouseDown event of OpenGLSurface1:
Dim poly As R3Polygon

OpenGLSurface1.MakeCurrent()

' do we have a finger selected?
if FingerIndex < 0 then

  ' no, so let's select one

  poly = R3_OGLPickPolygon(Scene, x, OpenGLSurface1.Height - y) ' pick the polygon at mouse coordinate (x, y)

  ' did the user click on a polygon?
  if poly <> nil then
  
    ' yes, so set the finger index using the polygon index
  
    if poly.Index >= 0 and (poly.Index <= 12) then
      FingerIndex = 0 ' pinky
    elseif poly.Index >= 13 and (poly.Index <= 25) then
      FingerIndex = 1 ' index finger
    elseif poly.Index >= 26 and (poly.Index <= 38) then
      FingerIndex = 2 ' middle finger
    elseif poly.Index >= 39 and (poly.Index <= 51) then
      FingerIndex = 3 ' ring finger
    elseif poly.Index >= 52 and (poly.Index <= 60) then
      FingerIndex = 4 ' thumb
    else
      FingerIndex = -1
    end if
  
    FrameCount = 0 ' reset the framecount
    Timer1.Enabled = true ' start the animation timer
  
  end if

end if

' save the mouse position

MousePrevX = x
MousePrevY = y

return true
27. Add the following code to the MouseDrag event of OpenGLSurface1:
Dim model As R3Model ' temporary model object

model = Scene.Model(0) ' get the first model in the scene

' apply a pitch and yaw rotation using the mouse x and y movement

model.Rotation.RotateProjection((y - MousePrevY), (x - MousePrevX))

OpenGLSurface1.Render ' refresh the OpenGL surface

' save the mouse position

MousePrevX = x
MousePrevY = y
34. Save and run the project.
35. Click and drag on the white background to rotate the hand, click on any finger to animate it and press SPACEBAR to switch between normal and joint view.

Code Analysis

All the required logic for skeletal animation is contained in the new R3Joint class. The R3Joint.Position vector property denotes the absolute position of the joint in 3D space, the R3Joint.Rotation quaternion defines the local rotation of the joint and the R3Joint.Child() array holds references to the child joints. The R3Joint.VertexIndex() array holds indexes to all the vertices in the model that is attached to the joint.

You can imagine a "bone" as being the line drawn from the position of particular joint, to the position of one of its children. To conserve memory, we do not explicitly define memory structures to store "bone" information.

The code on line 15 to 17 in the Timer1.Action event is responsible for rotating a finger. Special code is added on line 27 to 30 for the rotation of the thumb, because each frame requires two successive rotations, a 7 degree pitch rotation, followed by a -2.2 yaw rotation.

IMPORTANT: To reverse the rotation of two successive rotations, you need to give the negative values of the rotations in the reverse order (e.g. for the thumb, 2.2 yaw rotation then a -7 pitch rotation).

The mathematics involved with skeletal animation can sometimes be complicated, but the R3Joint class reduces the complexity considerably. The get a better understanding on how to use the R3Joint class in your own skeletal animation routines, experiment with rotating the quaternions of the joints in the hand model, or better yet, try to build your own model and attach a joint structure to the model. See how your joint rotations affect the final transformation of the model.

Project Downloads

<< Previous Tutorial     Next Tutorial >>


 
All the content on Real 3D Tutorials, with the exception of the SyntaxHighlighter which is licensed under the MIT License, is provided to the public domain and everyone is free to use, modify, republish, sell or give away this work without prior consent from anybody. Content is provided without warranty of any kind. Under no circumstances shall the author(s) or contributor(s) be liable for damages resulting directly or indirectly from the use or non-use of the content.
Should you find the content useful and would like to make a contribution, you can show your support by making a donation.