First a few vertex-related declarations. Nothing too exciting. We want our mesh to have a diffuse
colour component, untransformed coordinates, and because we're going to light it, a normal.
//This is the format we want our mesh to be in
#define MESH_FVF (D3DFVF_DIFFUSE|D3DFVF_XYZ|D3DFVF_NORMAL)
//The corresponding vertex structure
struct my_vertex{
FLOAT x, y, z;
FLOAT nx,ny,nz;
DWORD colour;
};
This tutorial uses the dhEngine, so we create one. Since we want to light the mesh, we also need
a light. And finally a pointer for our mesh.
dhEngine g_engine;
D3DLIGHT8 g_light;
//Our mesh which will contain our 3D Text
ID3DXMesh *g_mesh=NULL;
There's the standard engine initialization code, nothing exciting there so we won't cover it here.
Though it's worth noting that we need a Z-Buffer to make sure our mesh is drawn properly. Here's the
InitScene function which builds our light.
void InitScene(void){
//Here we set up a simple light, nothing we haven't seen in previous tutorials
ZeroMemory( &g_light, sizeof(D3DLIGHT8) );
g_light.Type=D3DLIGHT_POINT;
g_light.Diffuse.r=1.0f;
g_light.Diffuse.g=1.0f;
g_light.Diffuse.b=1.0f;
g_light.Range=1000.0f;
g_light.Position=D3DXVECTOR3(-8.0f,8.0f,-18.0f);
g_light.Attenuation0=1.0f;
g_engine.GetDevice()->SetLight( 0, &g_light );
g_engine.GetDevice()->LightEnable( 0, TRUE);
g_engine.SetRenderState(D3DRS_LIGHTING,TRUE);
}
There's no KillScene, since InitScene doesn't allocate anything there is nothing to free.
There is a KillFont, here it is. All it does is safely free our mesh.
void KillFont(void){
if(g_mesh!=NULL){
g_mesh->Release();
g_mesh=NULL;
}
}
The BuildFont procedure is where all the magic happens. It's a little long, but there really isn't
much to it.
void BuildFont(void){
HRESULT hr;
HFONT my_font;
HDC win_hdc;
ID3DXMesh *mesh=NULL;
//D3DXCreateText take an HDC as one of it's parameters, so we get one
//from our main window
win_hdc=GetDC(g_engine.GetWindow());
//This is a standard Win32 CreateFont call. For detail on it's parameters
//you may want to buy a good Win32 API reference. It's beyond the scope of
//this tutorial
my_font=CreateFont(0, //Height
0, //Width
0, //Escapement
0, //Orientation
FW_NORMAL, //Font weight
FALSE, //Italic
FALSE, //Underline
FALSE, //StrikeOut
DEFAULT_CHARSET, //Character Set Identifier
OUT_TT_ONLY_PRECIS, //OutputPrecision
CLIP_DEFAULT_PRECIS, //ClipPrecision
ANTIALIASED_QUALITY,//Quality
DEFAULT_PITCH, //Pitch
"Arial"); //Font Name
if(!my_font){
g_engine.FatalError("Error creating font.");
}
//The font has to be 'Select'ed into the HDC so Windows can get
//the information it requires
SelectObject(win_hdc,my_font);
//Here we actually create the mesh based on our font/text
//A high deviation will give a 'chunky' looking font, low
//deviation gives a smoother one.
hr=D3DXCreateTextA(g_engine.GetDevice(),//Our D3D Device
win_hdc, //Our windows HDC
"Drunken Hyena", //Our text
0.001f, //Deviation
0.4f, //Extrusion Depth (How deep the mesh should be)
&mesh, //our Mesh
NULL); //GlyphMetrics, advanced stuff
if(FAILED(hr)){
g_engine.FatalError("Error creating font mesh");
}
//Since the mesh created by D3DXCreateText doesn't have all the
//vertex components in it that we want, so we clone it and specify
//the FVF that we want used.
hr=mesh->CloneMeshFVF(D3DXMESH_MANAGED, //Options, see the SDK docs
MESH_FVF, //The FVF we want our new mesh to have
g_engine.GetDevice(),//Our D3D Device
&g_mesh);
if(FAILED(hr)){
g_engine.FatalError("Error cloning mesh");
}
//Clean up the resources we don't need anymore
mesh->Release();
ReleaseDC(g_engine.GetWindow(),win_hdc);
DeleteObject(my_font);
//Generate normals for our mesh, by cloning it we created a normal for each
//vertex, but the value hasn't been initialized.
hr=D3DXComputeNormals(g_mesh);
if(FAILED(hr)){
g_engine.FatalError("Error cloning mesh");
}
//Randomly colour our mesh
colour_font();
}
To make our font show up better (and look more interesting) we assign a random colour to each vertex.
This is done in the colour_font procedure.
void colour_font(void){
HRESULT hr;
ULONG vert_num,count;
my_vertex *vertices;
unsigned char red,green,blue;
//Find out how many vertices there are in the mesh
vert_num=g_mesh->GetNumVertices();
//Lock the vertex buffer with the mesh
hr=g_mesh->LockVertexBuffer(0,(unsigned char **)&vertices);
//Loop through each vertex assigning a random colour.
//Each channel (rgb) is assign (0-239 + 15), which keeps the
//colour from being too dark.
for(count=0;count < vert_num; count++){
red=(rand()%240)+15;
green=(rand()%240)+15;
blue=(rand()%240)+15;
vertices[count].colour=D3DCOLOR_XRGB(red,green,blue);
}
//Now that we can unlock our buffer and it's ready to go.
g_mesh->UnlockVertexBuffer();
}
After that the only left to do is draw the font. Here's our Render function.
void Render(void){
D3DXMATRIX matWorld;
D3DXMATRIX tmp_matrix;
//Clear the ZBuffer & colour
g_engine.ClearDZ();
//Notify the device that we're ready to render
if(SUCCEEDED(g_engine.BeginScene())){
g_engine.GetDevice()->SetVertexShader(MESH_FVF);
//By setting the world to the Identity and then multiplying each matrix
//one by one, we get code that's slightly less efficient than we could write it.
//However, this makes it easy to drop in new transformations or remove them
//with minimal fuss. You can always optimize afterwards if this really did turn
//out to be a bottleneck (unlikely)
D3DXMatrixIdentity(&matWorld);
//Roughly center the mesh, I came to this value by guessing rather than
//calculation.
D3DXMatrixTranslation(&tmp_matrix,-3.0f,0,0);
D3DXMatrixMultiply(&matWorld,&matWorld,&tmp_matrix);
//Spin!
D3DXMatrixRotationY(&tmp_matrix,GetTickCount()/1500.0f);
D3DXMatrixMultiply(&matWorld,&matWorld,&tmp_matrix);
//Set our World Matrix
g_engine.GetDevice()->SetTransform(D3DTS_WORLD,&matWorld );
//This is all it takes to draw the mesh. Nice and easy.
g_mesh->DrawSubset(0);
g_engine.EndScene();
}
//Show the results
g_engine.GetDevice()->Present( NULL, NULL, NULL, NULL );
}
|