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.
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.
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.
|