Timing in Animation and Movement(2)

Timing in Animation and Movement(2)

Animating with Time

In the olden days, games were made to animate graphics based on every frame processed. To ensure that the animations always ran at the same speed, those games sometimes limited the number of frames per second that could be processed. Of course, those old games were made for computers that couldn't easily process more than 20 to 30 frames per second, so it was safe to assume that limiting the number of frames per second would never surpass that 20 or 30 frames per second mark.

But that was then, and this is now. Modern computers can run circles around their ancestors, and limiting the number of frames to control animation is a definite no−no in this day and age. You need to base the speed of animation on the amount of time that has elapsed since the start of the animation sequence. Doing so is no problem because you already know that you can record the time when the animation started. Additionally, for each frame to update, you can read the current time and subtract the starting animation time. The result is a time value to use as an offset to your animation sequence.

Suppose you are using time−based key frames in your animation engine. You can use a simple key−frame structure that stores the time and a transformation matrix to use, such as this:

typedef struct sKeyframe {
  DWORD Time;
  D3DMATRIX matTransformation;
} sKeyframe;

As is typical for key frames, you can store an array of matrices, each with its own unique time. These structures are stored in chronological order, with the lower time values first. Therefore, you can create a small sequence of transformations to orient an object over time (see Figure 2.1).

Timing in Animation and Movement(2)_第1张图片

To replicate the key frames shown in Figure 2.1, I've constructed the following array:

sKeyframe Keyframes[4] = {
{ 0, 1.00000f, 0.00000f, 0.00000f, 0.00000f,
0.00000f, 1.00000f, 0.00000f, 0.00000f,
0.00000f, 0.00000f, 1.00000f, 0.00000f,
0.00000f, 0.00000f, 0.00000f, 1.00000f; },

{ 400, 0.000796f, 1.00000f, 0.00000f, 0.00000f,
−1.00000f, 0.000796f, 0.00000f, 0.00000f,
0.00000f, 0.00000f, 1.00000f, 0.00000f,
50.00000f, 0.00000f, 0.00000f, 1.00000f; },

{ 800, −0.99999f, 0.001593f, 0.00000f, 0.00000f,
−0.001593f, −0.99999f, 0.00000f, 0.00000f,
0.00000f, 0.00000f, 1.00000f, 0.00000f,
25.00000f, 25.00000f, 0.00000f, 1.00000f; },

{ 1200, 1.00000f, 0.00000f, 0.00000f, 0.00000f,
0.00000f, 1.00000f, 0.00000f, 0.00000f,
0.00000f, 0.00000f, 1.00000f, 0.00000f,
0.00000f, 0.00000f, 0.00000f, 1.00000f; }
};

Now comes the fun part. Using the timing methods you read about previously, you can record the time at which the animation started. And, for each frame to update the animation, you can calculate the elapsed time since the animation started (using that as an offset to the key frames). Create a simple frame update function that will determine which transformation to use depending on the elapsed time since the update function was first called.

void FrameUpdate()
{
  static DWORD StartTime = timeGetTime();
  DWORD Elapsed = timeGetTime() − StartTime;

With the elapsed time now in hand, you can scan the key frames to look for the two between which the time value lies. For example, if the current time is 60 milliseconds, the animation is somewhere between key frame #0 (at 0 milliseconds) and key frame #1 (at 400 milliseconds). A quick scan through the key frames determines which to use based on the elapsed time.

DWORD Keyframe = 0; // Start at 1st keyframe

for(DWORD i=0;i<4;i++) {
  // If time is greater or equal to a key−frame's time then update the keyframe to use
  if(Time >= Keyframes[i].Time)
    Keyframe = i;
}

At the end of the loop, the Keyframe variable will hold the first of the two key frames between which the animation time lies. If Keyframe isn't the last key frame in the array (in which there are four key frames), then you can add 1 to Keyframe to obtain the second key frame. If Keyframe is the last key frame in the array, you can use the same key−frame value in your calculations.

Using a second variable to store the next key frame in line is perfect. Remember that if Keyframe is the last key frame in the array, you need to set this new key frame to the same value.

DWORD Keyframe2 = (Keyframe==3) ? Keyframe:Keyframe + 1;

Now you need to grab the time values and calculate a scalar based on the time difference of the keys and the position of the key frame between the keys.

DWORD TimeDiff = Keyframes[Keyframe2].Time − Keyframes[Keyframe].Time;

// Make sure there's a time difference to avoid divide−by−zero errors later on.
if(!TimeDiff)
  TimeDiff=1;

float Scalar = (Time − Keyframes[Keyframe].Time) / TimeDiff;

You now have the scalar value (which ranges from 0 to 1) used to interpolate the transformation matrices of the keys. To make it easy to deal with the transformation matrices, those matrices are cast to a D3DXMATRIX type so that D3DX does the hard work for you.

// Calculate the difference in transformations
D3DXMATRIX matInt = D3DXMATRIX(Keyframes[Keyframe2].matTransformation) −
                                    D3DXMATRIX(Keyframes[Keyframe].matTransformation);

matInt *= Scalar; // Scale the difference

// Add scaled transformation matrix back to 1st keyframe matrix
matInt += D3DXMATRIX(Keyframes[Keyframe].matTransformation);

At this point, you have the proper animated transformation matrix to use stored in matInt. To see your hard work come to life, set matInt as the world transformation and render your animated mesh.

 

Main Routine:

#include  < windows.h >
#include 
" d3d9.h "
#include 
" d3dx9.h "
#include 
" Direct3D.h "

IDirect3D9
*                 g_d3d;
IDirect3DDevice9
*         g_device;
D3DXMESHCONTAINER_EX
*     g_robot_mesh_container;

struct  sKeyFrame
{
    DWORD        time;
    D3DMATRIX    mat_trans;
};

//  NOTE: transfromation matrix of key frame 0 is same as key frame 3.
sKeyFrame g_key_frames[ 4 =  
{
  
//  key_frame 1, 0ms
  {    0 1.000000f 0.000000f 0.000000f 0.000000f ,
         
0.000000f 1.000000f 0.000000f 0.000000f ,
         
0.000000f 0.000000f 1.000000f 0.000000f ,
         
0.000000f 0.000000f 0.000000f 1.000000f  },

  
//  key_frame 2, 40ms
  {   400 0.000796f 1.000000f 0.000000f 0.000000f ,
         
- 1.000000f 0.000796f 0.000000f 0.000000f ,
          
0.000000f 0.000000f 1.000000f 0.000000f ,
         
50.000000f 0.000000f 0.000000f 1.000000f  },

  
//  key_frame 3, 80ms
  {   800 - 0.999999f ,   0.001593f 0.000000f 0.000000f ,
          
- 0.001593f - 0.999999f 0.000000f 0.000000f ,
           
0.000000f ,   0.000000f 1.000000f 0.000000f ,
          
25.000000f 25.000000f 0.000000f 1.000000f  },

  
//  key_frame 4, 120ms
  {  1200 1.000000f 0.000000f 0.000000f 0.000000f ,
          
0.000000f 1.000000f 0.000000f 0.000000f ,
          
0.000000f 0.000000f 1.000000f 0.000000f ,
          
0.000000f 0.000000f 0.000000f 1.000000f  }
};

const   char  g_class_name[]  =   " TimeAnimClass " ;
const   char  g_caption[]  =   " Timed Animation " ;

LRESULT FAR PASCAL window_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

bool  do_init(HWND hwnd);
void  do_shutdown();
void  do_frame();

///////////////////////////////////////////////////////////////////////////////////////////// /

int  PASCAL WinMain(HINSTANCE inst, HINSTANCE, LPSTR,  int  cmd_show)
{      
  CoInitialize(NULL);    
//  Initialize the COM system

  WNDCLASSEX win_class;

  
//  Create the window class here and register it
  win_class.cbSize         =   sizeof (win_class);
  win_class.style         
=  CS_CLASSDC;
  win_class.lpfnWndProc   
=  window_proc;
  win_class.cbClsExtra    
=   0 ;
  win_class.cbWndExtra    
=   0 ;
  win_class.hInstance     
=  inst;
  win_class.hIcon         
=  LoadIcon(NULL, IDI_APPLICATION);
  win_class.hCursor       
=  LoadCursor(NULL, IDC_ARROW);
  win_class.hbrBackground 
=  NULL;
  win_class.lpszMenuName  
=  NULL;
  win_class.lpszClassName 
=  g_class_name;
  win_class.hIconSm       
=  LoadIcon(NULL, IDI_APPLICATION);

  
if ( ! RegisterClassEx( & win_class))
    
return  FALSE;

  
//  Create the main window
  HWND hwnd  =  CreateWindow(g_class_name, g_caption, WS_CAPTION  |  WS_SYSMENU  |  WS_MINIMIZEBOX,
                           
0 0 640 480 , NULL, NULL, inst, NULL);

  
if (hwnd  ==  NULL)
    
return  FALSE;

  ShowWindow(hwnd, cmd_show);
  UpdateWindow(hwnd);

  
//  Call init function and enter message pump
   if (do_init(hwnd)) 
  {
    MSG msg;    
    ZeroMemory(
& msg,  sizeof (MSG));

    
//  Start message pump, waiting for user to exit
     while (msg.message  !=  WM_QUIT) 
    {
      
if (PeekMessage( & msg, NULL,  0 0 , PM_REMOVE)) 
      {
        TranslateMessage(
& msg);
        DispatchMessage(
& msg);
      }
      
      do_frame();    
//  Render a single frame
    }
  }
  
  do_shutdown();
 
  UnregisterClass(g_class_name, inst);
  CoUninitialize();

  
return   0 ;
}

LRESULT FAR PASCAL window_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  
//  Only handle window destruction messages
   switch (msg) 
  {
    
case  WM_DESTROY:
      PostQuitMessage(
0 );
      
break ;

    
case  WM_KEYDOWN:
        
if (wParam  ==  VK_ESCAPE)
            DestroyWindow(hwnd);

        
break ;
  }

  
return  DefWindowProc(hwnd, msg, wParam, lParam);
}

bool  do_init(HWND hwnd)
{
    init_d3d(
& g_d3d,  & g_device, hwnd,  false false );

    
if (FAILED(load_mesh( & g_robot_mesh_container, g_device,  " ..\\Data\\robot.x " " ..\\Data\\ " 0 0 )))
        
return  FALSE;

    
//  setup a directional light

    D3DLIGHT9 light;
    ZeroMemory(
& light,  sizeof (D3DLIGHT9));

    light.Type 
=  D3DLIGHT_DIRECTIONAL;
    light.Diffuse.r 
=  light.Diffuse.g  =  light.Diffuse.b  =  light.Diffuse.a  =   1.0f ;
    light.Direction 
=  D3DXVECTOR3( 0.0f - 0.5f 0.5f );

    g_device
-> SetLight( 0 & light);
    g_device
-> LightEnable( 0 , TRUE);

    
return   true ;
}

void  do_shutdown()
{
    
//  free mesh data
    delete g_robot_mesh_container;
    g_robot_mesh_container 
=  NULL;

    
//  release D3D objects
    release_com(g_device);
    release_com(g_d3d);
}

void  do_frame()
{
    
static  DWORD start_time  =  timeGetTime();

    DWORD elapsed_time 
=  timeGetTime()  -  start_time;

    
//  bounds the time to the animation time, important!!
    elapsed_time  %=  (g_key_frames[ 3 ].time  +   1 );

    
//  dertermin which keyframe to use

    DWORD key_frame 
=   0 ;

    
for (DWORD i  =   0 ; i  <   4 ; i ++ )
    {
        
//  if time is greater or equal to a key-frame's time then update the keyframe to use
         if (elapsed_time  >=  g_key_frames[i].time)
            key_frame 
=  i;
    }

    
//  get second key frame
    DWORD key_frame_2  =  (key_frame  ==   3 ?  key_frame : (key_frame  +   1 );

    
//  Calculate the difference in time between keyframes and calculate a scalar value to use 
    
//  for adjusting the transformations.
    DWORD time_diff  =  g_key_frames[key_frame_2].time  -  g_key_frames[key_frame].time;

    
if (time_diff  ==   0 )
        time_diff 
=   1 ;

    
float  scalar  =  ( float )(elapsed_time  -  g_key_frames[key_frame].time)  /  time_diff;

    
//  calculate the difference in transformations
    D3DXMATRIX mat  =  D3DXMATRIX(g_key_frames[key_frame_2].mat_trans)  -  D3DXMATRIX(g_key_frames[key_frame].mat_trans);

    mat 
*=  scalar;     //  scale the difference

    
//  add scaled transformation matrix back to 1st key frame matrix
    mat  +=  D3DXMATRIX(g_key_frames[key_frame].mat_trans);

    g_device
-> SetTransform(D3DTS_WORLD,  & mat);

    
//  set a view transformation matrix

    D3DXMATRIX  mat_view;
    D3DXVECTOR3 eye(
25.0f 0.0f - 80.0f );
    D3DXVECTOR3 at(
25.0f 0.0f 0.0f );
    D3DXVECTOR3 up(
0.0f 1.0f 0.0f );

    D3DXMatrixLookAtLH(
& mat_view,  & eye,  & at,  & up);
    g_device
-> SetTransform(D3DTS_VIEW,  & mat_view);

    
//  clear the device and start drawing the scene

    g_device
-> Clear( 0 , NULL, D3DCLEAR_TARGET  |  D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA( 0 0 0 255 ),  1.0f 0 );

    g_device
-> BeginScene();

    g_device
-> SetRenderState(D3DRS_LIGHTING, TRUE);
    draw_mesh(g_robot_mesh_container);
    g_device
-> SetRenderState(D3DRS_LIGHTING, FALSE);

    g_device
-> EndScene();

    g_device
-> Present(NULL, NULL, NULL, NULL);
}


Runtime Snap:

Timing in Animation and Movement(2)_第2张图片

As you can see, using time−based animation is pretty simple. Even if you don't use key frames in your animation, you can still rely on these methods of using time in your own code. Now that you've seen how easy it is to use time−based animation, take a look at how easy it is to use time−based movement.

 

download source file

 

你可能感兴趣的:(Timing in Animation and Movement(2))