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.
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 if20. 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 surface21. Add the following code to the Paint event of Window1:
' refresh the OpenGL surface OpenGLSurface1.Render22. 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 if23. 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_MATERIAL24. 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.glLoadIdentity25. 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 if26. 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 true27. 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 = y34. Save and run the project.
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.