Skeletal animation

In this tutorial we will be expanding on the program in the Render loop tutorial to show the basics of moving a model's built-in skeleton. We will be using a 'Dwarf' model that has a relatively simple skeletal structure (with 46 bones). The individual bones inside the skeletal structure are usually referred to as 'joints'. The following topics are covered:

  1. Creating a project
  2. Dwarf model and its skeleton
  3. Animating joints

The tutorial's code files and media files are installed along with Ginjo-Builder and can be accessed from the 'Project' window that is shown when the editor starts. This tutorial is called '11_Skeletal_Animation' in the 'Tutorials' tab.

1. Creating a project

First we create a project and compiler options XML file. (See Render loop for a more detailed description of creating a project.)

  1. Create a new project through the Project menu.
  2. Right click on project's top level tree node named "New GBP(Unsaved)" and select Rename folder. Rename the folder to "SkeletalAnim".
  3. Save the project as "SkeletalAnimation.gbp" from the Project menu.
  4. Add a new file to the project tree and save it as "AnimateJoints.gbc"
  5. Add a new file to the project tree and save it as "SkeletalAnim.xml". (The XML file must have the same name as the top level tree node.)

Open "SkeletalAnim.xml", and just like in Render loop add the <exe> and <startup> elements. We will also import the GB namespace so we don't have to type it out every time. (For the full list of XML tags see Compiler options.)

<?xml version="1.0" encoding="utf-8"?>
<root>
   <exe path=".\game.exe" />
   <startup name="AnimateJoints.main" />
   
   <ImportSymbols>
      <namespace name="GB"  />
   </ImportSymbols>
</root>

2. Dwarf model and its skeleton

In our XML file we specified the startup function as "AnimateJoints.main". Double-click on "AnimateJoints.gbc" in the project tree to open it, and add the following function, called "main". (When you run your program this is the function where execution starts.) Initialize the Irrlicht 3D engine and add the Dwarf model as in the code below. In this tutorial we do not have any lights, thus the Lighting material flag of the model must be turned to false. (Otherwise the model will be completely black.)

function main(var:string cmdArgs[]) returns Int32
{
   var:gb.IrrlichtCreationParameters options=new gb.IrrlichtCreationParameters()
   //Without anti-aliasing our model edges would be jagged 
   options.AntiAliasing=255
   //Tell Irrlicht to use OpenGL for graphics
   options.DriverType=gb.video.DriverType.OpenGL
   //Turn off logging, we won't be using it for now
   options.LoggingLevel=gb.LogLevel.None
   
   var:gb.IrrlichtDevice engine = gb.IrrlichtDevice.CreateDevice(options)
   //Also set our window's title
   engine.SetWindowCaption('Skeletal animation')
   
   var:video.VideoDriver driver=engine.VideoDriver
   var:Scene.SceneManager smgr=engine.SceneManager

   //load model from relative path ..\media\dwarf.x
   var:scene.AnimatedMesh DwarfMesh = smgr.GetMesh('..\media\dwarf.x')
   //add model to scene
   var:Scene.AnimatedMeshSceneNode Dwarf
   Dwarf = smgr.AddAnimatedMeshSceneNode(DwarfMesh)

   if(!isNull(Dwarf)){
      Dwarf.Scale = new core.Vector3Df(0.8)
      Dwarf.Rotation=new core.Vector3Df(0,-90,0)
      Dwarf.SetMaterialFlag(video.MaterialFlag.Lighting, false)
   }
}

Next add a Maya camera from the cameras tutorial and the render loop to the "main" function exactly as in the first tutorial.

var:gb.scene.CameraSceneNode Camera= smgr.AddCameraSceneNodeMaya()
camera.Target=Dwarf.Position + new core.Vector3Df(0,30,0)

var:video.Color background=new video.Color(160, 160, 160)
while (engine.Run()){
   driver.BeginScene(video.ClearBufferFlag.All, background)
   smgr.DrawAll()
   driver.EndScene()
}

3. Animating joints

First, we must enable joint animation on the model and retrieve the bone we want to animate using either the bone's index or its name. There is now function for listing the joint names in a model. This information is either provided by the creator of the model or retrieved using a tool such as the Open 3D Model Viewer. Update the model's code as follows:

var:Scene.AnimatedMeshSceneNode Dwarf
Dwarf = smgr.AddAnimatedMeshSceneNode(DwarfMesh)

if(!isNull(Dwarf)){
   Dwarf.Scale = new core.Vector3Df(0.8)
   Dwarf.Rotation=new core.Vector3Df(0,-90,0)
   Dwarf.SetMaterialFlag(video.MaterialFlag.Lighting, false)
   Dwarf.SetJointMode(scene.JointUpdateOnRender.Control)
   Bone=Dwarf.GetJointNode('rsholda')
   if(isNull(Bone)){alert('Bone not found'); return 1}
}

In the movement/animators tutorial we saw how to update a model's position based on elapsed time. We will use the same technique here to change the retrieved bone's rotation. Update the render loop as follows:

var:core.Vector3Df BoneRotation
var:boolean Reverse
var:uint32 oldTime=engine.Timer.Time
var:uint32 newTime
var:single timeDiff
var:video.Color background=new video.Color(160, 160, 160)

Bone.Rotation=new core.Vector3Df(0)
while (engine.Run()){
   BoneRotation=Bone.Rotation
   newTime=engine.Timer.Time
   timeDiff=convert.ToSingle(newTime-oldTime)/1000	//time difference in seconds

   if(Reverse){BoneRotation.X-=timeDiff*50}
   else{BoneRotation.X+=timeDiff*50}
   
   if(BoneRotation.X>45){Reverse=true}
   elseif(BoneRotation.X<0){Reverse=false}

   Bone.Rotation=BoneRotation
   oldTime=newTime

   driver.BeginScene(video.ClearBufferFlag.All, background)
   smgr.DrawAll()
   driver.EndScene()
}