Main Site Links Resources Tutorials
News VB Gaming Code Downloads DirectX 7
Contact Webmaster VB Programming Product Reviews DirectX 8
  General Multimedia Articles DirectX 9
      Miscellaneous

Vertex / Mesh Animation
Author : Jack Hoxley
Written : 31st December 2000
Contact : [Web] [Email]
Download : Graph_10.Zip [181 Kb]


Contents of this lesson:
1. Introduction
2. Manual Tweening / Linear Interpolation
3. Vector Interpolation
4. Keyframe Interpolation


1. Introduction

Welcome back to part 3 of the advanced 3D geometry section. This lesson will cover one of the most essential features of any modern game - animation. Just read a preview or review of one of the upcoming first person shooter games and you'll see that their putting a lot of emphasis on the player detail - and how they move. Once you have learnt how to do animation you'll be well on your way to being able to create a fully interactive game environment, most things beyond this point are just additions - not requirements.

Animation in 3D can come in several different forms, depending on how you wish your engine and tools to be developed you will need to choose one that'll suit you the best. There are 3 main types of animation, or course there is nothing stopping you from developing your own types...
• Manual Tweening / Linear Interpolation
• Vector Interpolation
• Keyframe interpolation

This lesson will cover all 3 of the above animation models, by the end you should be able to choose one to best suit your needs.


2. Manual Tweening / Linear Interpolation

This is one of the easiest types of animation to use, which is why I'll cover it first. The only downside being that it's almost useless when you're dealing with very complicated models - or models with lots and lots of vertices. This is a type of tweening where it would definately benefit from using index buffers.

Interpolation is basically how something changes over a certain time or space. So far we've seen Direct3D interpolate colour across a triangle - the way it smoothly fades from colour to colour. Linear interpolation is the same but you know that it'll go in a straight line from A to B, as opposed to a quadratic interpolation which will take a weighted/curved route from A to B. Tweening is the same again really.

In order to interpolate from one position to another we need to use this formula:

(B * V) + A * (1.0 - V)

Where A is the source, of any value; B is the destination, of any value; and V is the interpolant - a value between 0.0 and 1.0, where 0.0 is at A and 1.0 is at B. Straight away you should realise that this is a fairly complicated formula - applying this to 800 vertices on every frame is going to hurt your frame rate. If you have the ability, creating an ActiveX DLL to do the work, or better yet a C/C++ DLL to do all the maths.

All we're going to do is write a function that takes 2 vertices, an interpolant value and then spits out another vertex. We'll then run all our vertices through this function on every frame.

This is the interpolating function:

Private Function TweenVertices(Source As LITVERTEX, Dest As LITVERTEX, TweenAmount As Single) As LITVERTEX
    TweenVertices.X = (Dest.X * TweenAmount) + Source.X * (1# - TweenAmount)
    TweenVertices.Y = (Dest.Y * TweenAmount) + Source.Y * (1# - TweenAmount)
    TweenVertices.Z = (Dest.Z * TweenAmount) + Source.Z * (1# - TweenAmount)
    TweenVertices.color = Source.color
End Function

There, not greatly complicated at all, if you were using unlit vertices - vertices with a normal vector - you would need to modify this code so that it also tweened from the source normal to the destination normal - but all that requires is 3 almost identical lines of code. Note that we have to set the colour of the vertex as well - because of the way that VB does it's functioncalling we have to recreate the entire vertex - a proper tweening function would also tween the colours, texture coordinates and specular values - we'll do that later on; but it isn't really very complicated.

There is one huge limitation to this method - and it's deceptively simple; it's linear. If you were trying to model a human walking around you may well find that linear interpolation doesn't really look quite right - we dont move our limbs in perfectly straight lines from A to B. But you'll just have to test it and see how badly it affects your models.

Now that we have the tweening function we're going to do something to it. Remember the cube example from the last lesson - the one that uses indices. We're going to create 3 cubes - a source, destination and current. we'll then interpolate between them.

'//At the start the current cube is the same as the source
CubeVertices(0) = CreateLitVertex(-1, -1, -1, &HFF0000, 0, 0, 0)
CubeVertices(1) = CreateLitVertex(-1, 1, -1, &HFF00&, 0, 0, 0)
CubeVertices(2) = CreateLitVertex(1, -1, -1, &HFF&, 0, 0, 0)
CubeVertices(3) = CreateLitVertex(1, 1, -1, &HFF00FF, 0, 0, 0)
CubeVertices(4) = CreateLitVertex(-1, -1, 1, &HFFFF00, 0, 0, 0)
CubeVertices(5) = CreateLitVertex(-1, 1, 1, &HFFFF, 0, 0, 0)
CubeVertices(6) = CreateLitVertex(1, -1, 1, &HFFCC00, 0, 0, 0)
CubeVertices(7) = CreateLitVertex(1, 1, 1, &HFFFFFF, 0, 0, 0)
    
'The source - a normal cube
CubeVerticesSource(0) = CreateLitVertex(-1, -1, -1, &HFF0000, 0, 0, 0)
CubeVerticesSource(1) = CreateLitVertex(-1, 1, -1, &HFF00&, 0, 0, 0)
CubeVerticesSource(2) = CreateLitVertex(1, -1, -1, &HFF&, 0, 0, 0)
CubeVerticesSource(3) = CreateLitVertex(1, 1, -1, &HFF00FF, 0, 0, 0)
CubeVerticesSource(4) = CreateLitVertex(-1, -1, 1, &HFFFF00, 0, 0, 0)
CubeVerticesSource(5) = CreateLitVertex(-1, 1, 1, &HFFFF, 0, 0, 0)
CubeVerticesSource(6) = CreateLitVertex(1, -1, 1, &HFFCC00, 0, 0, 0)
CubeVerticesSource(7) = CreateLitVertex(1, 1, 1, &HFFFFFF, 0, 0, 0)
   
'The destination - a pyramid type shape
CubeVerticesDest(0) = CreateLitVertex(-1, -1, -1, &HFF0000, 0, 0, 0)
CubeVerticesDest(1) = CreateLitVertex(-0.1, 1, -0.1, &HFF00&, 0, 0, 0)
CubeVerticesDest(2) = CreateLitVertex(1, -1, -1, &HFF&, 0, 0, 0)
CubeVerticesDest(3) = CreateLitVertex(0.1, 1, -0.1, &HFF00FF, 0, 0, 0)
CubeVerticesDest(4) = CreateLitVertex(-1, -1, 1, &HFFFF00, 0, 0, 0)
CubeVerticesDest(5) = CreateLitVertex(-0.1, 1, 0.1, &HFFFF, 0, 0, 0)
CubeVerticesDest(6) = CreateLitVertex(1, -1, 1, &HFFCC00, 0, 0, 0)
CubeVerticesDest(7) = CreateLitVertex(0.1, 1, 0.1, &HFFFFFF, 0, 0, 0)

Not greatly complicated either - only the last cube has been altered, we didn't really need to set the CubeVertices( ) array as the first loop would have set it to be the CubeVerticesSource( ) array; but for clarity I've left it in there.

Now that we have the relevent arrays ready, we need to know how to update them - again, not greatly complicated; but you can easily see how processing a 1000-2000 vertex model will severely hurt your frame rate.

This procedure is going to be called on every loop, and it will run through every vertex (we only have 8) and alter it - then plug it back into the vertex buffer.

Private Sub UpdateAnimation()
Dim I As Integer

'//Update the position and/or Direction
If AnimTweenDir = True Then
    AnimTweenFactor = AnimTweenFactor + (((GetTickCount() - LastTimeTweened) / 1000) * 1#)
    LastTimeTweened = GetTickCount
    If AnimTweenFactor >= 1# Then
        AnimTweenFactor = 1#
        AnimTweenDir = False
    End If
Else
    AnimTweenFactor = AnimTweenFactor - (((GetTickCount() - LastTimeTweened) / 1000) * 1#)
    LastTimeTweened = GetTickCount
    If AnimTweenFactor <= 0# Then
        AnimTweenFactor = 0#
        AnimTweenDir = True
    End If
End If

'//Update the current vertices
For I = 0 To 7
    CubeVertices(I) = TweenVertices(CubeVerticesSource(I), CubeVerticesDest(I), AnimTweenFactor)
Next I

'//Update the vertex buffer...
    
    If D3DVertexBuffer8SetData(VBuffer, 0, Len(CubeVertices(0)) * 8, 0, CubeVertices(0)) = D3DERR_INVALIDCALL Then GoTo BailOut:

Exit Sub
BailOut:
Debug.Print "Error occured whilst updating the animation..."
End Sub

Most of the work here is done by the little function we designed earlier; all this part does is work out what the new tweening value should be and then plug the processed vertices into the vertex buffer again. You may well be a little confused about the (((GetTickCount() - LastTimeTweened) / 1000) * 1#) part - this is time based animation.

There are two types of animation - frame based and time based. Frame based is where you increment the frame number by a fixed amount each time - 1 frame every cycle for example. BUT, if you do this slow computers will get jerky animation and fast computers will get animations that go too fast - you could limit the frame rate to 33fps if you wanted - but that's only a half measure, slow computers will still get poor animations. On the other hand you could design your game using time based animations - which I strongly advise that you do. Time based animations, rather than say "1 frame every cycle" are "move 30 frames every second", and you then calculate it so that if the frame rate is low it goes jerky and if it is high it goes perfectly smooth - but in both cases slow and fast we get from the same point to the same point at the same point in time. We do this by calculating the time that has passed since we last updated something - then divide this by 1000 (1 second) and then multiply it by the constant we want the animations to run at. For example, say we want it to go at 30fps, and it's been half a second since we last updated (ultra low frame rate) the current frame will be 0.5 * 30 = 15, the correct frame to be on half way through 1 second. If it's going ultra fast and has only been 10ms since we last called the function we will have 0.1 * 30 = 3, which is correct for only 10ms through the second....

Okay, we've done a whistle stop tour through custom interpolation and time based animations - experiment with this method and see how the frame rate drops when you alter a large model. And if you're keen to do extra work try and alter the TweenVertices( ) code so that it tweens between the colours, diffuse and specular and alters the texture coordinates as well. One last note before we move on - should you want to use this method it would be advisable to write an algorithm that ONLY updates vertices that need updating - there's no point in calculating the new value if the source and destination are the same....


3. Vector Interpolation

Vector interpolation is almost identical to the above method - except Direct3D does the TweenVertices( ) part for you - so you may well see a little bit of a speed improvement when using it. On top of this we also have access to several other types of tweening via the D3DX library...

Using the D3DX library we can do linear interpolation for all the main parts of a vertex that we use, the following list are the ones I'd choose for blending my vertices:

D3DXVec3Lerp( VOut as D3DVECTOR, V1 as D3DVECTOR, V2 as D3DVECTOR, S as Single)
- VOut = The result of the interpolation
- V1 = The source coordinates
- V2 = The destination coordinates
- S = The interpolation amount - between, but not limited to, 0.0 - 1.0 scale; where 0 is the source and 1 is the destination
- Uses = Perfect for interpolating the actual position and normals.

D3DXColorLerp( COut as D3DCOLORVALUE, C1 as D3DCOLORVALUE, C2 as D3DCOLORVALUE, S as Single)
- COut = The resulting colour
- C1 = The source colour
- C2 = The destination colour
- S = The interpolant, on a 0.0 to 1.0 scale
- Uses = Perfect for interpolating the colours of vertices. The example will show you how to use D3DCOLORVALUEs to store the vertex colour

D3DXVec2Lerp( VOut as D3DVECTOR2, V1 as D3DVECTOR2, V2 as D3DVECTOR2, S as Single)
- VOut = The result of this interpolation
- V1 = The source coordinates
- V2 = The destination coordinates
- S = The interpolant, on a 0.0 to 1.0 scale
- Uses = This interpolates a 2D coordinate, which is what texture coordinates are - so, perfect for blending the texture coordinates

There isn't anything else that you're likely to use that doesn't fall into any of these catergories, but there are a few alternatives that you can use, the simplest one being:

D3DXVec3Hermite( VOut as D3DVECTOR, V1 as D3DVECTOR, T1 as D3DVECTOR, V2 as D3DVECTOR, T2 as D3DVECTOR, S as Single)
- VOut = The Result
- V1 = The Source Coordinate
- T1 = The Tangent at the Source coordinate, this is the direction and speed the line will leave the source point.
- V2 = The Destination Coordinate
- T2 = The Tangent at the Destination coordinate, this is the direction and speed the line will enter the destination point.
- S = The Interpolant Value
- Uses = This generates a curved path that goes through the two control points.

To alter the code we have to use the D3DX library we need to change our vertex description, we're going to embed an additional ARGB value in each vertex, whilst Direct3D shouldn't pay any attention to it it's incredibly useful for what we want to do with colours.

Private Type LITVERTEX
    X As Single
    Y As Single
    Z As Single
    color As Long
    specular As Long
    tu As Single
    tv As Single
    ColorEx As D3DCOLORVALUE
End Type



'//NB: The FVF for this type doesn't change

now that's prepared we also need to alter the way we blend our vertices - this is the revised version:

Private Function TweenVertices(Source As LITVERTEX, Dest As LITVERTEX, TweenAmount As Single) As LITVERTEX
Dim vResult As D3DVECTOR
Dim vResult2 As D3DVECTOR2
    '//1. Tween the positions of the vertices
    D3DXVec3Lerp vResult, MakeVector(Source.X, Source.Y, Source.Z), MakeVector(Dest.X, Dest.Y, Dest.Z), TweenAmount
    TweenVertices.X = vResult.X
    TweenVertices.Y = vResult.Y
    TweenVertices.Z = vResult.Z
        
        '//1a. Tween the normals
            ' - Same as above, but not used in this sample
    
    '//2. Tween the Texture Coordinates
    D3DXVec2Lerp vResult2, MakeVector2D(Source.tu, Source.tv), MakeVector2D(Dest.tu, Dest.tv), TweenAmount
    TweenVertices.tu = vResult2.X
    TweenVertices.tv = vResult2.Y
    
    '//3. Tween the Colour values
    D3DXColorLerp TweenVertices.ColorEx, Source.ColorEx, Dest.ColorEx, TweenAmount
    With TweenVertices.ColorEx
        TweenVertices.color = RGB(.B * 255, .G * 255, .R * 255)
    End With
    
End Function

There are two things to watch out for here - firstly we need to dereference a vector to a vertex, we cant pass a vertex to the D3DX functions, so we pass a vector and then extract the information that we want from it. Secondly, if you notice the colours of the vertex appear to be slightly wrong - the RGB( R, G, B ) function is actually RGB( B, G, R ) - this is because Direct3D stores it's colours slightly differently to the way RGB( ) does, but reversing the colour orders works out fine.

You'll also need to alter the way that the vertices are created - so that we encode this new type of colour information, if you cant work it out yourself it's in the source code for this lesson; but for time and space I haven't stuck it in here....


4. Keyframe Interpolation

Keyframe interpolation is probably the type of animation that you're going to use. When we're doing complicated animations with lots and lots of geometry saving a snapshot of every frame would use up gigabytes of storage space - per animation. Instead we simplify the process, using the techniques you've already learnt we take a series of keyframes and predict the frames in the middle.

To better illustrate this think about it this way; we have a coke can that starts normal and ends up, 50 frames later flat (because someone stood on it). In our 3D editor we just scaled the object down, or moved the vertices/faces downwards to simulate it being smaller, we then stored all 50 frames in a file. Say we use one line in the file for every face, and there are 1000 faces in the coke can model; we'd therefore have to store 50,000 lines of data for a relatively simple animation - not very clever. How about we store a keyframe at the start (as a normal coke can) and at the end (as a flat coke can), that means we only store 2000 lines of data - quite an improvement (4% of the size). Surprisingly it would probably be much much faster as well. Imagine loading 50,000 lines of data into memory, or loading each frame as we go - either very slow or very jerky.

In order to do keyframe interpolation all we need to know is the vertex data at each keyframe, and at each keyframe what the current time is, then we can work out the rest. Unfortunately it also means storing 10 or so files for every animation - but if we use archives and/or compression it shouldn't be a problem at all.

This example wont load animation data from a file (except for the objects themselves), so all keyframe time constants will be built into the program. As well as this, you should also consider writing a class module to handle generic animations - it's not difficult, but it's a little big for this lesson. The class should be able to import a standardised file format (that you can make up), load the relevent objects and textures, then animate itself automatically so to speak - and the host application need only call an update or render procedure for it to calculate everything.

The theory behind keyframe animation is very simple, we gather the required information, then on each pass we calculate how long it's been since the animation has started - and therefore what position we are in the animation; we then calculate what the previous keyframe and the next keyframe will be - then work out how far between them we are. Finally we do a normal interpolation, and put the data back into a mesh object and render it. More on the specifics of this in a minute...

I've already mentioned that we're going to load objects from a file, this isn't going to be any different from what we've done in previous lessons (lesson 08 to be precise), but we're going to have to learn how to get the vertex data from the D3DXMESH object. Luckily the D3DX library helps us out here - with two very neat, and very, very useful functions:

Getting Data:
D3DXMeshVertexBuffer8GetData( D3DXMeshobj As Unknown, Offset As Long, Size As Long, Flags As Long, Data As Any) As Long
- D3DXMeshobj As Unknown =
A D3DXMESH object that you want to extract the data from.
- Offset As Long = How far into the vertex buffer we want to start reading, 0 is the beginning
- Size As Long = Size of the vertex buffer, this will be Len(D3DVERTEX) * Mesh.GetNumVertices
- Flags As Long = A combination of the CONST_D3DLOCKFLAGS, leave as 0.
- Data As Any = The first element in the array that you want the data to be read into, should be an array of D3DVERTEX vertices
- Return Code As Long = Returns D3D_OK for success, or either of D3DERR_INVALIDCALL or E_INVALIDARG for an error

Setting Data:
D3DXMeshVertexBuffer8SetData( D3DXMeshobj As Unknown, Offset As Long, Size As Long, Flags As Long, Data As Any) As Long
- D3DXMeshobj As Unknown =
The D3DXMESH object that defines where you want the data to be placed
- Offset As Long = How far into the Destination vertex buffer you want to place the data
- Size As Long = The Size of the buffer in bytes, this will be Len(D3DVERTEX) * Mesh.GetNumVertices
- Flags As Long = A Combination of the CONST_D3DLOCKFLAGS, leave as 0
- Data As Any = The first element in the array of data you want placed in the mesh's vertex buffer
- Return Code As Long = D3D_OK for success or D3DERR_INVALIDCALL or E_INVALIDARG for failure

We now have all the information that we need to perform keyframe animation. We can load objects, from .X files, into D3DXMESH objects, we can access the raw vertex data for these objects, and we can interpolate between keyframes (unless you fell asleep earlier!). So we now need to make some code out of all of this.

First off the pure maths parts - as mentioned earlier. We'll assume that our animation always runs from time 0 until time n, and it'll be measured in milliseconds - so we can easily use GetTickCount( ) for our timing functions. We'll also design a structure for each keyframe:

Private Type KeyFrame
    Mesh As D3DXMesh '//This is the object loaded from a file
    MatList() As D3DMATERIAL8 '//This is an array of the materials for each object
    TexList() As Direct3DTexture8 '//This is an array of each texture used
    nMaterials As Long '//How many materials and textures we're going to be using
    VertexList() As D3DVERTEX '//The raw vertex data for this keyframe
    TimeIndex As Long '//Where this keyframe is in the animation
End Type


Dim kfAnimLength As Long '//How long this animation will run for, before looping/Terminating
Dim nKeyFrames As Integer '//How many keyframe make up our animation...
Dim AnimLastStartAt As Long '//When we last started the animation playing...
Dim kfAnim() As KeyFrame '//An open ended array of keyframes - that make up our animation
Dim kfCurrent As KeyFrame '//Storage for the current frame.

Not too complicated really, we also want to write a function that'll fill one of these structures for us - if we give it the file name. We'll use this function:

'//CreateKeyFrameFromFile()
'   - Filename = A valid filename for a .X file 3D object
'   - TexturePrefix = the folder, terminating in a "\" where textures for this object can be loaded
'   - Time = The time index for this keyframe.
'
'   Output = a filled, valid structure.
'
Private Function CreateKeyFrameFromFile(Filename As String, TexturePrefix As String, Time As Long) As KeyFrame
On Error GoTo ErrOut: '//Our error handler

'//0. Any variables required
        Dim I As Long
        Dim XBuffer As D3DXBuffer
        Dim TextureFile As String
        Dim hResult As Long
        
'//1. Load the X-File into memory
        Set CreateKeyFrameFromFile.Mesh = D3DX.LoadMeshFromX(Filename, D3DXMESH_MANAGED, D3DDevice, Nothing, _
						   XBuffer, CreateKeyFrameFromFile.nMaterials)
        If CreateKeyFrameFromFile.Mesh Is Nothing Then GoTo ErrOut:  '//Dont continue if the above call did not work

'//2. Generate materials and textures
        ReDim CreateKeyFrameFromFile.MatList(CreateKeyFrameFromFile.nMaterials) As D3DMATERIAL8
        ReDim CreateKeyFrameFromFile.TexList(CreateKeyFrameFromFile.nMaterials) As Direct3DTexture8
        

        For I = 0 To CreateKeyFrameFromFile.nMaterials - 1
        
        '//Get D3DX to copy the data that we loaded from the file into our structure
        D3DX.BufferGetMaterial XBuffer, I, CreateKeyFrameFromFile.MatList(I)
        
        '//Fill in the missing gaps - the Ambient properties
        CreateKeyFrameFromFile.MatList(I).Ambient = CreateKeyFrameFromFile.MatList(I).diffuse
        
        '//get the name of the texture used for this part of the mesh
        TextureFile = D3DX.BufferGetTextureName(XBuffer, I)
        
        '//Now create the texture
        If TextureFile <> "" Then 'Dont try to create a texture from an empty string
            Set CreateKeyFrameFromFile.TexList(I) = D3DX.CreateTextureFromFileEx(D3DDevice, TexturePrefix & TextureFile, _
							D3DX_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, 0, _
							D3DFMT_UNKNOWN, D3DPOOL_MANAGED, _
							 D3DX_FILTER_LINEAR, D3DX_FILTER_LINEAR, 0, _
							ByVal 0, ByVal 0)
        End If
        Next I

'//3. Extract raw vertex data
        ReDim CreateKeyFrameFromFile.VertexList(CreateKeyFrameFromFile.Mesh.GetNumVertices) As D3DVERTEX
        hResult = D3DXMeshVertexBuffer8GetData(CreateKeyFrameFromFile.Mesh, 0, Len(CreateKeyFrameFromFile.VertexList(0)) * _ 
				       CreateKeyFrameFromFile.Mesh.GetNumVertices, 0, CreateKeyFrameFromFile.VertexList(0))
        If Not (hResult = D3D_OK) Then GoTo ErrOut: '//Break out if we could not extract the data...
        
        CreateKeyFrameFromFile.TimeIndex = Time '//Last thing to do - fill the time index for this keyframe.
        
'//NB: At this point we have filled the structure with all the information that we need.

Exit Function
ErrOut:
Debug.Print "An error occured while generating a keyframe from the file: " & Filename
End Function
 

There isn't anything very new in this function, object loading has been covered before, and extracting the raw data has just been explained. From this code we can now create several keyframes, this example will use 4 keyframes; you can quite easily re-engineer this to be whatever number you want...

In the Initialise( ) function we'll stick these lines of code:

'#######################
'## SETUP KEYFRAMES ##
'#######################

    nKeyFrames = 4
    kfAnimLength = 2500
    AnimLastStartAt = GetTickCount()
    ReDim kfAnim(nKeyFrames - 1) As KeyFrame
    
    kfAnim(0) = CreateKeyFrameFromFile(App.Path & "\frame0.x", App.Path & "\", 0)
    kfAnim(1) = CreateKeyFrameFromFile(App.Path & "\frame1.x", App.Path & "\", kfAnimLength * (1 / 3))
    kfAnim(2) = CreateKeyFrameFromFile(App.Path & "\frame2.x", App.Path & "\", kfAnimLength * (2 / 3))
    kfAnim(3) = CreateKeyFrameFromFile(App.Path & "\frame3.x", App.Path & "\", kfAnimLength)
    kfCurrent = CreateKeyFrameFromFile(App.Path & "\frame0.x", App.Path & "\", 0) 'Time index is irrelevent really ...

Note that I've specified the time index when creating the keyframes in relation to the "kfAnimLength" variable; this is only for convenience - I can change the "kfAnimLength" variable and the rest of the animation would scale accordingly. If you were writing a generalised version of this code (for a class) then you may well want to specify time indices on a 0.0 to 1.0 scale - to act as a multiplier for the animation length constant; but that's completely up to you.

Now, if we ran the program it'd trundle away loading the data - then do nothing; we now need to rewrite the code so it actually displays our animation. The first part is to set up some code that works out which key frames we need to use, and what time index we're at.

The maths for this will be:

CurrentTimeIndex = GetTickCount( ) - AnimLastStartAt

Then with this value we'll scan through our array and calculate which is the nearest, previous keyframe:

For I = 0 To nKeyFrames - 2 '1 less in array, and 1 less than that...
    If CurrentTimeIndex >= kfAnim(I).TimeIndex Then
        PrevFrame = I
        NextFrame = I + 1
    End If
Next I

At this point we'll now know which frames to blend between; we finally need to work out by how much we interpolate.

'//Where STime, eTime and cTime are Start, End and Current respectively...

'//Calculate the Time Values
sTime = kfAnim(PrevFrame).TimeIndex
eTime = kfAnim(NextFrame).TimeIndex
cTime = CurrentTimeIndex

eTime = eTime - sTime
cTime = cTime - sTime
sTime = sTime - sTime 'simplifies to 0
InterpolateAmount = cTime / eTime

Now all we need to do is interpolate the vertex data - something we've done a million times aleady this lesson (maybe a few less, really). It's at this point that we MUST assume that all the models have the same amount of vertex data - one less and we get a subscript error, one more and we'll get a vertex that doesn't move. It would be ideal to perform some kind of test on the models first - comparing the number of vertices, faces, textures and so on ...

For I = 0 To kfCurrent.Mesh.GetNumVertices '//Cycle through every vertex
   '//2a. Interpolate the Positions
      D3DXVec3Lerp vTemp3D, MakeVector(kfAnim(PrevFrame).VertexList(I).X, kfAnim(PrevFrame).VertexList(I).Y, _ 
	             kfAnim(PrevFrame).VertexList(I).Z), MakeVector(kfAnim(NextFrame).VertexList(I).X, kfAnim(NextFrame).VertexList(I).Y, _
	             kfAnim(NextFrame).VertexList(I).Z), InterpolateAmount
      kfCurrent.VertexList(I).X = vTemp3D.X
      kfCurrent.VertexList(I).Y = vTemp3D.Y
      kfCurrent.VertexList(I).Z = vTemp3D.Z
                
   '//2b. Interpolate the Normals
      D3DXVec3Lerp vTemp3D, MakeVector(kfAnim(PrevFrame).VertexList(I).nx, kfAnim(PrevFrame).VertexList(I).ny, _ 
	             kfAnim(PrevFrame).VertexList(I).nz), MakeVector(kfAnim(NextFrame).VertexList(I).nx, kfAnim(NextFrame).VertexList(I).ny, _
	             kfAnim(NextFrame).VertexList(I).nz), InterpolateAmount
      kfCurrent.VertexList(I).nx = vTemp3D.X
      kfCurrent.VertexList(I).ny = vTemp3D.Y
      kfCurrent.VertexList(I).nz = vTemp3D.Z
            
   '//2c. Interpolate the Texture Coordinates
      D3DXVec2Lerp vTemp2D, MakeVector2D(kfAnim(PrevFrame).VertexList(I).tu, kfAnim(PrevFrame).VertexList(I).tv), _
	            MakeVector2D(kfAnim(NextFrame).VertexList(I).tu, kfAnim(NextFrame).VertexList(I).tv), InterpolateAmount
      kfCurrent.VertexList(I).tu = vTemp2D.X
      kfCurrent.VertexList(I).tv = vTemp2D.Y
Next I
 

okay, so we now have the data prepared - we just need to place it back in where we can use it - the current mesh; so, using one of our new techniques we'll copy all our data back in:

hResult = D3DXMeshVertexBuffer8SetData(kfCurrent.Mesh, 0, Len(kfCurrent.VertexList(0)) * kfCurrent.Mesh.GetNumVertices, _
                                                                0, kfCurrent.VertexList(0))

Not greatly complicated really; if we now render "kfCurrent.Mesh" we should see our animation in progress - perfect. With the code that I've just been through it's not too difficult to add more keyframes and quite easily alter the speed that the animation runs at. The only major improvement that I haven't mentioned yet is about textures; currently we have only 4 keyframes - each with it's own copy of the texture; what if there were 15 keyframes, each with 4 medium sized textures - you're holding 56 too many textures - why store multiple copies? A later tutorial will cover the design of a texture pool class - so we only need to store one copy of each texture.


So, 10 lessons down now - From this point onwards you should easily be able to create a simple graphics engine for your game. If you're feeling brave maybe you should - but dont even begin to think about the next Quake game that you'd love to make; go for something simple like pong, tetris or space invaders...

Later on in this series I might upgrade this last keyframe method to use a C++ DLL component; the tutorial is already written for making something like this, but I might be kind enough to create it for you - and design a global, general class for all keyframe animation... but you'll have to wait and see :)

I strongly suggest that you download the source code for this lesson from the top of the page - whilst all the major code has been covered there are still a few very small things that I've left out...

Assuming you've swallowed all of this lesson, onto Lesson 11 : Advanced Geometry part 4 - Using point sprites for particle effects

DirectX 4 VB 2000 Jack Hoxley. All rights reserved.
Reproduction of this site and it's contents, in whole or in part, is prohibited,
except where explicitly stated otherwise.
Design by Mateo
Contact Webmaster