News
DirectX
Links
Contact Me

In Association with Amazon.com
In Association with Amazon.ca

$5 via PayPal


Dancing Square



Dancing Square
In this tutorial we cover simple animation by modifying a vertex, variable step timing so our animation runs at the same speed on different computers and camera rotation.
Here we define the vertices we'll need:
my_vertex g_vertices[] ={

   //First the vertices for our floor, 2 triangles (TRIANGLELIST)

   { -20.0f,  0.0f, 20.0f, 0xFFFF0000},
   {  20.0f,  0.0f, 20.0f, 0xFF00FF00},
   { -20.0f,  0.0f,-20.0f, 0xFF0000FF},

   { -20.0f,  0.0f,-20.0f, 0xFF0000FF},
   {  20.0f,  0.0f, 20.0f, 0xFF00FF00},
   {  20.0f,  0.0f,-20.0f, 0xFFFF00FF},

   //Now our dancing square
   //The vertices are given as a TRIANGLEFAN

   {  0.0f,  0.0f, 0.0f, 0xFF888888}, //Center Point
   { -1.0f,  0.0f, 1.0f, 0xFF884444}, //Back Left
   {  1.0f,  0.0f, 1.0f, 0xFF448844}, //Back Right
   {  1.0f,  0.0f,-1.0f, 0xFF444488}, //Front Right
   { -1.0f,  0.0f,-1.0f, 0xFF888844}, //Front Left
   { -1.0f,  0.0f, 1.0f, 0xFF884444}, //Back Left

};

I added the floor because with just a simple object it wasn't clear whether the square was spinning or if it was the camera. With multiple objects it becomes apparent that it's the camera. The floor gives a frame of reference. The floor is a simple Triangle List, nothing exciting or new there.

The square is defined as a Triangle Fan. The shared point is in the center and the remaing vertices surround it creating a square. The reason I use a Fan here instead of the standard List or Strip is because this gives us a single vertex that we can modify and affect all the triangles in the object. So we can make the center of the square bop up and down just by changing a single vertex.


To keep our square from going to high we define a maximum height for it to achieve:
//This sets the maximum height the center of our square can achieve
#define MAX_SQUARE_HEIGHT  (2.0f)

Here we create 2 global variables to track the positions of our objects:
//This global holds the current height of the center point for our square
float g_y_pos=0.0f;

//This global holds the current angle of rotation for our camera
float g_rot_angle=0.0f;

We add a few items to our WinMain. Only the game loop has changed, here it is:
   while(!g_app_done){
      //Check for window messages
      message_pump();

      //Calculate Timer Info
      calc_timer();

      update_square();

      spin_camera();

      //Draw our incredibly cool graphics
      render();
   }

A few changes are required in render(), here is the relevant piece:
   //Notify the device that we're ready to render
   if(SUCCEEDED(g_d3d_device->BeginScene())){

      g_d3d_device->SetVertexShader(D3D8T_CUSTOMVERTEX);

      g_d3d_device->SetStreamSource(0,g_vb,sizeof(my_vertex));

      //We're not modify the world matrix, all of our objects are already in
      //World Space.  If we wanted to we could set it once in init_scene()
      //and never change it.
      D3DXMatrixIdentity(&matWorld);

      //Set our World Matrix
      g_d3d_device->SetTransform(D3DTS_WORLD,&matWorld );

      //Draw our floor (2 triangle TRIANGLELIST)
      g_d3d_device->DrawPrimitive(D3DPT_TRIANGLELIST,0,2);

      //The first 6 vertices are our square. After that are the
      //vertices for a 4-triangle TRIANGLEFAN
      g_d3d_device->DrawPrimitive(D3DPT_TRIANGLEFAN,6,4);


      //Notify the device that we're finished rendering for this frame
      g_d3d_device->EndScene();
   }
The only interesting bit here is that our world matrix is set to the Identity Matrix. This means no tranformation occurs when model space is mapped to World Space because the models are defined in World Space already. As mentioned above, you could move the code to set the World Matrix into init_scene() since it never changes. I didn't bother, but feel free to experiment.

Here we start getting into the fun stuff. Our Eye Vector specifies the location in World Space of our eye (or camera if you prefer). The idea of rotating a vector shouldn't be new, after all the coordinates of a vertex is a vector. Note:The angle of rotation is taken from the global variable g_rot_angle. This variable is updated by calc_timer() which we'll see shortly.
After setting up our rotation matrix, we use D3DXVec3TransformCoord() to apply it to our Eye Vector. The next 2 steps should be familiar to you, we calculate our new view matrix and set it.

// Function: spin_camera
// Whazzit: Rotates our eye vector and rebuilds the View Matrix.
//          Rebuilding our View Matrix every frame is not the most
//          efficient thing to do since a lot of things need to be
//          recalculated by D3D every time we do it.  It's good enough
//          for now though.
void spin_camera(void){
D3DXMATRIX rotation_matrix,view_matrix;
D3DXVECTOR3 eye_vector(0.0f,4.0f,-6.0f);


   //Build a Y Rotation matrix to apply to our eye vector
   D3DXMatrixRotationY(&rotation_matrix,g_rot_angle);

   //This takes our eye vector and Transforms it (rotates in this case).
   D3DXVec3TransformCoord(&eye_vector,&eye_vector,&rotation_matrix); 


   //Rebuild our View Matrix
   D3DXMatrixLookAtLH(&view_matrix,&eye_vector,
                                   &D3DXVECTOR3( 0.0f, 0.0f, 0.0f ),
                                   &D3DXVECTOR3( 0.0f, 1.0f, 0.0f ));

   //Set our new view matrix
   g_d3d_device->SetTransform(D3DTS_VIEW,&view_matrix);

}

This is where our animation happens. 'g_vb' is our vertex buffer which holds the floor & our square. For simplicity we lock the whole Vertex buffer even though we're only modifying a single vertex. The first 6 vertices are our floor, so we skip those and modify the first (center) point of our square. The variable b_y_pos is set it calc_timer.
// Function: update_square
// Whazzit: Locks the vertex buffer and changes the y-coordinate of the center point
//          in our 'square' then unlocks it.
void update_square(void){
HRESULT hr;
my_vertex *vb_vertices;

   hr=g_vb->Lock(0, //Offset, we want to start at the beginning
                 0, //SizeToLock, 0 means lock the whole thing
                 (unsigned char **)&vb_vertices, //If successful, this will point to the data in the VB
                 0);  //Flags, nothing special
   if(FAILED(hr)){
      FatalError("Error Locking buffer");
   }
   
   //The first 6 vertices are our floor, the next one is the center point of our square
   vb_vertices[6].y=g_y_pos;

   //Unlock so Direct3D knows we're done and can do any behind-the-scenes magic required
   g_vb->Unlock();

}

This is where we do our timing. Variable step timing is simple. All you have to do is know how much time has passed since the last frame and scale all of your movement by that. If an object is moving at 0.002 units per tick and 10 ticks have passed, you move the object 0.02 units.
// Function: calc_timer
// Whazzit: Here is where we calculate our variable step timing.  It's variable step because
//          each frame we check how much time has passed since the previous frame and scale
//          our movement accordingly.
void calc_timer(void){
static DWORD last_time=0;
static float direction=1.0f;
DWORD now,delta;
float move;

   //What time is it?
   now=GetTickCount();

   //delta is now the difference (in ticks) between the last frame and now
   delta=now-last_time;

   last_time=now;

   //direction is always equal to 1 or -1 and simply keeps us alternating between
   //moving up and down.
   move=(delta/500.0f) * direction;

   //Add the movement to our global position.  Since move is calculated based on the amount
   //of time that has passed since our last frame it will run at the same speed on just
   //about any computer
   g_y_pos+=move;

   //Similarly, we use the delta to calculate our rotation speed
   g_rot_angle+=delta/1000.0f;

   //If our movement would make us go higher than MAX_SQUARE_HEIGHT set it that
   //high and change direction.
   //Similarly, if we go below 0, stop at 0 and change direction.
   if(g_y_pos > MAX_SQUARE_HEIGHT){
      g_y_pos=MAX_SQUARE_HEIGHT;
      direction=-1.0f;
   }else if(g_y_pos < 0.0f){
      g_y_pos=0.0f;
      direction=1.0f;
   }
}