www.xbdev.net
xbdev - software development
Monday October 21, 2024
Home | Contact | Support | 3D File Formats The bits and bytes... | 3D File Formats The bits and bytes...

MD2 Class - ~Quake 2 File Format~
Author bkenwright@xbdev.net

So to keep with things I decided to make a simple class called CLoadMd2 which can be used to import quake 2 files into your game or application.  The CLoadMd2 class won't contain any DirectX or OpenGl commands etc, it will simply load all the data from the 3D file into structures which can then be used by yourself externally to render your shape to the screen... I'll do to different sections to this tutorial.  First I'll write a simple static MD2 loader class which will simply display a simple MD2 3D Model, the first frame if there are more than one... Part II will be a dynamic animated class...OOOooo... yeah... it won't be the most efficient version... but it it will allow you to see the animated model in a whole new light.

If you tune in to the next tutorial later on I'll be developing a CLoadXMd2 class which will keep the class "encapsulated" as they say and will contain all the directX3D code etc, so you can use the class without having to do any work ;)  But that class is for the final game you'll be designing... this tutorial is to show you the inner workings of the md2 file format.

PART -I- ReadMd2File(..)

Whole Code -)(-

Well its may look pretty boring, but I only chose to extract and display the actual model vertices and faces for the first part... its as easy as cake to add the texturing ... which I do in the later on when I present the CLoaderMd2 class...  But the first section shows you a md2 loader function... I think that sometimes functions can be a lot simpler to follow... especially when learning something new... but as you progress classes allow you to expand on your understanding and work with much larger and more complex code.

(download the below code)

Well its in three parts... but you'll thank me later on... the only code you should really be interested in ... is how I use md2.cpp... as this first version uses functions... NO CLASSES... I'll get to that in a bit, this bit of code starts at main.cpp, it creates a window, then in md2.cpp we load in our md2 data from the file ... I've loaded it into a common array structure which can be passed to dxdraw.cpp which is simply a directX3D part of the code... all the directX code is in there!  It will render our shape to the screen.

main.cpp

md2.cpp

dxdraw.cpp

I'm going to include the full source code, which can also be downloaded... but sometimes I think its better if you can see it all on the screen... a lot of it is just either windows init code or directX initilisatino code etc... so it makes it look larger than it is.

/***************************************************************************/

/*                                                                         */

/* File: main.cpp                                                          */

/* Author: bkenwright@xbdev.net                                       */

/* Date: 10-11-2002                                                        */

/*                                                                         */

/***************************************************************************/

 

#pragma comment(lib, "D3d8.lib") //directX 8

#pragma comment(lib, "D3dx8.lib")

#include <d3dx8.h>

/***************************************************************************/

/*                                                                         */

/* These three structures will hold all our data from the .md2 model...    */

/* its only responsible for extracting it.. its not got the texture data   */

/* I'll add that in later... this shows a principle where you could create */

/* a set of structures where you read in the data into them.               */

/*                                                                         */

/***************************************************************************/

 

struct stArrayVerts

{

      float x, y, z;

};

struct stArrayFaces

{

      short vertexIndex[3];

};

 

struct stModelData

{

      int numVerts;

      int numFaces;

      stArrayVerts* pV;

      stArrayFaces* pF;

};

stModelData m; // Our global 3D model data will be in "m".

 

WNDCLASS a; HWND hwnd; MSG c;

long _stdcall zzz (HWND, UINT, WPARAM, LPARAM);

 

 

#include "md2.cpp"      // All the md2 loading stuff is in here!

#include "dxdraw.cpp"   // All the directX3D draw code is in here.!

 

 

void gameloop()

{

      Render(&m);  // This is called repeatedly to render our model data to screen.

}

 

 

int _stdcall WinMain(HINSTANCE i, HINSTANCE j, char *k, int l)

{

      a.lpszClassName="a1";

      a.hInstance = i;

      a.lpfnWndProc = zzz;

      a.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);

      RegisterClass(&a);

      hwnd=CreateWindow("a1", "time client", WS_OVERLAPPEDWINDOW, 30,30,300,300,NULL,NULL,i,NULL);

 

      init(hwnd);                               // -1- Init DirectX.

      ShowWindow(hwnd,1);                       // -2- Create our window.

      ReadMd2File(&m, "pac3D.md2"); // -3- Load our md2 file.

      while(1)

      {

           

            if (PeekMessage(&c, NULL, 0, 0, PM_NOREMOVE))

            {

                  if(!GetMessage(&c, 0,0,0))

                        break;

                  DispatchMessage(&c);

            }

            else

                  gameloop();            // -4- Display our md2 file data we loaded earlier.

      }

      return 1;

}

 

long _stdcall zzz (HWND w, UINT x, WPARAM y, LPARAM z)

{

      if (x == WM_DESTROY)

      {

            de_init();                 // -5- Destroy DirectX stuff.

            delete[] m.pV;                   // -6- Tidy up any memory allocation for our 3D model.

            delete[] m.pF;

            PostQuitMessage(0);

      }

      return DefWindowProc(w,x,y,z);

}

Next the code which actually loads our quake2 file in... the code your probably most interested in (md2.cpp).

/***************************************************************************/

/*                                                                         */

/* File: md2.cpp                                                           */

/* Author: bkenwright@xbdev.net                                       */

/* Date: 10-11-2002                                                        */

/*                                                                         */

/***************************************************************************/

 

struct stMd2Header

{

      int magic;              // The magic number used to identify the file.

      int version;            // The file version number (must be 8).

      int skinWidth;          // The width in pixels of our image.

      int skinHeight;         // The height in pixels of our image.

      int frameSize;          // The size in bytes the frames are.

      int numSkins;           // The number of skins associated with the model.

      int numVertices;  // The number of vertices.

      int numTexCoords; // The number of texture coordinates.

      int numTriangles; // The number of faces (polygons).

      int numGlCommands;      // The number of gl commands.

      int numFrames;          // The number of animated frames.

      int offsetSkins;  // The offset in the file for the skin data.

      int offsetTexCoords;// The offset in the file for the texture data.

      int offsetTriangles;// The offset in the file for the face data.

      int offsetFrames; // The offset in the file for the frames data.

      int offsetGlCommands;// The offset in the file for the gl commands data.

      int offsetEnd;          // The end of the file offset.

};

stMd2Header Md2Header;

 

// Some structures to hold or read in data in.

struct stSkins

{

      char skinName[64];

};

 

struct stTexCoords

{

      short u, v;

};

 

struct stVertices

{

      float vertex[3];

      float normal[3];

};

 

struct stTriangles

{

      short vertexIndex[3];

      short texIndex[3];

};

 

struct stVerts

{

      byte vertex[3]; // an index reference into the location of our vertexs

      byte lightNormalIndex; // in index into which tex coords to use.

};

struct stFrames

{

      float scale[3];

      float translate[3];

      char strFrameName[16];

      stVertices* pVerts;

};

 

 

void ReadMd2File(stModelData* m, char* szFileName)

{

      FILE *f = fopen(szFileName, "rb");

      fread(&Md2Header, 1, sizeof(Md2Header), f);

 

      // Allocate memory for our data so we can read it in.

      stSkins* pSkins         = new stSkins[ Md2Header.numSkins ];

      stTexCoords* pTexCoords = new stTexCoords[ Md2Header.numTexCoords ];

      stTriangles* pTriangles = new stTriangles[ Md2Header.numTriangles ];

      stFrames* pFrames = new stFrames[ Md2Header.numFrames ];

 

      // -1- Seek to the start of our skins name data and read it in.

      fseek(f, Md2Header.offsetSkins, SEEK_SET);

      fread(pSkins, sizeof(stSkins), Md2Header.numSkins, f);

 

      // -2- Seek to the start of our Texture Coord data and read it in.

      fseek(f, Md2Header.offsetTexCoords, SEEK_SET);

      fread(pTexCoords, sizeof(stTexCoords), Md2Header.numTexCoords, f);

 

      // -3- Seek to the start of the Triangle(e.g. Faces) data and read that in.

      fseek(f, Md2Header.offsetTriangles, SEEK_SET);

      fread(pTriangles, sizeof(stTriangles), Md2Header.numTriangles, f);

 

      // -4- Finally lets read in "one" of the frames, the first one.!

     

      struct stAliasFrame

      {

            float scale[3];

            float translate[3];

            char name[16];

            stVerts aliasVerts[1];

      };

     

      unsigned char largebuffer[10000];

      stAliasFrame* pTempFrame = (stAliasFrame*) largebuffer;

 

      fseek(f, Md2Header.offsetFrames, SEEK_SET);

      fread(pTempFrame, 1, Md2Header.frameSize, f); // We have read in all the frame data here (into a temporyary!!..eeEKK)..

 

      pFrames[0].pVerts = new stVertices[ Md2Header.numVertices ];

     

      // CONVERSION!  A few things before we can use our read in values,

      // for some reason the Z and Y need to be swapped, as Z is facing up

      // and Y is facing into the screen.

      // Also our texture coordinates values are between 0 and 256, we just

      // divide them all by 256 which makes them between 0 and 1.

 

      // Swap Z<->Y

     

      for(int i=0; i< Md2Header.numVertices; i++)

      {

            pFrames[0].pVerts[i].vertex[0] = pTempFrame->aliasVerts[i].vertex[0] * pTempFrame->scale[0] + pTempFrame->translate[0]; // x

            pFrames[0].pVerts[i].vertex[1] = pTempFrame->aliasVerts[i].vertex[2] * pTempFrame->scale[2] + pTempFrame->translate[2]; // y

            pFrames[0].pVerts[i].vertex[2] = -1 * (pTempFrame->aliasVerts[i].vertex[1] * pTempFrame->scale[1] + pTempFrame->translate[1]); // z

      }

 

      // Scale Textures.

      for (int j=0; j< Md2Header.numTexCoords; j++)

      {

            pTexCoords[j].u = pTexCoords[j].u / float(Md2Header.skinWidth);

            pTexCoords[j].v = pTexCoords[j].v / float(Md2Header.skinHeight);

      }

 

      // Now --Here-- is where we have all our data...if we wanted to could

      // convert of draw it here or something... but since we only wanted

      // to see how its extracted, we just clean up after ourselfs and exit.

 

  

      m->numVerts = Md2Header.numVertices;

      m->pV = new stArrayVerts[ m->numVerts ];

 

      for(int ii=0; ii< m->numVerts; ii++)

      {

            m->pV[ii].x = pFrames[0].pVerts[ii].vertex[0];

            m->pV[ii].y = pFrames[0].pVerts[ii].vertex[1];

            m->pV[ii].z = pFrames[0].pVerts[ii].vertex[2];

      }

 

     

      m->numFaces = Md2Header.numTriangles;

      m->pF = new stArrayFaces[ m->numFaces ];

      for(int jj=0; jj< m->numFaces; jj++)

      {

            m->pF[jj].vertexIndex[0] = pTriangles[jj].vertexIndex[0];

            m->pF[jj].vertexIndex[1] = pTriangles[jj].vertexIndex[1];

            m->pF[jj].vertexIndex[2] = pTriangles[jj].vertexIndex[2];

      }

 

      // Tidy up before exiting.

      delete[] pFrames[0].pVerts;

      delete[] pSkins;

      delete[] pTexCoords;

      delete[] pTriangles;

      delete[] pFrames;

      fclose(f);

}

And last but not least, the code which uses directX to display our 3D model data which we have extracted from the .md2 file to screen..

***************************************************************************/

/*                                                                         */

/* File: dxdraw.cpp                                                        */

/* Author: bkenwright@xbdev.net                                       */

/* Date: 10-11-2002                                                        */

/*                                                                         */

/***************************************************************************/

 

LPDIRECT3D8 g_pD3D = NULL;

LPDIRECT3DDEVICE8 g_pD3DDevice = NULL;

 

/***************************************************************************/

/*                                                                         */

/* init, de_init, and set_camera are simple directX setup functions.       */

/*                                                                         */

/***************************************************************************/

void init(HWND hWnd)

{

    //First of all, create the main D3D object. If it is created successfully we

    //should get a pointer to an IDirect3D8 interface.

    g_pD3D = Direct3DCreate8(D3D_SDK_VERSION);

    //Get the current display mode

    D3DDISPLAYMODE d3ddm;

    g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);

    //Create a structure to hold the settings for our device

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));

    //Fill the structure.

    //We want our program to be windowed, and set the back buffer to a format

    //that matches our current display mode

    d3dpp.Windowed = TRUE;

    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;

    d3dpp.BackBufferFormat = d3ddm.Format;

    //For depth buffering (e.g.) the z-buffer

    d3dpp.BackBufferCount=1;

    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

    d3dpp.EnableAutoDepthStencil = TRUE;

    //Create a Direct3D device

    g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pD3DDevice);

    //Turn off lighting becuase we are specifying that our vertices have colour

    g_pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

    //Turn on z-buffering

    g_pD3DDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);

 

      //Turn on back face culling. This is becuase we want to hide the back of our polygons

      g_pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);

}

 

void de_init()

{

      g_pD3DDevice->Release();

    g_pD3DDevice = NULL;

    g_pD3D->Release();

    g_pD3D = NULL;

}

void set_camera()

{

    // [1] D3DTS_VIEW

    D3DXMATRIX v;

    g_pD3DDevice->SetTransform(D3DTS_VIEW, D3DXMatrixLookAtLH(&v, &D3DXVECTOR3(0,0,-100),

                                                                  &D3DXVECTOR3(0,0,0),

                                                                  &D3DXVECTOR3(0,1,0))); 

    // [2] D3DTS_PROJECTION

    D3DXMATRIX p;

    g_pD3DDevice->SetTransform( D3DTS_PROJECTION, D3DXMatrixPerspectiveFovLH( &p,  D3DX_PI/4,  1.0f,

                                                                                                 0.0f,200.0f));

};

 

struct my_vertex

{

      FLOAT x, y, z;  // D3DFVF_XYZ

      DWORD colour;   // D3DFVF_DIFFUSE

};

 

/***************************************************************************/

/*                                                                         */

/* This function is called repeatedly from main.cpp in windows code, as    */

/* it allows us to constandly display the model when windows is idle.      */

/*                                                                         */

/***************************************************************************/

 

void Render(stModelData* m)

{

      if(!g_pD3DDevice)return;

 

      // All we do here is set up where where looking at...eg. the camera pos.

      set_camera();

 

      // Set a world matrix so our md2 model rotates around.

      static float angle = 0.0f;

    angle += 0.0001f;

      //~~~*~~~~ Create a matrix

      D3DXMATRIX mx;

    //~~~*~~~~ Do somthing to our empty matrix.

    D3DXMatrixRotationY(&mx, angle ); // angle in radians...eg. 1 degree = PI/180

    //~~~*~~~~ Use the matrix! No use having a matrix if we don't use it!

    g_pD3DDevice->SetTransform(D3DTS_WORLD, &mx);

 

 

      // Create our directX buffer which will be used to hold or model data.

      UINT my_vertex_description = (D3DFVF_XYZ | D3DFVF_DIFFUSE);

      IDirect3DVertexBuffer8 * DX_vb;

      g_pD3DDevice->CreateVertexBuffer( m->numFaces * 3 * sizeof(my_vertex), 0, my_vertex_description, D3DPOOL_MANAGED, &DX_vb );

 

      // Copy our array which is in computer memory over to the directX memory.. using that pointer we

    // just created etc.

    unsigned char *temp_pointer_vb;

    my_vertex* tt = new my_vertex[m->numFaces * 3];

 

      for(int i=0; i< (m->numFaces); i++)

      {

            int index = m->pF[i].vertexIndex[0];

            tt[i*3].x = m->pV[ index ].x;

            tt[i*3].y = m->pV[ index ].y;

            tt[i*3].z = m->pV[ index ].z;

            tt[i*3].colour = 0xff00ffff;

 

            index = m->pF[i].vertexIndex[1];

            tt[i*3+1].x = m->pV[ index ].x;

            tt[i*3+1].y = m->pV[ index ].y;

            tt[i*3+1].z = m->pV[ index ].z;

            tt[i*3+1].colour = 0xff00ffff;

           

            index = m->pF[i].vertexIndex[2];

            tt[i*3+2].x = m->pV[ index ].x;

            tt[i*3+2].y = m->pV[ index ].y;

            tt[i*3+2].z = m->pV[ index ].z;

            tt[i*3+2].colour = 0xff00ffff;

      }

     

    DX_vb->Lock(0,0, &temp_pointer_vb, 0);

      memcpy(temp_pointer_vb, tt, m->numFaces * 3 * sizeof(my_vertex) );

    DX_vb->Unlock();

      delete[] tt;

    // Okay at this point... our graphics card has our vertices stored in it... we've just copied

    // them over :) Some stuff to setup or graphics card!

    //Turn off lighting becuase we are specifying that our vertices have colour

    g_pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

    g_pD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);

      g_pD3DDevice->SetTextureStageState(0,D3DTSS_COLORARG1, D3DTA_DIFFUSE);

 

      // Clear the back buffer to a blue color

      g_pD3DDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );

      // Draw our triangle.

      g_pD3DDevice->SetStreamSource(0, DX_vb, sizeof(my_vertex));

      g_pD3DDevice->SetVertexShader(my_vertex_description);

      g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, m->numFaces);

      // After rendering the scene we display it.

      g_pD3DDevice->Present( NULL, NULL, NULL, NULL );

     

      // As where calling the functino over and over again.. we'd be constantly creating new memory

      // without releasing the old if not for this line!

      DX_vb->Release();

}

There you go!  Wasn't that easy?  Eeeek.  Well if you've looked over the code its not as bad as you might think... if your new to directX, well the dxdraw.cpp file is full of all the directX code, and if your unsure about windows programming, well all the windows code is in main.cpp.

How it all comes together is like this - stModelData is a global structure which which I create an instance of called m, (not the most creative name ;)  And then after setting up the windows stuff, I pass a pointer of this member to the function lReadMd2File(..) in md2.cpp, which will load our md2 file in, and fill our structure we passed it will the data we need.  Finally... we have our data... so to do something with it, I created the dxdraw.cpp file which if you pass the a pointer to our variable "m" which holds our 3D model data it will render it to the screen!

Its not the most tidies and efficient of codes... but you should be able to get the jist of the .md2 file format.  Again if you know how it works you can design your own eventually.

PART -II- CLoadMD2.

So what do you expect now... yup you guess write, instead of messing around with functions, I decided to put together a fully functional Class called CLoadMD2 which will load all the data in and then you can pass the to the dxdraw.cpp file and it will render the data inside the class... its not how a class should really be used... as the principles of encapsulation etc... as we are actually accessing the data inside the CLoadMD2 class... I think its tidier.... all the md2 loading code and data structures are all inside the md2.cpp file and you only need to use an instance of the class.

Yup you get to see the actual wonderful model that is inside the .md2 file in all its glory... which includes the added texture!.

(download full code)

As before there are 3 main file...

main.cpp md2.cpp dxdraw.cpp

Well a lot of its the same as above, but still I thought I'd include it... you can never know come code to well...

/***************************************************************************/

/*                                                                         */

/* File: main.cpp                                                          */

/* Author: bkenwright@xbdev.net                                       */

/* Date: 10-11-2002                                                        */

/*                                                                         */

/***************************************************************************/

 

#include <windows.h>

#include <stdio.h>

 

WNDCLASS a; HWND hwnd; MSG c;

long _stdcall zzz (HWND, UINT, WPARAM, LPARAM);

 

 

#include "md2.cpp"

#include "dxdraw.cpp"

 

CLoadMD2 g_md2;  // Important piece of code here! ->Using our CLoadMD2 Class<--

 

void gameloop()

{

      Render(&g_md2);

}

 

 

int _stdcall WinMain(HINSTANCE i, HINSTANCE j, char *k, int l)

{

      a.lpszClassName="a1";

      a.hInstance = i;

      a.lpfnWndProc = zzz;

      a.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);

      RegisterClass(&a);

      hwnd=CreateWindow("a1", "time client", WS_OVERLAPPEDWINDOW, 30,30,300,300,NULL,NULL,i,NULL);

 

      init(hwnd);                               // -1- Init DirectX3D.

      ShowWindow(hwnd,1);                       // -2- Show our window.

      g_md2.ImportMD2("pac3D.md2");   // -3- Load our data into our CLoadMD2 class instance.

      while(1)

      {

           

            if (PeekMessage(&c, NULL, 0, 0, PM_NOREMOVE))

            {

                  if(!GetMessage(&c, 0,0,0))

                        break;

                  DispatchMessage(&c);

            }

            else

                  gameloop();                   // -4- Display our data (e.g. calls Render in dxdraw.cpp).

      }

 

      return 1;

}

 

long _stdcall zzz (HWND w, UINT x, WPARAM y, LPARAM z)

{

      if (x == WM_DESTROY)

      {

            de_init();                          // -5- Destroy DirectX code.

            g_md2.Release();              // -6- Destroy our CLoadMD2 instance.

            PostQuitMessage(0);

      }

      return DefWindowProc(w,x,y,z);

}

Now for our CLoadMD2 class which is in file md2.cpp...

/***************************************************************************/

/*                                                                         */

/* File: md2.cpp                                                           */

/* Author: bkenwright@xbdev.net                                       */

/* Date: 10-11-2002                                                        */

/*                                                                         */

/***************************************************************************/

 

struct stMd2Header

{

      int magic;              // The magic number used to identify the file.

      int version;            // The file version number (must be 8).

      int skinWidth;          // The width in pixels of our image.

      int skinHeight;         // The height in pixels of our image.

      int frameSize;          // The size in bytes the frames are.

      int numSkins;           // The number of skins associated with the model.

      int numVertices;  // The number of vertices.

      int numTexCoords; // The number of texture coordinates.

      int numTriangles; // The number of faces (polygons).

      int numGlCommands;      // The number of gl commands.

      int numFrames;          // The number of animated frames.

      int offsetSkins;  // The offset in the file for the skin data.

      int offsetTexCoords;// The offset in the file for the texture data.

      int offsetTriangles;// The offset in the file for the face data.

      int offsetFrames; // The offset in the file for the frames data.

      int offsetGlCommands;// The offset in the file for the gl commands data.

      int offsetEnd;          // The end of the file offset.

};

 

// Some structures to hold or read in data in.

struct stMd2Skins

{

      char skinName[64];

};

 

struct stMd2TexCoords

{

      short u, v;

};

 

struct stMd2Triangles

{

      short vertexIndex[3];

      short texIndex[3];

};

 

struct stMd2Vertices

{

      float vertex[3];

      float normal[3];

};

struct stMd2Frames

{

      stMd2Vertices* pFinalVerts;

};

 

/***************************************************************************/

/*                                                                         */

/* The CLoadMD2 class, yup its name speaks for itself, it loads the 3D     */

/* model data from the .md2 file and then we can access its public data    */

/* variables to use the data.                                              */

/*                                                                         */

/***************************************************************************/

 

class CLoadMD2

{

public:

      CLoadMD2(){};

      ~CLoadMD2(){};

public:

      bool ImportMD2(char* szFileName);

      void Release();

 

protected:

      void ReadMD2Data();

 

      FILE* m_fp;

public:

      stMd2Header       m_Md2Header;

 

      stMd2Skins        *m_pSkins;

      stMd2Triangles    *m_pTriangles;

      stMd2TexCoords  *m_pTexCoords;

      stMd2Frames       *m_pFrames;

};

 

/***************************************************************************/

/*                                                                         */

/* The action implimentations of our CLoadMD2 class.                       */

/*                                                                         */

/***************************************************************************/

bool CLoadMD2::ImportMD2(char* szFileName)

{

      m_fp = fopen(szFileName, "rb");

 

      ReadMD2Data();

 

      fclose(m_fp);

      return true;

}

 

void CLoadMD2::ReadMD2Data()

{

      fread(&m_Md2Header, 1, sizeof(m_Md2Header), m_fp);   

 

      // Allocate memory for our data so we can read it in.

      m_pSkins          = new stMd2Skins  [ m_Md2Header.numSkins ];

      m_pTexCoords      = new stMd2TexCoords[ m_Md2Header.numTexCoords ];

      m_pTriangles      = new stMd2Triangles[ m_Md2Header.numTriangles ];

      m_pFrames         = new stMd2Frames [ m_Md2Header.numFrames ];

 

      // -1- Seek to the start of our skins name data and read it in.

      fseek(m_fp, m_Md2Header.offsetSkins, SEEK_SET);

      fread(m_pSkins, sizeof(stMd2Skins), m_Md2Header.numSkins, m_fp);

 

      // -2- Seek to the start of our Texture Coord data and read it in.

      fseek(m_fp, m_Md2Header.offsetTexCoords, SEEK_SET);

      fread(m_pTexCoords, sizeof(stMd2TexCoords), m_Md2Header.numTexCoords, m_fp);

 

      // -3- Seek to the start of the Triangle(e.g. Faces) data and read that in.

      fseek(m_fp, m_Md2Header.offsetTriangles, SEEK_SET);

      fread(m_pTriangles, sizeof(stMd2Triangles), m_Md2Header.numTriangles, m_fp);

 

      // -4- Finally lets read in "one" of the frames, the first one.!

     

      struct stAliasVerts

      {

            byte vertex[3]; // an index reference into the location of our vertexs

            byte lightNormalIndex; // in index into which tex coords to use.

      };

      struct stAliasFrame

      {

            float scale[3];

            float translate[3];

            char name[16];

            stAliasVerts aliasVerts[1];

      };

     

      unsigned char largebuffer[50000];

      stAliasFrame* pTempFrame = (stAliasFrame*) largebuffer;

 

      fseek(m_fp, m_Md2Header.offsetFrames, SEEK_SET);

      fread(pTempFrame, 1, m_Md2Header.frameSize, m_fp); // We have read in all the frame data here (into a temporyary!!..eeEKK)..

 

      m_pFrames[0].pFinalVerts = new stMd2Vertices[ m_Md2Header.numVertices ];

     

      // CONVERSION!  A few things before we can use our read in values,

      // for some reason the Z and Y need to be swapped, as Z is facing up

      // and Y is facing into the screen.

      // Also our texture coordinates values are between 0 and 256, we just

      // divide them all by 256 which makes them between 0 and 1.

 

      // Swap Z<->Y

      for(int i=0; i< m_Md2Header.numVertices; i++)

      {

            m_pFrames[0].pFinalVerts[i].vertex[0] = pTempFrame->aliasVerts[i].vertex[0] * pTempFrame->scale[0]

                                                                                                      + pTempFrame->translate[0];      // x

            m_pFrames[0].pFinalVerts[i].vertex[2] = -1*(pTempFrame->aliasVerts[i].vertex[1] * pTempFrame->scale[1]

                                                                                                      + pTempFrame->translate[1]); // z

            m_pFrames[0].pFinalVerts[i].vertex[1] = pTempFrame->aliasVerts[i].vertex[2] * pTempFrame->scale[2]

                                                                                                      + pTempFrame->translate[2];      // y

      }

 

      // Scale Textures.

      for (int j=0; j< m_Md2Header.numTexCoords; j++)

      {

            // WARNING.. you can't put a decimal number into a short...e.g.

            // you can't put 0.1 into a unsigned short int, it will be changed to 0.

            /*

            m_pTexCoords[j].u = m_pTexCoords[j].u ;// 256; //float(m_Md2Header.skinWidth);

            m_pTexCoords[j].v = m_pTexCoords[j].v ;// 256; //float(m_Md2Header.skinHeight);

            */

      }

 

}

 

void CLoadMD2::Release()

{

      // Tidy up before exiting.

      delete[] m_pFrames[0].pFinalVerts;

 

      delete[] m_pSkins;

      delete[] m_pTexCoords;

      delete[] m_pTriangles;

      delete[] m_pFrames;

}

And finally, the code which actually displays our model data using directX3D is in dxdraw.cpp.

/***************************************************************************/

/*                                                                         */

/* File: dxdraw.cpp                                                        */

/* Author: bkenwright@xbdev.net                                       */

/* Date: 10-11-2002                                                        */

/*                                                                         */

/***************************************************************************/

 

LPDIRECT3D8 g_pD3D = NULL;

LPDIRECT3DDEVICE8 g_pD3DDevice = NULL;

 

void init(HWND hWnd)

{

    //First of all, create the main D3D object. If it is created successfully we

    //should get a pointer to an IDirect3D8 interface.

    g_pD3D = Direct3DCreate8(D3D_SDK_VERSION);

    //Get the current display mode

    D3DDISPLAYMODE d3ddm;

    g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);

    //Create a structure to hold the settings for our device

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));

    //Fill the structure.

    //We want our program to be windowed, and set the back buffer to a format

    //that matches our current display mode

    d3dpp.Windowed = TRUE;

    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;

    d3dpp.BackBufferFormat = d3ddm.Format;

    //For depth buffering (e.g.) the z-buffer

    d3dpp.BackBufferCount=1;

    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

    d3dpp.EnableAutoDepthStencil = TRUE;

    //Create a Direct3D device

    g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pD3DDevice);

    //Turn on z-buffering

    g_pD3DDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);

 

      //Turn on back face culling. This is becuase we want to hide the back of our polygons

    //g_pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); //D3DCULL_CCW);

      g_pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);

}

 

void de_init()

{

      g_pD3DDevice->Release();

    g_pD3DDevice = NULL;

    g_pD3D->Release();

    g_pD3D = NULL;

}

void set_camera()

{

    // [1] D3DTS_VIEW

    D3DXMATRIX v;

    g_pD3DDevice->SetTransform(D3DTS_VIEW, D3DXMatrixLookAtLH(&v, &D3DXVECTOR3(0,0,-90),

                                                                  &D3DXVECTOR3(0,0,0),

                                                                  &D3DXVECTOR3(0,1,0))); 

    // [2] D3DTS_PROJECTION

    D3DXMATRIX p;

    g_pD3DDevice->SetTransform( D3DTS_PROJECTION, D3DXMatrixPerspectiveFovLH( &p,  D3DX_PI/4,  1.0f,

                                                                                                 1.0f,200.0f));

};

 

struct my_vertex

{

      FLOAT x, y, z;  // D3DFVF_XYZ

      FLOAT tu, tv;   // D3DFVF_TEX1

};

 

void Render(CLoadMD2* m)

{

      if(!g_pD3DDevice)return;

 

      set_camera();

 

     

      static float angle = 0.0f;

    angle += 0.001f;

      //~~~*~~~~ Create a matrix

      D3DXMATRIX mx;

    //~~~*~~~~ Do somthing to our empty matrix.

    D3DXMatrixRotationY(&mx, angle ); // angle in radians...eg. 1 degree = PI/180

    //~~~*~~~~ Use the matrix! No use having a matrix if we don't use it!

    g_pD3DDevice->SetTransform(D3DTS_WORLD, &mx);

     

 

      int numFaces = m->m_Md2Header.numTriangles;

     

 

      UINT my_vertex_description = (D3DFVF_XYZ | D3DFVF_TEX1);

      IDirect3DVertexBuffer8 * DX_vb;

      g_pD3DDevice->CreateVertexBuffer( numFaces * 3 * sizeof(my_vertex), 0, my_vertex_description, D3DPOOL_MANAGED, &DX_vb );

 

      // Copy our array which is in computer memory over to the directX memory.. using that pointer we

    // just created etc.

    unsigned char *temp_pointer_vb;

 

    DX_vb->Lock(0,0, &temp_pointer_vb, 0);

      my_vertex* p_mem = (my_vertex*)temp_pointer_vb;

 

      for(int i=0; i< numFaces; i++)

      {

            for(int c=0; c<3; c++) // 3 sides to each face (e.g. triangle).

            {

                  int index = m->m_pTriangles[i].vertexIndex[c];

                  p_mem[i*3+c].x = m->m_pFrames[0].pFinalVerts[index].vertex[0];//x

                  p_mem[i*3+c].y = m->m_pFrames[0].pFinalVerts[index].vertex[1];//y

                  p_mem[i*3+c].z = m->m_pFrames[0].pFinalVerts[index].vertex[2];//z

                 

                  int texIndex = m->m_pTriangles[i].texIndex[c];

                  p_mem[i*3+c].tu = m->m_pTexCoords[ texIndex ].u / 256.0f;

                  p_mem[i*3+c].tv = m->m_pTexCoords[ texIndex ].v / 256.0f;

            }

      }

 

      DX_vb->Unlock();

 

    // Okay at this point... our graphics card has our vertices stored in it... we've just copied

    // them over :) Some stuff to setup or graphics card!

    //Turn off lighting becuase we are specifying that our vertices have colour

    g_pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

    g_pD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);

      g_pD3DDevice->SetTextureStageState(0,D3DTSS_COLORARG1, D3DTA_TEXTURE);

 

      // Clear the back buffer to a blue color

      g_pD3DDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );

     

      IDirect3DTexture8* pTexture;

      D3DXCreateTextureFromFile(g_pD3DDevice, "Pac3D.bmp", &pTexture);

 

      // Draw our triangle.

      g_pD3DDevice->SetStreamSource(0, DX_vb, sizeof(my_vertex));

      g_pD3DDevice->SetVertexShader(my_vertex_description);

      g_pD3DDevice->SetTexture(0,pTexture);

      g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, numFaces);

      // After rendering the scene we display it.

      g_pD3DDevice->Present( NULL, NULL, NULL, NULL );

     

      // As where calling the functino over and over again.. we'd be constantly creating new memory

      // without releasing the old if not for this line!

      DX_vb->Release();

}

As you might be aware, I did this code like this so that all the different parts of the code are seperated, e.g. the windows code, the directX code and the loading in of our 3D model data from the md2 file.  Why?  Well if you ever code to write code for the xbox?  Or linux? etc its a lot easier.

 

 

 

 

 

 

 
Advert (Support Website)

 
 Visitor:
Copyright (c) 2002-2024 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.