OpenGL:Tutorials:Tutorial Framework:MD2Animation:SourceCode

From GPWiki

Files:GUITutorial_warn.gif The Game Programming Wiki has moved! Files:GUITutorial_warn.gif

The wiki is now hosted by GameDev.NET at wiki.gamedev.net. All gpwiki.org content has been moved to the new server.

However, the GPWiki forums are still active! Come say hello.

This is the source for the OpenGL Framework MD2 Demo. See OpenGL:Tutorials:Tutorial_Framework for further information.

Render.cpp

#include "Framework.h"
#include <iostream>
#include <math.h>
#include <time.h>
#include "tga.h"
#include "MD2Loader.h"
 
using namespace std;
 
#define FRAMEDELAY 50
#define TEXTURECOUNT 32
 
// Function declarations
bool LoadTexture(char *TexName, GLuint TexHandle);
 
// Here we go!
void Render(void)
 {
 
  MD2Obj Obj; // Our object class
  GLuint Texture[TEXTURECOUNT]; // Texture store 
 
  float ViewRotate=0.0f; // A few vars to handle view rotation, animation and time base values
  long Time1,Time2,Ticks,NextFrame;
  int Frames,CurFrame=0;
 
  char Text[256]; // General purpose string
 
  GLfloat Ambient[]  = { 0.1f,  0.1f,  0.1f, 1.0f};  // Ambient light value
  GLfloat Diffuse[]  = { 1.0f,  1.0f,  1.0f, 1.0f};  // Diffuse light value
  GLfloat Position[] = {10.0f, 60.0f, 10.0f, 1.0f};  // Light position
 
  // Allocate all textures in one go
  glGenTextures(32,Texture);
 
  // Load our Object
   if(Obj.Load("..\\Obj\\WalkMech.md2"))
    {
     RunLevel=0;
     cout<<"Unable to load Object!\n";
     return;
    }
 
 
  // Find out how many frames we have
  Frames=Obj.GetFrameCount();
 
 
  // Load a texture for our object
  strcpy(Text,"..\\Obj\\");
  strcat(Text,Obj.GetTexName());
  
   if(LoadTexture(Text,Texture[0]))
    Obj.SetTexture(Texture[0]);
    
 
  // Background color
  glClearColor(0.0f,0.0f,0.0f,1.0f);
 
 
  // Setup our screen
  glMatrixMode(GL_PROJECTION);
  glViewport(0,0,800,600);
  glLoadIdentity();
  glFrustum(-.5f,.5f,-.5f*(600.0f/800.0f),.5f*(600.0f/800.0f),1.0f,500.0f);
  glMatrixMode(GL_MODELVIEW);
 
 
  // Enable z-buffer
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_TEXTURE_2D);
 
 
  // Enable Lighting
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glLightfv(GL_LIGHT0, GL_AMBIENT, Ambient); // Set the ambient lighting value for Light0
  glLightfv(GL_LIGHT0, GL_DIFFUSE, Diffuse); // Set the diffuse lighting value for Light0
 
 
  // Set up TBM
  Time1=Time2=clock();
  NextFrame=Time1 + FRAMEDELAY;
 
  
  // Main Loop
   while(RunLevel)
    {
      // Esc quits
      if(Keys[VK_ESCAPE])
       RunLevel=0;
 
 
     // Reset view
     glLoadIdentity(); 
     glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
 
 
     // Get ticks since last frame
     Time2=clock();
     Ticks=Time2-Time1;
     Time1=Time2;
 
 
     // Set up the view 
     glTranslatef(0.0f,-15.0f,-80.0f);
     glRotatef(-60.0f,1.0f,0.0f,0.0f);
 
     // Set Light Position
     glLightfv(GL_LIGHT0,GL_POSITION,Position);  // Set position for the light
 
     // Rotate view
     glRotatef(ViewRotate,0.0f,0.0f,1.0f);
 
 
     // Draw our Object
     Obj.Draw(CurFrame);
 
      // Advance the frame counter
      if(Time1>NextFrame)
       {
        CurFrame++;
        NextFrame=Time1 + FRAMEDELAY;
         
         if(CurFrame>=Frames)
          CurFrame=0;
       }
 
 
     // Show our scene
     FlipBuffers();
 
 
     // Rotate view for next frame
     ViewRotate+=(Ticks/50.0f);
    }
 
 
  // Clean up textures
  glDeleteTextures(TEXTURECOUNT,Texture);
 }
 
 
 
// Load a TGA texture
bool LoadTexture(char *TexName, GLuint TexHandle)
 {
  TGAImg Img;        // Image loader
 
  // Load our Texture
   if(Img.Load(TexName)!=IMG_OK)
    return false;
 
  glBindTexture(GL_TEXTURE_2D,TexHandle); // Set our Tex handle as current
 
  // Create the texture
   if(Img.GetBPP()==24)
    glTexImage2D(GL_TEXTURE_2D,0,3,Img.GetWidth(),Img.GetHeight(),0,GL_RGB,GL_UNSIGNED_BYTE,Img.GetImg());
   else if(Img.GetBPP()==32)
    glTexImage2D(GL_TEXTURE_2D,0,4,Img.GetWidth(),Img.GetHeight(),0,GL_RGBA,GL_UNSIGNED_BYTE,Img.GetImg());
   else
    return false;
 
  // Specify filtering and edge actions
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
 
  return true;
 }

MD2Loader.h

#ifndef _MD2LOADER_H
#define _MD2LOADER_H
 
#include <iostream>
#include <fstream>
#include <time.h>
#include <memory.h>
#include <math.h>
#include "framework.h"
 
#define MD2_OK         0x0
#define MD2_ERR_MEM    0x1
#define MD2_ERR_FILE   0x2
#define MD2_ERR_FORMAT 0x4
 
 
struct MD2Header
 {
  int ID;         // File Type - Normally 'IPD2'
  int Version;    
  int TexWidth;   // Texture width
  int TexHeight;  // Texture height 
  int FrameSize;  // Size for frames in bytes
  int nTextures;  // Number of textures
  int nVertices;  // Number of vertices
  int nTexCoords; // Number of UVs
  int nTriangles; // Number of polys
  int nGLCmd;     // Number of GL Commmands
  int nFrames;    // Number of frames
  int TexOffset;  // Offset to texture name(s)
  int UVOffset;   // Offset to UV data
  int FaceOffset; // Offset to poly data
  int FrameOffset;// Offset to first frame  
  int GLCmdOffset;// Offset to GL Cmds          
  int EOFOffset;  // Size of file
 };
 
 
struct MD2FrameInfo
 {
  float Scale[3];
  float Translate[3];
  char Name[16];
 };
 
struct MD2Face
 {
  short p1,p2,p3;
  short uv1,uv2,uv3;
 };
 
struct MD2Vtx
 {
  unsigned char Vtx[3];
  unsigned char lNorm;
 };
 
struct Mesh_Vtx
 {
  float x,y,z;
 };
 
struct Mesh_UV
 {
  float u,v;
 };
 
struct MD2Frame
 {
  Mesh_Vtx *Vtx;
  Mesh_Vtx *Norm;
 };
 
struct MD2TexCoord
 {
  short u,v;
 };	 
 
class MD2Obj
 {
  public:
   MD2Obj();
   ~MD2Obj();
   int Load(char* filename);
   int GetFrameCount();
   char* GetTexName();
   void SetTexture(GLuint TexNum);
   void Draw(int Frame);
 
  private:
   int nFrames,nTri,nVtx,nUV;
   MD2Face *Face;
   MD2Frame *frame;
   Mesh_UV *UV;
   char TexName[64];
   GLuint TexID;
 
   void CalcNormal(Mesh_Vtx v1,Mesh_Vtx v2,Mesh_Vtx v3,Mesh_Vtx* Result);
 };
 
#endif 

MD2Loader.cpp

#include "MD2Loader.h"
 
MD2Obj::MD2Obj()
 {
  // Terminate Arrays
  Face=NULL;
  frame=NULL;
  UV=NULL;
  TexName[0]=NULL;
 
  TexID=0;
 }
 
 
MD2Obj::~MD2Obj()
 {
   // Free Arrays
   if(Face)
    {
     delete [] Face;
     Face=NULL;
    }
 
   if(frame)
    {
     delete [] frame;
     frame=NULL;
    }
 
   if(UV)
    {
     delete [] UV;
     UV=NULL;
    }
 }
 
 
int MD2Obj::Load(char *filename)
 {
  using namespace std;
 
  ifstream fIn;
  unsigned long fSize;
  unsigned char *data=NULL;
  
  MD2Header Head;
  long FrameLoop,ItemLoop;
  MD2Vtx *vtx;
  MD2TexCoord *MD2_UV;
  MD2FrameInfo FrameInfo;
 
  // Clear any existing data
   if(Face)
    {
     delete [] Face;
     Face=NULL;
    }
 
   if(frame)
    {
     delete [] frame;
     frame=NULL;
    }
 
   if(UV)
    {
     delete [] UV;
     UV=NULL;
    }
 
 
  // Open the specified file
  fIn.open(filename,ios::binary);
    
   if(fIn==NULL)
    return MD2_ERR_FILE;
 
  // Get file size
  fIn.seekg(0,ios_base::end);
  fSize=fIn.tellg();
  fIn.seekg(0,ios_base::beg);
 
  // Allocate some space
  data=new unsigned char[fSize];
 
   if(data==NULL)
    {
     fIn.close();
     return MD2_ERR_MEM;
    }
 
  // Read the file into memory
  fIn.read((char*)data,fSize);
 
  fIn.close();
 
 
  // Get Header data
  memcpy(&Head,data,68);
 
  // Dump info about object
  cout<<"ID - "<<data[0]<<data[1]<<data[2]<<data[3]<<"\n";
  cout<<"Version - "<<Head.Version<<"\n";   
  cout<<"Tex Width - "<<Head.TexWidth<<"\n";
  cout<<"Tex Height - "<<Head.TexHeight<<"\n";
  cout<<"Frame Size - "<<Head.FrameSize<<"\n";
  cout<<"Textures - "<<Head.nTextures<<"\n";
  cout<<"Vertices - "<<Head.nVertices<<"\n";
  cout<<"UVs - "<<Head.nTexCoords<<"\n";
  cout<<"Faces - "<<Head.nTriangles<<"\n";
  cout<<"GL cmds - "<<Head.nGLCmd<<"\n";
  cout<<"Frames - "<<Head.nFrames<<"\n";
  cout<<"Skin Offset - "<<Head.TexOffset<<"\n";
  cout<<"UV Offset - "<<Head.UVOffset<<"\n";
  cout<<"Face Offset - "<<Head.FaceOffset<<"\n";
  cout<<"Frame Offset - "<<Head.FrameOffset<<"\n";
  cout<<"GL Offset - "<<Head.GLCmdOffset<<"\n";
  cout<<"Filesize - "<<Head.EOFOffset<<"\n";
 
 
  // A few checks to ensure this is an MD2 file
   if(Head.ID!=844121161) // chars 'IDP2' as a dword
    return MD2_ERR_FORMAT;
 
   if(Head.Version!=8)
    return MD2_ERR_FORMAT;
 
   if(Head.EOFOffset!=fSize)
    return MD2_ERR_FORMAT;
 
 
  // Grab the info we'll need later
  nFrames=Head.nFrames;
  nTri=Head.nTriangles;
  nVtx=Head.nVertices;
  nUV=Head.nTexCoords;
 
 
  // Allocate arrays
 
  // Frames
  frame=new MD2Frame[nFrames];
 
   if(!frame)
    {
     delete [] data;
     return MD2_ERR_MEM;
    }
 
  // Frame components
   for(FrameLoop=0;FrameLoop!=nFrames;++FrameLoop)
    {
     frame[FrameLoop].Vtx = new Mesh_Vtx[nVtx];
     frame[FrameLoop].Norm= new Mesh_Vtx[nVtx];
 
      if(!frame[FrameLoop].Vtx || !frame[FrameLoop].Norm)
       {
        delete [] data;
        return MD2_ERR_MEM;
       }
    }
 
  // MD2 vtx buffer
  vtx = new MD2Vtx[Head.nVertices];
  
  // Faces
  Face = new MD2Face[Head.nTriangles];
 
  // UVs
  UV = new Mesh_UV[nUV];
 
  // MD2 UV buffer
  MD2_UV = new MD2TexCoord[Head.nTexCoords];
 
 
  // Check we've created the arrays
   if(!vtx || !Face || !UV || !MD2_UV)
    {
     delete [] data;
     return MD2_ERR_MEM;
    }
 
 
  // Extract and convert info from file
  
  // Read first texture name
   if(Head.nTextures>0)
    {
     memcpy(TexName,&data[Head.TexOffset],64);
     cout<<"Texture Name - "<<TexName<<"\n";
    }
 
  // Read face data
  memcpy(Face,&data[Head.FaceOffset],Head.nTriangles*sizeof(MD2Face));
 
  // 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;
    }
 
   // Finished with MD2 style UVs
   delete [] MD2_UV;
 
 
 
  // Load frame vertex info
   for(FrameLoop=0;FrameLoop!=nFrames;++FrameLoop)
    {
     // Get frame conversion data
     memcpy(&FrameInfo,&data[Head.FrameOffset + (Head.FrameSize * FrameLoop)],sizeof(FrameInfo));
 
     // Read MD2 style vertex data
     memcpy(vtx,&data[Head.FrameOffset + (Head.FrameSize * FrameLoop) + sizeof(FrameInfo)],nVtx * sizeof(MD2Vtx));
 
     // 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];
       }
    }
 
  // Finished with vtx and filedata
  delete [] vtx;
  delete [] data;
 
 
  // Calc normals for each frane
   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]);
 
       }
    }
 
  return MD2_OK;
 }
 
 
// A few Get() and Set()s to access the private data members
int MD2Obj::GetFrameCount()
 {
  return nFrames;
 }
 
 
char* MD2Obj::GetTexName()
 {
   if(TexName)
    return TexName;
   else
    return NULL;
 }
 
 
void MD2Obj::SetTexture(GLuint TexNum)
 {
  TexID=TexNum;
 }
 
 
// Draw the specified frame
void MD2Obj::Draw(int Frame)
 {
  int Part;
 
   // Limit frame range
   if(Frame>=nFrames)
    Frame=0;
 
  glBindTexture(GL_TEXTURE_2D,TexID);
 
  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();
 
 }
 
 
void MD2Obj::CalcNormal(Mesh_Vtx v1,Mesh_Vtx v2,Mesh_Vtx v3,Mesh_Vtx* Result)
 {
  double v1x,v1y,v1z,v2x,v2y,v2z;
  double nx,ny,nz;
  double vLen;
 
  // Calculate vectors
  v1x = v1.x - v2.x;
  v1y = v1.y - v2.y;
  v1z = v1.z - v2.z;
 
  v2x = v2.x - v3.x;
  v2y = v2.y - v3.y;
  v2z = v2.z - v3.z;
  
  // Get cross product of vectors
 
  nx = (v1y * v2z) - (v1z * v2y);
  ny = (v1z * v2x) - (v1x * v2z);
  nz = (v1x * v2y) - (v1y * v2x);
 
  // Normalise final vector
  vLen = sqrt( (nx * nx) + (ny * ny) + (nz * nz) );
 
  Result->x = (float) (nx / vLen);
  Result->y = (float) (ny / vLen);
  Result->z = (float) (nz / vLen);
 }