topical media & game development
graphic-directx-game-18-Props-PropsDemo.cpp / cpp
//=============================================================================
// PropsDemo.cpp by Frank Luna (C) 2005 All Rights Reserved.
//
// Adds various props to our terrain scene like trees, water, and a castle.
//
// Controls: Use mouse to look and 'W', 'S', 'A', and 'D' keys to move.
// Use 'M' to enable free camera, 'N' to disable free camera.
//=============================================================================
include <d3dApp.h>
include <DirectInput.h>
include <crtdbg.h>
include <GfxStats.h>
include <list>
include <ctime>
include <Terrain.h>
include <Camera.h>
include <Water.h>
struct Object3D
{
Object3D()
{
mesh = 0;
}
~Object3D()
{
ReleaseCOM(mesh);
for(UINT i = 0; i < textures.size(); ++i)
ReleaseCOM(textures[i]);
}
ID3DXMesh* mesh;
std::vector<Mtrl> mtrls;
std::vector<IDirect3DTexture9*> textures;
AABB box;
};
class PropsDemo : public D3DApp
{
public:
PropsDemo(HINSTANCE hInstance, std::string winCaption, D3DDEVTYPE devType, DWORD requestedVP);
~PropsDemo();
bool checkDeviceCaps();
void onLostDevice();
void onResetDevice();
void updateScene(float dt);
void drawScene();
void buildFX();
void drawObject(Object3D& obj, const D3DXMATRIX& toWorld);
void buildCastle();
void buildTrees();
void buildGrass();
void buildGrassFin(GrassVertex* v, WORD* k, int& indexOffset,
D3DXVECTOR3& worldPos, D3DXVECTOR3& scale);
private:
GfxStats* mGfxStats;
Terrain* mTerrain;
Water* mWater;
float mTime; // Time elapsed from program start.
// Models
Object3D mCastle;
D3DXMATRIX mCastleWorld;
Object3D mTrees[4];
static const int NUM_TREES = 200;
D3DXMATRIX mTreeWorlds[NUM_TREES];
static const int NUM_GRASS_BLOCKS = 4000;
ID3DXMesh* mGrassMesh;
IDirect3DTexture9* mGrassTex;
// Grass FX
ID3DXEffect* mGrassFX;
D3DXHANDLE mhGrassTech;
D3DXHANDLE mhGrassViewProj;
D3DXHANDLE mhGrassTex;
D3DXHANDLE mhGrassTime;
D3DXHANDLE mhGrassEyePosW;
D3DXHANDLE mhGrassDirToSunW;
// General light/texture FX
ID3DXEffect* mFX;
D3DXHANDLE mhTech;
D3DXHANDLE mhWVP;
D3DXHANDLE mhWorldInvTrans;
D3DXHANDLE mhEyePosW;
D3DXHANDLE mhWorld;
D3DXHANDLE mhTex;
D3DXHANDLE mhMtrl;
D3DXHANDLE mhLight;
// The sun.
DirLight mLight;
// Camera fixed to ground or can fly?
bool mFreeCamera;
// Default texture if no texture present for subset.
IDirect3DTexture9* mWhiteTex;
};
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
PSTR cmdLine, int showCmd)
{
// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif
srand(time(0));
// Construct camera before application, since the application uses the camera.
Camera camera;
gCamera = &camera;
PropsDemo app(hInstance, "Props Demo", D3DDEVTYPE_HAL, D3DCREATE_HARDWARE_VERTEXPROCESSING);
gd3dApp = &app;
DirectInput di(DISCL_NONEXCLUSIVE|DISCL_FOREGROUND, DISCL_NONEXCLUSIVE|DISCL_FOREGROUND);
gDInput = &di;
return gd3dApp->run();
}
PropsDemo::PropsDemo(HINSTANCE hInstance, std::string winCaption, D3DDEVTYPE devType, DWORD requestedVP)
: D3DApp(hInstance, winCaption, devType, requestedVP)
{
if(!checkDeviceCaps())
{
MessageBox(0, "checkDeviceCaps() Failed", 0, 0);
PostQuitMessage(0);
}
InitAllVertexDeclarations();
mTime = 0.0f;
mGfxStats = new GfxStats();
// Set path to art resources.
SetCurrentDirectory("Art/");
// World space units are meters.
mTerrain = new Terrain(257, 257, 2.0f, 2.0f,
"castlehm257.raw", "grass.dds", "dirt.dds",
"rock.dds", "blend_castle.dds", 0.5f, 0.0f);
D3DXVECTOR3 toSun(-1.0f, 3.0f, 1.0f);
D3DXVec3Normalize(&toSun, &toSun);
mTerrain->setDirToSunW(toSun);
// Setup water.
D3DXMATRIX waterWorld;
D3DXMatrixTranslation(&waterWorld, 8.0f, 35.0f, -80.0f);
mWater = new Water(33, 33, 20, 20, waterWorld);
// Initialize camera.
gCamera->pos() = D3DXVECTOR3(8.0f, 35.0f, -100.0f);
gCamera->setSpeed(20.0f);
mFreeCamera = false;
buildCastle();
buildTrees();
buildGrass();
HR(D3DXCreateTextureFromFile(gd3dDevice, "grassfin0.dds", &mGrassTex));
HR(D3DXCreateTextureFromFile(gd3dDevice, "whitetex.dds", &mWhiteTex));
buildFX();
mLight.dirW = -toSun;
D3DXVec3Normalize(&mLight.dirW, &mLight.dirW);
mLight.ambient = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
mLight.diffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
mLight.spec = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
HR(mFX->SetValue(mhLight, &mLight, sizeof(DirLight)));
HR(mGrassFX->SetValue(mhGrassDirToSunW, &(-mLight.dirW), sizeof(D3DXVECTOR3)));
mGfxStats->addVertices(mTerrain->getNumVertices());
mGfxStats->addTriangles(mTerrain->getNumTriangles());
mGfxStats->addVertices(mWater->getNumVertices());
mGfxStats->addTriangles(mWater->getNumTriangles());
mGfxStats->addVertices(mCastle.mesh->GetNumVertices());
mGfxStats->addTriangles(mCastle.mesh->GetNumFaces());
mGfxStats->addVertices(mTrees[0].mesh->GetNumVertices()*NUM_TREES/4);
mGfxStats->addTriangles(mTrees[0].mesh->GetNumFaces()*NUM_TREES/4);
mGfxStats->addVertices(mTrees[1].mesh->GetNumVertices()*NUM_TREES/4);
mGfxStats->addTriangles(mTrees[1].mesh->GetNumFaces()*NUM_TREES/4);
mGfxStats->addVertices(mTrees[2].mesh->GetNumVertices()*NUM_TREES/4);
mGfxStats->addTriangles(mTrees[2].mesh->GetNumFaces()*NUM_TREES/4);
mGfxStats->addVertices(mTrees[3].mesh->GetNumVertices()*NUM_TREES/4);
mGfxStats->addTriangles(mTrees[3].mesh->GetNumFaces()*NUM_TREES/4);
mGfxStats->addVertices(mGrassMesh->GetNumVertices());
mGfxStats->addTriangles(mGrassMesh->GetNumFaces());
onResetDevice();
}
PropsDemo::~PropsDemo()
{
delete mGfxStats;
delete mTerrain;
delete mWater;
ReleaseCOM(mWhiteTex);
ReleaseCOM(mFX);
ReleaseCOM(mGrassMesh);
ReleaseCOM(mGrassTex);
ReleaseCOM(mGrassFX);
DestroyAllVertexDeclarations();
}
bool PropsDemo::checkDeviceCaps()
{
D3DCAPS9 caps;
HR(gd3dDevice->GetDeviceCaps(&caps));
// Check for vertex shader version 2.0 support.
if( caps.VertexShaderVersion < D3DVS_VERSION(2, 0) )
return false;
// Check for pixel shader version 2.0 support.
if( caps.PixelShaderVersion < D3DPS_VERSION(2, 0) )
return false;
return true;
}
void PropsDemo::onLostDevice()
{
mGfxStats->onLostDevice();
mTerrain->onLostDevice();
mWater->onLostDevice();
HR(mFX->OnLostDevice());
HR(mGrassFX->OnLostDevice());
}
void PropsDemo::onResetDevice()
{
mGfxStats->onResetDevice();
mTerrain->onResetDevice();
mWater->onResetDevice();
HR(mFX->OnResetDevice());
HR(mGrassFX->OnResetDevice());
// The aspect ratio depends on the backbuffer dimensions, which can
// possibly change after a reset. So rebuild the projection matrix.
float w = (float)md3dPP.BackBufferWidth;
float h = (float)md3dPP.BackBufferHeight;
gCamera->setLens(D3DX_PI * 0.25f, w/h, 1.0f, 1000.0f);
}
void PropsDemo::updateScene(float dt)
{
mTime += dt;
mGfxStats->update(dt);
gDInput->poll();
// Fix camera to ground or free flying camera?
if( gDInput->keyDown(DIK_N) )
mFreeCamera = false;
if( gDInput->keyDown(DIK_M) )
mFreeCamera = true;
if( mFreeCamera )
{
gCamera->update(dt, 0, 0);
}
else
{
gCamera->update(dt, mTerrain, 2.5f);
}
mWater->update(dt);
}
void PropsDemo::drawScene()
{
// Clear the backbuffer and depth buffer.
HR(gd3dDevice->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xff888888, 1.0f, 0));
HR(gd3dDevice->BeginScene());
HR(mFX->SetValue(mhEyePosW, &gCamera->pos(), sizeof(D3DXVECTOR3)));
HR(mFX->SetTechnique(mhTech));
UINT numPasses = 0;
HR(mFX->Begin(&numPasses, 0));
HR(mFX->BeginPass(0));
drawObject(mCastle, mCastleWorld);
// Use alpha test to block non leaf pixels from being rendered in the
// trees (i.e., use alpha mask).
HR(gd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, true));
HR(gd3dDevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL));
HR(gd3dDevice->SetRenderState(D3DRS_ALPHAREF, 200));
// Draw the trees: NUM_TREES/4 of each of the four types.
for(int i = 0; i < NUM_TREES; ++i)
{
if( i < NUM_TREES/4 )
drawObject(mTrees[0], mTreeWorlds[i]);
else if( i < 2*NUM_TREES/4 )
drawObject(mTrees[1], mTreeWorlds[i]);
else if( i < 3*NUM_TREES/4 )
drawObject(mTrees[2], mTreeWorlds[i]);
else
drawObject(mTrees[3], mTreeWorlds[i]);
}
HR(gd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, false));
HR(mFX->EndPass());
HR(mFX->End());
HR(mGrassFX->SetValue(mhGrassEyePosW, &gCamera->pos(), sizeof(D3DXVECTOR3)));
HR(mGrassFX->SetMatrix(mhGrassViewProj, &(gCamera->viewProj())));
HR(mGrassFX->SetFloat(mhGrassTime, mTime));
HR(mGrassFX->Begin(&numPasses, 0));
HR(mGrassFX->BeginPass(0));
// Draw to depth buffer only.
HR(mGrassMesh->DrawSubset(0));
HR(mGrassFX->EndPass());
HR(mGrassFX->End());
mTerrain->draw();
mWater->draw(); // draw alpha blended objects last.
mGfxStats->display();
HR(gd3dDevice->EndScene());
// Present the backbuffer.
HR(gd3dDevice->Present(0, 0, 0, 0));
}
void PropsDemo::buildFX()
{
// Create the generic Light & Tex FX from a .fx file.
ID3DXBuffer* errors = 0;
HR(D3DXCreateEffectFromFile(gd3dDevice, "DirLightTex.fx",
0, 0, 0, 0, &mFX, &errors));
if( errors )
MessageBox(0, (char*)errors->GetBufferPointer(), 0, 0);
// Obtain handles.
mhTech = mFX->GetTechniqueByName("DirLightTexTech");
mhWVP = mFX->GetParameterByName(0, "gWVP");
mhWorldInvTrans = mFX->GetParameterByName(0, "gWorldInvTrans");
mhMtrl = mFX->GetParameterByName(0, "gMtrl");
mhLight = mFX->GetParameterByName(0, "gLight");
mhEyePosW = mFX->GetParameterByName(0, "gEyePosW");
mhWorld = mFX->GetParameterByName(0, "gWorld");
mhTex = mFX->GetParameterByName(0, "gTex");
// Create the grass FX from a .fx file.
HR(D3DXCreateEffectFromFile(gd3dDevice, "grass.fx",
0, 0, 0, 0, &mGrassFX, &errors));
if( errors )
MessageBox(0, (char*)errors->GetBufferPointer(), 0, 0);
// Obtain handles.
mhGrassTech = mGrassFX->GetTechniqueByName("GrassTech");
mhGrassViewProj = mGrassFX->GetParameterByName(0, "gViewProj");
mhGrassTex = mGrassFX->GetParameterByName(0, "gTex");
mhGrassTime = mGrassFX->GetParameterByName(0, "gTime");
mhGrassEyePosW = mGrassFX->GetParameterByName(0, "gEyePosW");
mhGrassDirToSunW= mGrassFX->GetParameterByName(0, "gDirToSunW");
HR(mGrassFX->SetTechnique(mhGrassTech));
HR(mGrassFX->SetTexture(mhGrassTex, mGrassTex));
}
void PropsDemo::drawObject(Object3D& obj, const D3DXMATRIX& toWorld)
{
// Transform AABB into the world space.
AABB box;
obj.box.xform(toWorld, box);
// Only draw if AABB is visible.
if( gCamera->isVisible( box ) )
{
HR(mFX->SetMatrix(mhWVP, &(toWorld*gCamera->viewProj())));
D3DXMATRIX worldInvTrans;
D3DXMatrixInverse(&worldInvTrans, 0, &toWorld);
D3DXMatrixTranspose(&worldInvTrans, &worldInvTrans);
HR(mFX->SetMatrix(mhWorldInvTrans, &worldInvTrans));
HR(mFX->SetMatrix(mhWorld, &toWorld));
for(UINT j = 0; j < obj.mtrls.size(); ++j)
{
HR(mFX->SetValue(mhMtrl, &obj.mtrls[j], sizeof(Mtrl)));
// If there is a texture, then use.
if(obj.textures[j] != 0)
{
HR(mFX->SetTexture(mhTex, obj.textures[j]));
}
// But if not, then set a pure white texture. When the texture color
// is multiplied by the color from lighting, it is like multiplying by
// 1 and won't change the color from lighting.
else
{
HR(mFX->SetTexture(mhTex, mWhiteTex));
}
HR(mFX->CommitChanges());
HR(obj.mesh->DrawSubset(j));
}
}
}
void PropsDemo::buildCastle()
{
// Load the castle mesh.
D3DXMATRIX T, Ry;
LoadXFile(<castle.x>, &mCastle.mesh, mCastle.mtrls, mCastle.textures);
// Compute castle AABB.
VertexPNT* v = 0;
HR(mCastle.mesh->LockVertexBuffer(0, (void**)&v));
HR(D3DXComputeBoundingBox(&v->pos, mCastle.mesh->GetNumVertices(),
mCastle.mesh->GetNumBytesPerVertex(),
&mCastle.box.minPt, &mCastle.box.maxPt));
HR(mCastle.mesh->UnlockVertexBuffer());
// Manually set castle materials.
for(UINT i = 0; i < mCastle.mtrls.size(); ++i)
{
mCastle.mtrls[i].ambient = WHITE*0.5f;
mCastle.mtrls[i].diffuse = WHITE;
mCastle.mtrls[i].spec = WHITE*0.8f;
mCastle.mtrls[i].specPower = 28.0f;
}
// Build castle's world matrix.
D3DXMatrixRotationY(&Ry, D3DX_PI);
D3DXMatrixTranslation(&T, 8.0f, 35.0f, -80.0f);
mCastleWorld = Ry*T;
}
void PropsDemo::buildTrees()
{
// Load 4 unique meshes. To draw more than 4 trees, we just draw these
// 4 trees repeatedly, with different world matrices applied.
LoadXFile(<tree0.x>, &mTrees[0].mesh, mTrees[0].mtrls, mTrees[0].textures);
LoadXFile(<tree1.x>, &mTrees[1].mesh, mTrees[1].mtrls, mTrees[1].textures);
LoadXFile(<tree2.x>, &mTrees[2].mesh, mTrees[2].mtrls, mTrees[2].textures);
LoadXFile(<tree3.x>, &mTrees[3].mesh, mTrees[3].mtrls, mTrees[3].textures);
// Build tree bounding boxes.
for(int i = 0; i < 4; ++i)
{
VertexPNT* v = 0;
HR(mTrees[i].mesh->LockVertexBuffer(0, (void**)&v));
HR(D3DXComputeBoundingBox(&v->pos, mTrees[i].mesh->GetNumVertices(),
mTrees[i].mesh->GetNumBytesPerVertex(),
&mTrees[i].box.minPt, &mTrees[i].box.maxPt));
HR(mTrees[i].mesh->UnlockVertexBuffer());
}
// Build world matrices for NUM_TREES trees. To do this, we generate a
// random position on the terrain surface for each tree. In reality,
// this is not the best way to do it, as we'd like to have more control and
// manually place trees in the scene by an artist. Nevertheless, this is
// an easy way to get trees in the scene of our demo. To prevent trees
// from being placed on mountain peaks, or in the water, we can specify to
// only generate trees in an allowed height range. By inspecting the heightmap
// used in this demo, castlehm257.raw, the range [35, 50] seems to be a good
// one to generate trees in. Note that this method does not prevent trees from
// interpenetrating with one another and it does not prevent the trees from
// interpenetrating with the castle.
// Scale down a bit do we ignore the borders of the terrain as candidates.
int w = (int)(mTerrain->getWidth() * 0.8f);
int d = (int)(mTerrain->getDepth() * 0.8f);
D3DXMATRIX S, T;
for(int i = 0; i < NUM_TREES; ++i)
{
float x = (float)((rand() % w) - (w*0.5f));
float z = (float)((rand() % d) - (d*0.5f));
// Subtract off height to embed trunk in ground.
float y = mTerrain->getHeight(x, z) - 0.5f;
// Trees modeled to a different scale then ours, so scale them down to make sense.
// Also randomize the height a bit.
float treeScale = GetRandomFloat(0.15f, 0.25f);
// Build tree's world matrix.
D3DXMatrixTranslation(&T, x, y, z);
D3DXMatrixScaling(&S, treeScale, treeScale, treeScale);
mTreeWorlds[i] = S*T;
// Only generate trees in this height range. If the height
// is outside this range, generate a new random position and
// try again.
if(y < 35.0f || y > 50.0f)
--i; // We are trying again, so decrement back the index.
}
}
void PropsDemo::buildGrass()
{
D3DVERTEXELEMENT9 elems[MAX_FVF_DECL_SIZE];
UINT numElems = 0;
HR(GrassVertex::Decl->GetDeclaration(elems, &numElems));
HR(D3DXCreateMesh(NUM_GRASS_BLOCKS*2, NUM_GRASS_BLOCKS*4, D3DXMESH_MANAGED,
elems, gd3dDevice, &mGrassMesh));
GrassVertex* v = 0;
WORD* k = 0;
HR(mGrassMesh->LockVertexBuffer(0, (void**)&v));
HR(mGrassMesh->LockIndexBuffer(0, (void**)&k));
int indexOffset = 0;
// Scale down the region in which we generate grass.
int w = (int)(mTerrain->getWidth() * 0.15f);
int d = (int)(mTerrain->getDepth() * 0.15f);
// Randomly generate a grass block (three intersecting quads) around the
// terrain in the height range [35, 50] (similar to the trees).
for(int i = 0; i < NUM_GRASS_BLOCKS; ++i)
{
//============================================
// Construct vertices.
// Generate random position in region. Note that we also shift
// this region to place it in the world.
float x = (float)((rand() % w) - (w*0.5f)) - 30.0f;
float z = (float)((rand() % d) - (d*0.5f)) - 20.0f;
float y = mTerrain->getHeight(x, z);
// Only generate grass blocks in this height range. If the height
// is outside this range, generate a new random position and
// try again.
if(y < 37.0f || y > 40.0f)
{
--i; // We are trying again, so decrement back the index.
continue;
}
float sx = GetRandomFloat(0.75f, 1.25f);
float sy = GetRandomFloat(0.75f, 1.25f);
float sz = GetRandomFloat(0.75f, 1.25f);
D3DXVECTOR3 pos(x, y, z);
D3DXVECTOR3 scale(sx, sy, sz);
buildGrassFin(v, k, indexOffset, pos, scale);
v += 4;
k += 6;
}
HR(mGrassMesh->UnlockVertexBuffer());
HR(mGrassMesh->UnlockIndexBuffer());
// Fill in the attribute buffer (everything in subset 0)
DWORD* attributeBufferPtr = 0;
HR(mGrassMesh->LockAttributeBuffer(0, &attributeBufferPtr));
for(UINT i = 0; i < mGrassMesh->GetNumFaces(); ++i)
attributeBufferPtr[i] = 0;
HR(mGrassMesh->UnlockAttributeBuffer());
DWORD* adj = new DWORD[mGrassMesh->GetNumFaces()*3];
HR(mGrassMesh->GenerateAdjacency(EPSILON, adj));
HR(mGrassMesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT|D3DXMESHOPT_VERTEXCACHE,
adj, 0, 0, 0));
delete [] adj;
}
void PropsDemo::buildGrassFin(GrassVertex* v, WORD* k, int& indexOffset,
D3DXVECTOR3& worldPos, D3DXVECTOR3& scale)
{
// Only top vertices have non-zero amplitudes:
// The bottom vertices are fixed to the ground.
float amp = GetRandomFloat(0.5f, 1.0f);
v[0] = GrassVertex(D3DXVECTOR3(-1.0f,-0.5f, 0.0f), D3DXVECTOR2(0.0f, 1.0f), 0.0f);
v[1] = GrassVertex(D3DXVECTOR3(-1.0f, 0.5f, 0.0f), D3DXVECTOR2(0.0f, 0.0f), amp);
v[2] = GrassVertex(D3DXVECTOR3( 1.0f, 0.5f, 0.0f), D3DXVECTOR2(1.0f, 0.0f), amp);
v[3] = GrassVertex(D3DXVECTOR3( 1.0f,-0.5f, 0.0f), D3DXVECTOR2(1.0f, 1.0f), 0.0f);
// Set indices of fin.
k[0] = 0 + indexOffset;
k[1] = 1 + indexOffset;
k[2] = 2 + indexOffset;
k[3] = 0 + indexOffset;
k[4] = 2 + indexOffset;
k[5] = 3 + indexOffset;
// Offset the indices by four to have the indices index into
// the next four elements of the vertex buffer for the next fin.
indexOffset += 4;
// Scale the fins and randomize green color intensity.
for(int i = 0; i < 4; ++i)
{
v[i].pos.x *= scale.x;
v[i].pos.y *= scale.y;
v[i].pos.z *= scale.z;
// Generate random offset color (mostly green).
v[i].colorOffset = D3DXCOLOR(
GetRandomFloat(0.0f, 0.1f),
GetRandomFloat(0.0f, 0.2f),
GetRandomFloat(0.0f, 0.1f),
0.0f);
}
// Add offset so that the bottom of fin touches the ground
// when placed on terrain. Otherwise, the fin's center point
// will touch the ground and only half of the fin will show.
float heightOver2 = (v[1].pos.y - v[0].pos.y) / 2;
worldPos.y += heightOver2;
// Set world center position for the quad.
v[0].quadPos = worldPos;
v[1].quadPos = worldPos;
v[2].quadPos = worldPos;
v[3].quadPos = worldPos;
}
(C) Æliens
20/2/2008
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.