Hehehehe, a bunny shape and more... well to keep things simple and to
introduce the concept of how 3D file systems worked, I came across a very simple
file system called .plg. Its a text based file system and you can open it
with notepad. Let me show you a snippet of what lies inside (Oct.plg:
We have the shape name followed by the number of vertices and then the number
of sides, e.g. 6 vertices and 8 triangles. Now what follows might be a bit
wobbly to a new 3D programmer, but its a list of all the vertices once, for
example a cube would have 8 vertices ... one for each corner.
So after that, we have the index of each face, so "109 3 4 1 3", 109 is the
colour (0-255), 3 is the number of sides for this shape (3 for a triangle),
followed by the index into the vertice list, so we would have 4th joined to the
1st then joined to the 3rd vertices and closing it would make our triangle.
Now believe it or not, this is how nearly all 3D formats work, .md2 (quake
format), .3ds (3d studio max format) all use this format... and many many more.
Keeping my code simple, I've put the DirectX initialisation and cleanup
functions in a separate file "init.cpp" and the main code in main.cpp. It
makes things simpler to follow, and well its rare that you'll ever need to
change init.cpp.
Grab hold of your chair.... in we go....
/***************************************************************************/
/* */
/* FileName: init.cpp */
/* */
/* Details: shapes from files. */
/* */
/* Author: Ben_3D@xbdev.net */
/* */
/***************************************************************************/
//Main header file for the XDK
#include
<xtl.h>
LPDIRECT3D8
g_pD3D = NULL; // DirectX
Object
LPDIRECT3DDEVICE8 g_pD3DDevice = NULL; //
Screen Object
void
InitialiseD3D()
{
g_pD3D
= Direct3DCreate8(D3D_SDK_VERSION);
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.BackBufferWidth = 640;
d3dpp.BackBufferHeight = 480;
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.BackBufferCount = 1;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
g_pD3D->CreateDevice(0, D3DDEVTYPE_HAL, NULL,
D3DCREATE_HARDWARE_VERTEXPROCESSING,
&d3dpp, &g_pD3DDevice);
//NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW
NEW NEW NEW NEW NEW NEW
//Our 3D shapes are going to use a colour defined by
us, and we don't have
//any lighting so we set the lighting to false.
g_pD3DDevice->SetRenderState(D3DRS_LIGHTING, false);
//Turn on z-buffering
g_pD3DDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);
g_pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
}
void
CleanUp()
{
g_pD3DDevice->Release();
g_pD3D->Release();
}
Well if you look at the above code is should be boring and repetaive...so
don't worry about it...all your exciting and wonderful directX code has it in :)
Now for the sticky slimmy code that makes you wanna queez...
/***************************************************************************/
/* */
/* FileName: main.cpp */
/* */
/* Details: shapes from files. */
/* */
/* Author: Ben_3D@xbdev.net */
/* */
/* */
/***************************************************************************/
/***************************************************************************/
char
shapefilename[] = "D:\\Rabbit.plg";
// Main header file for the XDK
#include
<xtl.h>
// Standard librarys used to read in our data from file. 3D object.
#include
<stdio.h>
#include
<fstream.h>
#include
"init.cpp"
// Here are a couple of structures to store our data, its so much tidier to
use
// structures, as we can store an XYZ value in the stXYZ stucture, an if we
need
// to access it we can just go stXYZ myP; myP.x = 0; etc.
struct
stXYZ
{
float x, y, z;
};
struct
stFace
{
int p1;
int p2;
int p3;
};
// These are our global variables, I'm going to load the 3D object in, and put
// them in here.
// Global variables. These will hold the data that contains our shapes.
stXYZ*
pVerts;
stFace*
pFaces;
int
iNum_vectors = 0;
int
iNum_polys = 0;
// Our wonderful wonderful loader function, if you look at this funciton
you'll
// notice that it works on either the xdk and windows, and would even work
// on linux as it uses the standard librarys.
int
loadshape(char *filename)
{
long int
colour;
int i;
char temp[16];
// Error if iNum_vectors = 0 when returning from
function
int iNum_points = 0;
// Temp used to determine if the side is made up
// of 3 sides (triangle or
more).
ifstream fin(filename);
if(!fin) {
// If where in here, and error occured opening the
file.
return 0;
}
// read in number of points and polygons in object
fin >>
temp >> iNum_vectors >> iNum_polys;
// Initialize our arrays
pVerts
= new stXYZ[iNum_vectors];
pFaces
= new stFace[iNum_polys];
// read in all the vectors used in this object
for (i=0; i<iNum_vectors; i++)
{
fin
>> pVerts[i].x >> pVerts[i].y >> pVerts[i].z;
}
// now read in all of the polygon data
for (i=0; i<iNum_polys; i++)
{
fin
>> colour;
// We could use the colour in some way.. but I'm
just going to set
// all the faces to the same colour... keep it nice
and simple :)
fin
>> iNum_points;
if( iNum_points > 3 )
{
return 0;
// Our code only supports triangles.
}
fin
>> pFaces[i].p1 >> pFaces[i].p2 >> pFaces[i].p3;
}
fin.close();
}
// Now upto now there's been "No" directX used, only knowledge of the 3d file,
// and the c and c++ standard librarys. So what I'm going to do, is load the
// vertex points into the directX buffers and display it. This isn't very
// efficent... but its simple to follow :)
void
DrawShape()
{
struct CUSTOMVERTEX
{
FLOAT x, y, z;
DWORD colour;
};
CUSTOMVERTEX *myVertices = new
CUSTOMVERTEX[iNum_polys*3];
// Total number of triangles is iNum_polys
int index=0;
for (int i=0;
i<iNum_polys; i++)
{
myVertices[index].x = pVerts[pFaces[i].p1].x;
myVertices[index].y = pVerts[pFaces[i].p1].y;
myVertices[index].z = pVerts[pFaces[i].p1].z;
myVertices[index].colour = 0xff00ffff;
index++;
myVertices[index].x = pVerts[pFaces[i].p2].x;
myVertices[index].y = pVerts[pFaces[i].p2].y;
myVertices[index].z = pVerts[pFaces[i].p2].z;
myVertices[index].colour = 0xff0ff00f;
index++;
myVertices[index].x = pVerts[pFaces[i].p3].x;
myVertices[index].y = pVerts[pFaces[i].p3].y;
myVertices[index].z = pVerts[pFaces[i].p3].z;
myVertices[index].colour = 0xff00ffff;
index++;
}
LPDIRECT3DVERTEXBUFFER8 pVertexBuffer = NULL;
UINT
D3DFVF_CUSTOMVERTEX = (D3DFVF_XYZ|D3DFVF_DIFFUSE);
g_pD3DDevice->CreateVertexBuffer(3 * iNum_polys *
sizeof(CUSTOMVERTEX),
0, D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT, &pVertexBuffer);
VOID*
pV;
pVertexBuffer->Lock(0,0, (BYTE**)&pV, 0);
memcpy(pV, myVertices, sizeof(CUSTOMVERTEX)*iNum_polys*3);
pVertexBuffer->Unlock();
g_pD3DDevice->SetStreamSource(0, pVertexBuffer,
sizeof(CUSTOMVERTEX));
g_pD3DDevice->SetVertexShader(D3DFVF_CUSTOMVERTEX);
g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, iNum_polys);
pVertexBuffer->Release();
delete myVertices;
}
void
Render()
{
//Clear the backbuffer to black
g_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB(0, 0, 255), 1.0f, 0);
//Begin the scene
g_pD3DDevice->BeginScene();
//camera camera camera camera
camera camera camera camera camera camera
D3DXMATRIX view_matrix, matProj;
D3DXMatrixLookAtLH(&view_matrix,&D3DXVECTOR3( 0.0f,
0.0f,-4.0f ),
&D3DXVECTOR3( 0.0f, 0.0f, 0.0f ),
&D3DXVECTOR3( 0.0f, 1.0f, 0.0f ));
g_pD3DDevice->SetTransform(D3DTS_VIEW,&view_matrix);
D3DXMatrixPerspectiveFovLH(&matProj,
//Result Matrix
D3DX_PI/4,//Field of
View, in radians. (PI/4) is typical
((float)600 / (float)400),
//Aspect ratio
1.0f, //Near view
plane
1000.0f ); // Far view
plane
g_pD3DDevice->SetTransform( D3DTS_PROJECTION, &matProj );
//end camera end camera end camera
end camera end camera end camera end
//Okay this all may seem scary for
someone new to matrix's
//but these few lines
//of code rotate our sphere so
that we can make sure its round :)
D3DXMATRIX matWorld;
D3DXMATRIX trans_matrix; //Our
translation matrix
D3DXMATRIX y_rot_matrix;
static
float y_rot=0;
y_rot-=0.005f;
//Set up the rotation matrix for
the triangle
D3DXMatrixRotationY(&y_rot_matrix,y_rot);
//Set up the translation matrix
(move it over to the left a bit)
D3DXMatrixTranslation(&trans_matrix,-1.0,0.0f,0.0f);
//Combine out matrices
D3DXMatrixMultiply(&matWorld,&y_rot_matrix,&trans_matrix);
//Set our World Matrix
g_pD3DDevice->SetTransform(D3DTS_WORLD,&matWorld );
//NEW NEW NEW NEW NEW NEW NEW NEW
NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW
DrawShape();
//End the scene
g_pD3DDevice->EndScene();
//Filp the back and front buffers so that whatever
has been rendered on
//the back buffer
//will now be visible on screen (front buffer).
g_pD3DDevice->Present(NULL, NULL, NULL, NULL);
}
/////////////////////////////////////////////////////////////////////////////////////////
//NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW
//NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW NEW
//Application entry point
void
__cdecl main()
{
InitialiseD3D();
loadshape(shapefilename);
while(true)
{
Render();
}
CleanUp();
}
There's a lot of code there, I hope some of it is easy to follow the main
pieces of code you should be chewing up slowly is "loadshape(..)" and the "DrawShape()"
functions. We call "loadshape(..)" once at the start which loads our data
in from the file, then in our main loop we call "DrawShape()" over and over
again and it takes our vertices and face data that we loaded in and puts it into
a list of triangle faces and puts it to the screen.
Looking at the first part of this code you might realise that its standard
C... no DirectX or anthing special...just C...hmmm...I know what your
thinking... well you can use other C functions and load in other file formats
such as .3ds for .md3 etc...yup.. .its as easy as that.