Tải bản đầy đủ (.pdf) (71 trang)

Advanced 3D Game Programming with DirectX - phần 7 ppsx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (628.2 KB, 71 trang )


427
struct mtVertex
{
point3 m_loc; // Position
point3 m_norm; // Normal
ulong m_diff; // Color (Diffuse)
ulong m_spec; // Color (Specular)
texCoord2 m_tex1;
texCoord3 m_tex2;

static ulong m_fvfFlags;
};

ulong mtVertex::m_fvfFlags =
D3DFVF_XYZ |
D3DFVF_NORMAL |
D3DFVF_DIFFUSE |
D3DFVF_SPECULAR |
D3DFVF_TEXCOORDSIZE2(0) | // set 0 is 2-dimensional
D3DFVF_TEXCOORDSIZE3(1); // set 1 is 3-dimensional


Primitive Types
When drawing primitives using the D3D device, you need to inform the device what type of primitive you
would like it to draw. Currently, Direct3D can draw three types of primitives: points, lines, and triangles.
D3DPT_POINTLIST
The data being handed to the driver is a list of points. The Direct3D
device draws one pixel for each vertex handed to it.

D3DPT_LINELIST


The data being handed to the driver is a list of lines. The number of
vertices provided to the device must be even. If n vertices are passed in,
n/2 lines are drawn. For example, the third line D3D draws is from the
fourth to the fifth vertex.


428
D3DPT_LINESTRIP
Direct3D draws a continuous strip of lines. Each vertex besides the first
becomes the endpoint of a line, with a beginning of the vertex before it.

D3DPT_TRIANGLELIST
Direct3D draws a list of distinct triangles. Each three vertices are
rendered as a triangle. Of course, the number of vertices supplied to the
DrawPrim functions must be a multiple of three.

D3DPT_TRIANGLESTRIP
Direct3D draws a triangle strip, each vertex after the first two defining the
third point of a triangle. See Chapter 5
for a discussion of triangle strips.

D3DPT_TRIANGLEFAN
Direct3D draws a triangle fan, each vertex after the first two defining the
third point of a triangle. See Chapter 5
for a discussion of triangle fans.
The DrawPrimitive Functions
There are four total functions to draw primitives for us. They are all very similar and once you've
mastered one, you've pretty much mastered them all. Let's take a look at each of them.
DrawPrimitive
DrawPrimitive is the most basic primitive drawing function. It simply takes the current vertex buffer that

is attached to a rendering stream and renders it. It doesn't use any indexed information, and therefore
isn't as efficient for drawing triangle meshes as DrawIndexedPrimitive for most applications. The one
exception is drawing triangle strips and fans. On some cards (such as the GeForce), the cache
coherency goes way up and using DrawPrimitive is actually faster than DrawIndexedPrimitive.
HRESULT DrawPrimitive(
D3DPRIMITIVETYPE PrimitiveType,
UINT StartVertex,
UINT PrimitiveCount
);
PrimitiveType
The type of primitive you would like the device to draw for you.

StartVertex
Index of the first vertex you want to load; usually set this to 0.

PrimitiveCount
The number of primitives to render.

429
DrawPrimitiveUP
DrawPrimitiveUP is very similar to the regular DrawPrimitive except that it does not require you to
package your vertices in buffers. Instead it takes a pointer to vertex data that exists somewhere in
system memory and uses that as the rendering stream. UP, by the way, stands for user pointer. The
function has this definition:
HRESULT DrawPrimitiveUP(
D3DPRIMITIVETYPE PrimitiveType,
UINT PrimitiveCount,
CONST void* pVertexStreamZeroData,
UINT VertexStreamZeroStride
);

PrimitiveType
The type of primitive you would like the device to draw for you.

PrimitiveCount
The number of primitives you want to render.

pVertexStreamZeroData
A pointer to the vertex data that the device will use as rendering
stream 0.

VertexStreamZeroStride
The stride between each vertex, in bytes. Usually this will be 0.
DrawIndexedPrimitive
DrawIndexedPrimitive accepts two buffers: an array of vertices and an array of indices. The entire list of
vertices is transformed, and then the primitives are drawn using the list of indices.

Warning
Each time DrawIndexedPrimitive is called, the entire list of vertices is transformed,
regardless of whether or not they actually end up being used in the list of indices.
Thus, for efficiency reasons, DrawIndexedPrimitive shouldn't be called multiple
times for the same buffer. If this type of behavior is required, consider putting the
vertices in a vertex buffer and transforming them just once using the
ProcessVertices method on the vertex buffer interface.
HRESULT DrawIndexedPrimitive(
D3DPRIMITIVETYPE Type,
INT BaseVertexIndex, // Note this new parameter UINT MinIndex,
UINT NumVertices,

430
UINT StartIndex,

UINT PrimitiveCount
);
Type
The type of primitive you would like the device to draw for you.

BaseVertexIndex
Offset from the start of the index buffer to the first vertex index.

MinIndex
The lowest vertex that will be used for this call.

NumVertices
The number of vertices that will be used for this call.

StartIndex
The location in the array to start reading vertices

PrimitiveCount
The number of primitives that will be rendered.
DrawIndexedPrimitiveUP
DrawIndexedPrimitiveUP is to DrawIndexedPrimitive what DrawPrimitiveUP was to DrawPrimitive.
Basically it operates in exactly the same way as DrawIndexedPrimitive, except that it uses vertex data
at a particular memory location instead of requiring it to be packaged into a vertex buffer and attached
to a rendering stream. It has this definition:
HRESULT DrawIndexedPrimitiveUP(
D3DPRIMITIVETYPE PrimitiveType,
UINT MinVertexIndex,
UINT NumVertexIndices,
UINT PrimitiveCount,
CONST void* pIndexData,

D3DFORMAT IndexDataFormat,
CONST void* pVertexStreamZeroData,
UINT VertexStreamZeroStride
);
PrimitiveType
The type of primitive you would like the device to draw for you.


431
MinVertexIndex
The minimum vertex index that will be used for a vertex in this call.

NumVertexIndices
The number of vertex indices to be used for this call.

PrimitiveCount
The number of primitives that you want to render.

pIndexData
A pointer to the index data.

IndexDataFormat
This can be set to either D3DFMT_INDEX16 or D3DFMT_INDEX32,
depending on whether you are using 16- or 32-bit indices. You will
usually use 16-bit indices.

pVertexStreamZeroData
A pointer to the vertex data.

VertexStreamZeroStride

The stride (distance between each vertex, in bytes) for the vertices;
this will almost always be 0.
Adding Direct3D to the Graphics Layer
Now that you know enough Direct3D to get up and running, let's add Direct3D support to the graphics
layer in the game library. I'll be adding more than initialization code this time around, as there are some
convenience functions to help and also new native matrix types.
Direct3D Initialization
Getting Direct3D initialized used to be a tricky process, but these days it is much more straightforward,
conceptually. In fact, in Chapter 2
, I showed you almost everything you need to know, although I'll admit
I glossed over the more complex 3D topics a little. Don't worry; I'll cover them here. For the updates
there will be some changes to the class system in Chapter 2
. There are a few new steps to perform,
such as initializing view and projection matrices, and so on.
The particular feature set an application would like may not necessarily be the same for all applications.
For example, some apps may choose not to use a z-buffer to avoid the added memory overhead on
low-memory cards. To facilitate the various options a user application might like, the graphics layer's
Direct3D initialization call accepts a set of flags that modify the path the initialization steps go through.
The flags are:


432

Table 8.7: The set of graphics layer flags
GLF_ZBUFFER
The application is requesting that a z-buffer is created.

GLF_HIRESZBUFFER
The application is requesting that a high-resolution (24- or 32-bit) z-
buffer is created.


GLF_STENCIL
The application is requesting stencil bits in addition to depth information
in the z-buffer.

GLF_FORCEREFERENCE
The application is demanding a reference device. If one of these cannot
be created, the initialization phase fails.

GLF_FORCEHARDWARE
The application is demanding a hardware (HAL) device. If one of these
cannot be created, the initialization phase fails.

GLF_FORCESOFTWARE
The application is demanding a software device. If one of these cannot
be created, the initialization phase fails.

GLF_FORCE16BIT
The application is forcing 16-bit rendering.

Let's take a step-by-step look at how Direct3D is initialized within the graphics layer. Some of this you
have already seen in Chapter 2
, but for consistency I'll show you it again since it is pretty relevant.
Acquire an IDirect3D9 Interface
Getting an IDirect3D9 interface pointer is the simplest task to do. All you need to do is ask the Direct 3D
interface pointer. This is done using Direct3DCreate9. For a discussion on how COM works, see
Chapter 1
.
Listing 8.10: Acquiring a Direct3D9 interface pointer


// Create the Direct3D interface
m_pD3D = Direct3DCreate9( D3D_SDK_VERSION );
if( !m_pD3D )

433
{
throw cGameError( "Could not create IDirect3D9" );
}


Fill In the Presentation Parameters
I'm going to run through this quickly because you have seen a lot of it before. However, it has changed
somewhat, so pay attention to the updates. If you need a refresher, refer back to Chapter 2
. The first
part of the D3DPRESENT_PARAMETERS structure deals with the format of the back buffer. Check out
the following code:
// Structure to hold the creation parameters for the device
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof( d3dpp ) );

// The width and height for the initial back buffer
d3dpp.BackBufferWidth = width;
d3dpp.BackBufferHeight = height;

// Set the flags for the bit depth - only supports 16-, 24-, and 32-bit
formats
if( bpp == 16 )
d3dpp.BackBufferFormat = D3DFMT_R5G6B5;
else if( bpp == 24 )
d3dpp.BackBufferFormat = D3DFMT_R8G8B8;

else if( bpp == 32 )
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
else
{
OutputDebugString( "Invalid surface format - defaulting to 32bit" );
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
}


434
// Only have one back buffer associated with the primary surface
d3dpp.BackBufferCount = 1;
// No multisampling
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
// Copy the back buffer to the primary surface normally
d3dpp.SwapEffect = D3DSWAPEFFECT_COPY;
// The handle to the window to render in to
d3dpp.hDeviceWindow = m_hWnd;
// Fullscreen operation
d3dpp.Windowed = FALSE;
Notice how the bit depth format is set with flags by comparing the bit depth passed as an integer. That
code is quite straightforward. Now check out the following code, which implements some of the flags
that I was talking about previously to set up the depth and stencil buffer.
// If a depth buffer was requested
if( flags & (GLF_ZBUFFER|GLF_HIRESZBUFFER) )
{
// Tell Direct3D we want a depth buffer
d3dpp.EnableAutoDepthStencil = TRUE;

if( flags & (GLF_HIRESZBUFFER) )

{

if( flags & (GLF_STENCIL) )
// 24-bit depth buffer and 8-bit stencil
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
else
// 32-bit depth buffer and no stencil
d3dpp.AutoDepthStencilFormat = D3DFMT_D32;
}
else
{
if( flags & (GLF_STENCIL) )

435
// 15-bit depth buffer and 1-bit stencil
d3dpp.AutoDepthStencilFormat = D3DFMT_D15S1;
else
// 16-bit depth buffer and no stencil
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
}
}
else
{
// No depth buffer or stencil
d3dpp.EnableAutoDepthStencil = FALSE;
}
That is also pretty straightforward, so I'll let the code speak for itself. Finally, just before I actually create
the device there is another snippet of code that I want to show that has changed from Chapter 2
:
// Use the default refresh rate

d3dpp.FullScreen_RefreshRateInHz= D3DPRESENT_RATE_DEFAULT;
// Update the screen as soon as possible (don't wait for vsync)
d3dpp.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

// Hardware device by default
D3DDEVTYPE DeviceType = D3DDEVTYPE_HAL;

if( flags & (GLF_FORCEHARDWARE) )
DeviceType = D3DDEVTYPE_HAL;

else if( flags & (GLF_FORCEREFERENCE) )
DeviceType = D3DDEVTYPE_REF;
Notice how you now have the option of forcing a certain type of device to be created by passing a flag to
the InitD3DFullScreen. After all of that structure filling it is simple to create the device with a call to, you
guessed it, CreateDevice. The function call looks like this:
// Create the device using hardware acceleration if available
r = m_pD3D->CreateDevice( Ordinal, DeviceType, m_hWnd,

436
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &m_pDevice );
if( FAILED(r))
{
throw cGameError( "Could not create IDirect3DDevice9" );
}
And that's it—you now have a fully 3D capable device set up and ready to render. If you have had
previous experience with DirectX, particularly prior to version 5.0, you will be trying to pick your jaw off
the floor out of surprise at how easy it is to create. In this last section (about two pages) is everything
that used to take over a thousand lines of code to set up. Just smile and nod.
Create a Viewport and Projection Matrix

Creating the viewport is one of the more monotonous tasks in Direct3D initialization. The graphics layer
is assuming that all applications will want the entire viewport as visible. If this is not the case, user
applications will have to create a viewport themselves.
The code that the graphics layer uses to set up the viewport is straightforward. The z-range from 0.0 to
1.0 is used, and the bounds of the screen are used as the viewport boundaries.
Listing 8.11: Viewport creation code


void cGraphicsLayer::MakeViewport()
{
HRESULT hr;
if( !m_pDevice )
{
DP("[cGraphicsLayer::MakeViewport]: no device\n");
return;
}

DWORD dwRenderWidth = m_rcScreenRect.right;
DWORD dwRenderHeight = m_rcScreenRect.bottom;
D3DVIEWPORT9 vp={0,0, dwRenderWidth, dwRenderHeight, 0.0f, 1.0f };


437
hr = m_pDevice->SetViewport( &vp );
if( FAILED( hr ) )
throw cGameError("viewport setting failed.");
}


The projection matrix your application gives Direct3D is dependent on the dimensions of your frame

buffer, so it is created when you create the viewport. It just uses the same projection matrix discussed in
Chapter 5
, which uses the recommended projection matrix from the SDK documentation.
Listing 8.12: Projection matrix construction code

eResult cGraphicsLayer::MakeProjectionMatrix()
{
D3DMATRIX mat;

DWORD width, height;
width = m_rcScreenRect.right;
height = m_rcScreenRect.bottom;

float fAspect = ((float)height) / width;

if( fabs(m_far-m_near) < 0.01f )
return resFailed;
if( fabs(sin(m_fov/2)) < 0.01f )
return resFailed;

float w = fAspect * (float)( cos(m_fov/2)/sin(m_fov/2) );
float h = 1.0f * (float)( cos(m_fov/2)/sin(m_fov/2) );
float Q = m_far / ( m_far - m_near );

ZeroMemory( &mat, sizeof(D3DMATRIX) );
mat._11 = w;

438
mat._22 = h;
mat._33 = Q;

mat._34 = 1.0f;
mat._43 = -Q*m_near;
m_pDevice->SetTransform( D3DTS_PROJECTION, &mat );

return resAllGood;
}


Further Additions to the GameLib
To handle the addition of Direct3D to the GameLib, some changes needed to be made.
The cGraphicsLayer class got a host of new functions added to it. Their names and functions are
summed up in Table 8.8
.


Table 8.8: New functions in cGraphicsLayer
void BeginScene();
Wraps IDirect3DDevice9::BeginScene.

void EndScene();
Wraps IDirect3DDevice9::EndScene.

void SetProjectionData(
float inFov,
float inNear,
float inFar );
Sets the three important projection parameters (field of view, near
z plane distance, far z plane distance). By default these values
are PI/2, 1.0, and 1000.0, respectively.


void GetProjectionData(
float* inFov,
float* inNear,
float* inFar );
Gets the three important projection parameters (field of view,
near z plane distance, far z plane distance). By default these
values are PI/2, 1.0, and 1000.0, respectively.


439
eResult
MakeProjectionMatrix();
Rebuilds the projection matrix using the currently set values for
field of view, near plane, and far plane. The projection matrix is
identical to the one described in Chapter 5
.

void GetProjectionMatrix(
matrix4* pMat );
Gets the currently set projection matrix from the D3D device.

void SetProjectionMatrix(
const matrix4& mat );
Sets the current projection matrix to the supplied matrix. Provided
for completeness, the projection matrix should be set with
SetProjectionData and MakeProjectionMatrix.

void GetViewMatrix(
matrix4* pMat );
Gets the currently set view matrix from the D3D device.


void SetViewMatrix(
const matrix4& mat );
Sets the current view matrix to the supplied matrix.

void GetWorldMatrix(
matrix4* pMat );
Gets the currently set world matrix from the D3D device.

void SetWorldMatrix(
const matrix4& mat );
Sets the current world matrix to the supplied matrix.

LPDIRECT3DDEVICE9
GetDevice();
Gets the Direct3D device interface.

LPDIRECT3D9 GetD3D();
Gets the Direct3D interface.

void Clear(
bool bClearFrame,
bool bClearZ,
Clears the back buffer, and the z-buffer if needed, to the provided
color and value.

440
DWORD frameColor,
float zValue );


The Direct3DX Library
One of the biggest complaints people made about Direct3D in the past was its complexity. The
initialization procedure, loading texture correctly from disk, and many other tasks proved to be
remarkably difficult. However, versions 8 and 9 have gone a long way to improve this state of affairs.
Microsoft's answer to this was two-fold. First, Direct3D 9.0 is considerably easier to use and manage
than previous versions. Lights, materials, and viewports used to be interfaces that needed to be
AddRef'd and Released.
The second, more interesting answer to the complaints about D3D's complexity is the Direct3DX library
(D3DX for short). It attempts to take care of most of the grunt work by providing things like macros,
mathematical functions, COM objects, and many other useful bits and pieces that makes DirectX a nicer
place to live. I'm not going to give you an exhaustive look at the D3DX library, since it so large, but I
really suggest you take a look at DirectX 9.0 C++ Documentation/DirectX Graphics/Direct3DX C++
Reference in the documentation. You may be surprised at what you find.
D3DX is extremely useful for small applications and prototyping. If you only want to test a certain
feature, or if you want to check to see what a texture looks like under certain conditions, D3DX is a
godsend.
D3DX, while extremely useful for prototyping, is not something I will be using much for this code, since it
hides away a lot of the functionality that I'm teaching you.
Application: D3D View
The sample application for this chapter is an object viewer. It loads object files from disk and displays
the object spinning around the scene. Before you can draw the spinning object, you of course need a
way to load it.
There are a myriad of different object formats out there. OBJ, 3DS, DXF, ASC, and PLG files are
available on the net or easy to construct. However, they're all either extremely hard to parse or not fully
featured enough. Rather than trudge through a parser for one of these data types, I'm going to
circumvent a lot of headache and create our own format. The web is rife with parsers for any of these
other formats, so if you want to parse it you won't have to reinvent the wheel.
The .o3d Format
The name for the object format will be .o3d (object 3D format). It's an ASCII file, which makes it easy to
edit manually if the need arises. The object is designed for regular D3DVERTEX objects, which have no


441
color information but may have normal or texture information. Listing 8.13
has the o3d file for a simple
tetrahedron model.
Listing 8.13: Tetrahedron model

Tetrahedron 3 1 4 4
-1.0 -1.0 -1.0
1.0 1.0 -1.0 -1.0 1.0 1.0
1.0 -1.0 1.0
2 3 4
1 4 3
1 3 2
1 2 4


The first line of the file is the header. It has five fields, separated by spaces. They are, in order:
 The name for the object (spaces within the name are not allowed).
 The number of fields per vertex. This can be three (just position), five (three position and two
texture), six (three position and three normal), or eight (three position, three normal, and two
texture).
 The offset for the indices. Some index lists are 0-based, some are 1-based. This offset is
subtracted from each of the indices on load. Since the indices in the tetrahedron list start at 1, the
offset is 1 (since index 1 will actually be element 0 internally).
 The number of vertices in the model.
 The number of triangles in the model.
After the header line, there is one line for each of the vertices. Each line has n fields separated by
spaces (where n is the number of fields per vertex). The first three fields are always position.
After the list of vertices, there is a list of triangles. Each triangle is defined with three indices separated

by spaces. Each index has the offset (defined in the header) subtracted from it.
The cModel Class
To load o3d models, I'm going to create a class that represents a model. It has one constructor that
takes a filename on disk. The constructor opens the file, parses it, and extracts the vertex and triangle
information. It takes the information and fills up two vectors. If the file it loads does not have normal

442
information defined for it, the class uses face averaging to automatically generate normals for the
object.
Face averaging is used often to find normals for vertices that make a model appear rounded when
Gouraud shading is used on it. The normals for each of the faces are computed, and the normal is
added to each of the face's vertices. When all of the faces have contributed their normals, the vertex
normals are normalized. This, in essence, makes each vertex normal the average of the normals of the
faces around it. This gives the model a smooth look.
The cModel class can automatically draw an object, given a matrix to use for the world matrix. It uses
DrawIndexedPrimitive to draw the entire model in one fell swoop. There are also a few accessor
functions; future classes that load models will use cModel to load the file for them, and just extract the
vertex and triangle information for themselves.
Listing 8.14: cModel, a simple drawable 3D object

#define FVF_TYPE ( D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE |
D3DFVF_TEX1 )

class cModel
{
typedef tri<unsigned short> sTri;

vector< sTri> m_tris;

vector< sVertex > m_verts;


string m_name;

public:

cModel( const char* filename );

float GenRadius();
void Scale( float amt );


443
void Draw( const matrix4& mat );

//========== Access functions.

int NumVerts(){ return m_verts.size(); }
int NumTris(){ return m_tris.size(); }
const char* Name(){ return m_name.c_str(); }

/**
* Some other classes may end up using cModel
* to assist in their file parsing. Because of this
* give them a way to get at the vertex and triangle
* data.
*/
sVertex* VertData(){ return &m_verts[0]; }
sTri* TriData(){ return &m_tris[0]; }
};


cModel::cModel( const char* filename )
{
int i;

cFile file;
file.Open( filename );

queue<string> m_tokens;

file.TokenizeNextNCLine( &m_tokens, '#' );

// first token is the name.
m_name = m_tokens.front();
m_tokens.pop();

444

// next is the # of fields in the vertex info
int nVertexFields = atoi( m_tokens.front().c_str() );
m_tokens.pop();

// next is the triangle offset
int offset = atoi( m_tokens.front().c_str() );
m_tokens.pop();

// next is the # of vertices
int nVerts = atoi( m_tokens.front().c_str() );
m_tokens.pop();

// next is the # of triangles

int nTris = atoi( m_tokens.front().c_str() );
m_tokens.pop();

// Reserve space in the vector for all the verts.
// This will speed up all the additions, since only
// one resize will be done.
m_verts.reserve( nVerts );
for( i=0; i<nVerts; i++ )
{
m_tokens.empty();
file.TokenizeNextNCLine( &m_tokens, '#' );

sVertex curr;

// Vertex data is guaranteed
curr.loc.x = atof( m_tokens.front().c_str() );
m_tokens.pop();
curr.loc.y = atof( m_tokens.front().c_str() );

445
m_tokens.pop();
curr.loc.z = atof( m_tokens.front().c_str() );
m_tokens.pop();

// Load normal data if nfields is 6 or 8
if( nVertexFields == 6 || nVertexFields == 8 )
{
curr.norm.x = atof( m_tokens.front().c_str() );
m_tokens.pop();
curr.norm.y = atof( m_tokens.front().c_str() );

m_tokens.pop();
curr.norm.z = atof( m_tokens.front().c_str() );
m_tokens.pop();
}
else
{
curr.norm.Assign( 0, 0, 0 );
}

// Load texture data if nfields is 5 or 8
if( nVertexFields == 5 || nVertexFields == 8 )
{
curr.u = atof( m_tokens.front().c_str() );
m_tokens.pop();
curr.v = atof( m_tokens.front().c_str() );
m_tokens.pop();
}
else
{
curr.u = 0.f;
curr.v = 0.f;
}

446
m_verts.push_back( curr );
}

// Reserve space in the vector for all the verts.
// This will speed up all the additions, since only
// one resize will be done.

m_tris.reserve( nTris );
for( i=0; i<nTris; i++ )
{
m_tokens.empty();
file.TokenizeNextNCLine( &m_tokens, '#' );

sTri tri;

// vertex data is guaranteed
tri.v[0] = atoi( m_tokens.front().c_str() ) - offset;
m_tokens.pop();
tri.v[1] = atoi( m_tokens.front().c_str() ) - offset;
m_tokens.pop();
tri.v[2] = atoi( m_tokens.front().c_str() ) - offset;
m_tokens.pop();

m_tris.push_back( tri );
}

if( nVertexFields == 3 || nVertexFields == 5 ) {
// Normals weren't provided. Generate our own.
// First set all the normals to zero.
for( i=0; i<nVerts;i++ )
{
m_verts[i].norm.Assign( 0,0,0 );
}

447

// Then go through and add each triangle's normal

// to each of its verts.
for( i=0; i<nTris; i++ )
{
plane3 plane(
m_verts[ m_tris[i].v[0] ].loc,
m_verts[ m_tris[i].v[1] ].loc,
m_verts[ m_tris[i].v[2] ].loc );

m_verts[ m_tris[i].v[0] ].norm += plane.n;
m_verts[ m_tris[i].v[1] ].norm += plane.n;
m_verts[ m_tris[i].v[2] ].norm += plane.n;
}

// Finally normalize all of the normals
for( i=0; i<nVerts; i++ )
{
m_verts[i].norm.Normalize();
}
}

void cModel::Scale( float amt )
{
int size = m_verts.size();
for( int i=0; i<size; i++ )
{
m_verts[i].loc *= amt;
}
}

void cModel::Draw( const matrix4& mat )


448
{
Graphics()->SetWorldMatrix( mat );

SetFVF(FVF_TYPE);

Graphics()->GetDevice()->DrawIndexedPrimitiveUP(
D3DPT_TRIANGLELIST,
0,
m_verts.size(),
m_tris.size()
&m_tris[0],
D3DFMT_INDEX16,
&m_verts[0],
sizeof( vertex ) );
}

float cModel::GenRadius()
{
float best = 0.f;
int size = m_verts.size();
for( int i=0; i<size; i++ )
{
float curr = m_verts[i].loc.Mag();
if( curr > best )
best = curr;
}
return best;
}



Now that you have a way to load models, a program just needs to be wrapped around it. That is what
the D3DSample program does. It takes a filename in the constructor, loads it, creates three colored

449
directional lights, and spins the object around in front of the camera. There is no user input for this
program; it's just there to look pretty. See Figure 8.5
for a screen shot of D3DSample in action.

Figure 8.5: Screen shot from D3DSample
The code for D3DSample appears in Listing 8.15
. There are a few models in the Chapter 08\BIN\Media
folder in the downloadable files, so you can mess around with it if you want to see what other models
look like. I highly recommend the rabbit.
Listing 8.15: D3DSample.cpp

/*******************************************************************
* Advanced 3D Game Programming using DirectX 9.0
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Title: D3DSample.cpp * Desc: An extremely simple D3D app, using the
framework * we have made
* (C) 2003 by Peter A Walsh and Adrian Perez
* See license.txt for modification and distribution information
******************************************************************/

#include "stdafx.h"

class cD3DSampleApp : public cApplication
{



450
public:

string m_filename;
cModel* m_pModel;

void InitLights();

//========== cApplication

virtual void DoFrame( float timeDelta );
virtual void SceneInit();

virtual void SceneEnd()
{
delete m_pModel;
}

cD3DSampleApp() :
cApplication()
{
m_title = string( "D3DSample - Objects Spinning in D3D" );
m_pModel = NULL;
m_filename = " \\BIN\\Media\\Cow.o3d";
}
};

cApplication* CreateApplication()

{
return new cD3DSampleApp();
}

void DestroyApplication( cApplication* pApp )

451
{
delete pApp;
}

void cD3DSampleApp::SceneInit()
{
/**
* We're making the FOV less than 90 degrees.
* this is so the object doesn't warp as much
* when we're really close to it.
*/
Graphics()->SetProjectionData( PI/4.f, 0.5f, 10.f );
Graphics()->MakeProjectionMatrix();

/**
* initialize our scene
*/
LPDIRECT3DDEVICE9 pDevice = Graphics()->GetDevice();

pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
pDevice->SetRenderState(D3DRS_LIGHTING, TRUE);
pDevice->SetRenderState(D3DRS_DITHERENABLE, TRUE);
pDevice->SetRenderState(D3DRS_SPECULARENABLE, TRUE);

pDevice->SetRenderState(D3DRS_AMBIENT, 0x404040);

/**
* initialize the camera
*/
Graphics()->SetViewMatrix( matrix4::Identity );

/**
* Create a model with the given filename,

×