OpenGL:Tutorials:Tutorial Framework:MD2Animation

From GPWiki
Jump to: navigation, search

In this example, we're going to load and animate an MD2 object. The format was developed by ID and used in Quake II. It has some unusual quirks which we'll see as we progress.

NOTE I've tweaked the framework a little. Mainly to separate the TGA loader into a header and an code file. I feel that the framework needs a utility module, so texture and other non-core routines may move into another chunk of code in the future.

MD2Loader.h Declares an MD2 class along with several structures which are discussed on the MD2 format page. Most of the methods in MD2Loader.cpp are self explanatory, however, we will need to take a close look at the Load() function.

Loading the object

OK, So after loading the file into memory and allocating space for the object based on the information that the header provided to us we get down to the nitty-gritty of actually making sense of the data.

The texture names are stored in 64 byte strings at the offset indicated by Head.TexOffset, here we only take the first one:

  // Read first texture name
  if(Head.nTextures>0)
   {
    memcpy(TexName,&data[Head.TexOffset],64);
    cout<<"Texture Name - "<<TexName<<"\n";
   }

The actual loading of the texture is handled outside of the class in this example.


The face data tells us how to connect the vertices, this remains the same throughout the animation and so can be read directly into our object:

 // Read face data
 memcpy(Face,&data[Head.FaceOffset],Head.nTriangles*sizeof(MD2Face));

Now, as mentioned on the MD2 page, the vertex and UV data is encoded to reduce the filesize.
To convert the file data into useful values we declare two temporary arrays to hold the UV and vertex information from the file:

 MD2Vtx *vtx;
 MD2TexCoord *MD2_UV;

These arrays are allocated to the sizes specified in the header:

 // MD2 vtx buffer
 vtx = new MD2Vtx[Head.nVertices];
 
 // UVs
 UV = new Mesh_UV[nUV];

To convert the UV data, we must divide the integer values from the file by the size of the texture map to produce a floating point value in the range 0.0f - 1.0f which we'll store in our object:

 // Read MD2 UV data
 memcpy(MD2_UV,&data[Head.UVOffset],Head.nTexCoords*sizeof(MD2TexCoord));
 
  // Convert into regular UVs
  for(ItemLoop=0;ItemLoop!=nUV;++ItemLoop)
   {
    UV[ItemLoop].u=((float)MD2_UV[ItemLoop].u)/Head.TexWidth;
    UV[ItemLoop].v=((float)MD2_UV[ItemLoop].v)/Head.TexHeight;
   }

Now we have the converted UVs, we can get rid of the original file data:

 // Finished with MD2 style UVs
 delete [] MD2_UV;

Next we'll grab the vertex data. Each frame has a header containing the name of the frame, plus size and position transform. So, for each frame we grab the header:

NOTE You may want to store and analyse the names of the frames in order to use separate animation sequences within the file. That however, is beyond the scope of this article.

  // Load frame vertex info
  for(FrameLoop=0;FrameLoop!=nFrames;++FrameLoop)
   {
    // Get frame conversion data
    memcpy(&FrameInfo,&data[Head.FrameOffset + (Head.FrameSize * FrameLoop)],sizeof(FrameInfo));

Next, we read the encoded vertex data that follows the header into our temporary array:

    // Read MD2 style vertex data
    memcpy(vtx,&data[Head.FrameOffset + (Head.FrameSize * FrameLoop) + sizeof(FrameInfo)],nVtx * sizeof(MD2Vtx));

Finally, we multiply each vertex by the header scale value and apply the translation:

    // Convert vertices
     for(ItemLoop=0;ItemLoop!=nVtx;++ItemLoop)
      {
       frame[FrameLoop].Vtx[ItemLoop].x=(vtx[ItemLoop].Vtx[0] * FrameInfo.Scale[0])+FrameInfo.Translate[0];
       frame[FrameLoop].Vtx[ItemLoop].y=(vtx[ItemLoop].Vtx[1] * FrameInfo.Scale[0])+FrameInfo.Translate[1];   
       frame[FrameLoop].Vtx[ItemLoop].z=(vtx[ItemLoop].Vtx[2] * FrameInfo.Scale[0])+FrameInfo.Translate[2];
      }
    }

Now we have finished loading the object, the converted vertices are stored in our object and we can dispose of the temporary arrays:

 // Finished with vtx and filedata
 delete [] vtx;
 delete [] data;

As the normal data that accompanies the model references a lookup table within the Quake II engine, I simply recalculate the normals using the same technique that was covered in the lighting tutorial.

  // Calc normals for each frame
  for(FrameLoop=0;FrameLoop!=nFrames;FrameLoop++)
   {
     // Calc face normal
     for(ItemLoop=0;ItemLoop!=nTri;ItemLoop++)
      {
       CalcNormal(frame[FrameLoop].Vtx[Face[ItemLoop].p1],
                  frame[FrameLoop].Vtx[Face[ItemLoop].p2],
                  frame[FrameLoop].Vtx[Face[ItemLoop].p3],
                  &frame[FrameLoop].Norm[ItemLoop]);
      }
   }

Now we have our model loaded, converted and ready for action!

Displaying the object

The Draw() function draws our model at the origin. If we want to place the object somewhere in the game world, we can do something like:

  glPushMatrix();
 
  glLoadIdentity();
  glTranslatef(ObjX,ObjY,ObjZ);
  glRotatef(ObjRot,1.0f,0.0f,0.0f);
 
  Obj.Draw(FrameNum);
 
  glPopMatrix();

The Draw() function's Frame parameter is used to select the appropriate set of vertices to render the model. The face array provides the indices to be used within the frame set. In this way the single copy of the UV and Poly data is distorted and animated by the multiple frames of vertex data.

  glBegin(GL_TRIANGLES);
 
   for(Part=0;Part<nTri;++Part)
    {
     glNormal3f(frame[Frame].Norm[Part].x,frame[Frame].Norm[Part].y,frame[Frame].Norm[Part].z);
     glTexCoord2f(UV[Face[Part].uv1].u,UV[Face[Part].uv1].v);
     glVertex3f(frame[Frame].Vtx[Face[Part].p1].x,frame[Frame].Vtx[Face[Part].p1].y,frame[Frame].Vtx[Face[Part].p1].z);
     glTexCoord2f(UV[Face[Part].uv2].u,UV[Face[Part].uv2].v);
     glVertex3f(frame[Frame].Vtx[Face[Part].p2].x,frame[Frame].Vtx[Face[Part].p2].y,frame[Frame].Vtx[Face[Part].p2].z);
     glTexCoord2f(UV[Face[Part].uv3].u,UV[Face[Part].uv3].v);
     glVertex3f(frame[Frame].Vtx[Face[Part].p3].x,frame[Frame].Vtx[Face[Part].p3].y,frame[Frame].Vtx[Face[Part].p3].z);
    }  
 
  glEnd();

This is a little heavy on array access, so an improvement to the above code could compile each frame into a display list at startup to speed up the model rendering process later.

So, if all goes well, we should now be able to display our MD2 model thus:

GLTut7MD2.jpg

Source Code

I've placed the source code here as it's getting a little too big to fit on this page. It should be compiled using the OpenGL Tutorial Framework.

Downloads

GLTut_7-MD2Anim.zip - A zip including Win32 and GLFW source code, images, models and Win32 Binary.