topical media & game development
graphic-directx-game-22-ShadowMap-Terrain.cpp / cpp
//=============================================================================
// Terrain.cpp by Frank Luna (C) 2004 All Rights Reserved.
//=============================================================================
include <Terrain.h>
include <Camera.h>
include <d3dUtil.h>
include <algorithm>
include <list>
Terrain::Terrain(UINT vertRows, UINT vertCols, float dx, float dz,
std::string heightmap, std::string tex0, std::string tex1,
std::string tex2, std::string blendMap, float heightScale,
float yOffset)
{
mVertRows = vertRows;
mVertCols = vertCols;
mDX = dx;
mDZ = dz;
mWidth = (mVertCols-1)*mDX;
mDepth = (mVertRows-1)*mDZ;
mHeightmap.loadRAW(vertRows, vertCols, heightmap, heightScale, yOffset);
HR(D3DXCreateTextureFromFile(gd3dDevice, tex0.c_str(), &mTex0));
HR(D3DXCreateTextureFromFile(gd3dDevice, tex1.c_str(), &mTex1));
HR(D3DXCreateTextureFromFile(gd3dDevice, tex2.c_str(), &mTex2));
HR(D3DXCreateTextureFromFile(gd3dDevice, blendMap.c_str(), &mBlendMap));
buildGeometry();
buildEffect();
}
Terrain::~Terrain()
{
for(UINT i = 0; i < mSubGrids.size(); ++i)
ReleaseCOM(mSubGrids[i].mesh);
ReleaseCOM(mFX);
ReleaseCOM(mTex0);
ReleaseCOM(mTex1);
ReleaseCOM(mTex2);
ReleaseCOM(mBlendMap);
}
DWORD Terrain::getNumTriangles()
{
return (DWORD)mSubGrids.size()*mSubGrids[0].mesh->GetNumFaces();
}
DWORD Terrain::getNumVertices()
{
return (DWORD)mSubGrids.size()*mSubGrids[0].mesh->GetNumVertices();
}
float Terrain::getWidth()
{
return mWidth;
}
float Terrain::getDepth()
{
return mDepth;
}
void Terrain::onLostDevice()
{
HR(mFX->OnLostDevice());
}
void Terrain::onResetDevice()
{
HR(mFX->OnResetDevice());
}
float Terrain::getHeight(float x, float z)
{
// Transform from terrain local space to "cell" space.
float c = (x + 0.5f*mWidth) / mDX;
float d = (z - 0.5f*mDepth) / -mDZ;
// Get the row and column we are in.
int row = (int)floorf(d);
int col = (int)floorf(c);
// Grab the heights of the cell we are in.
// A*--*B
// | /|
// |/ |
// C*--*D
float A = mHeightmap(row, col);
float B = mHeightmap(row, col+1);
float C = mHeightmap(row+1, col);
float D = mHeightmap(row+1, col+1);
// Where we are relative to the cell.
float s = c - (float)col;
float t = d - (float)row;
// If upper triangle ABC.
if(t < 1.0f - s)
{
float uy = B - A;
float vy = C - A;
return A + s*uy + t*vy;
}
else // lower triangle DCB.
{
float uy = C - D;
float vy = B - D;
return D + (1.0f-s)*uy + (1.0f-t)*vy;
}
}
void Terrain::setDirToSunW(const D3DXVECTOR3& d)
{
HR(mFX->SetValue(mhDirToSunW, &d, sizeof(D3DXVECTOR3)));
}
// Sort by distance from nearest to farthest from the camera. In this
// way, we draw objects in front to back order to reduce overdraw
// (i.e., depth test will prevent them from being processed further.
bool Terrain::SubGrid::operator<(const SubGrid& rhs) const
{
D3DXVECTOR3 d1 = box.center() - gCamera->pos();
D3DXVECTOR3 d2 = rhs.box.center() - gCamera->pos();
return D3DXVec3LengthSq(&d1) < D3DXVec3LengthSq(&d2);
}
void Terrain::draw()
{
// Frustum cull sub-grids.
std::list<SubGrid> visibleSubGrids;
for(UINT i = 0; i < mSubGrids.size(); ++i)
{
if( gCamera->isVisible(mSubGrids[i].box) )
visibleSubGrids.push_back(mSubGrids[i]);
}
// Sort front-to-back from camera.
visibleSubGrids.sort();
HR(mFX->SetMatrix(mhViewProj, &gCamera->viewProj()));
HR(mFX->SetTechnique(mhTech));
UINT numPasses = 0;
HR(mFX->Begin(&numPasses, 0));
HR(mFX->BeginPass(0));
for(std::list<SubGrid>::iterator iter = visibleSubGrids.begin(); iter != visibleSubGrids.end(); ++iter)
HR(iter->mesh->DrawSubset(0));
HR(mFX->EndPass());
HR(mFX->End());
}
void Terrain::buildGeometry()
{
//===============================================================
// Create one large mesh for the grid in system memory.
DWORD numTris = (mVertRows-1)*(mVertCols-1)*2;
DWORD numVerts = mVertRows*mVertCols;
ID3DXMesh* mesh = 0;
D3DVERTEXELEMENT9 elems[MAX_FVF_DECL_SIZE];
UINT numElems = 0;
HR(VertexPNT::Decl->GetDeclaration(elems, &numElems));
// Use Scratch pool since we are using this mesh purely for some CPU work,
// which will be used to create the sub-grids that the graphics card
// will actually draw.
HR(D3DXCreateMesh(numTris, numVerts,
D3DPOOL_SCRATCH|D3DXMESH_32BIT, elems, gd3dDevice, &mesh));
//===============================================================
// Write the grid vertices and triangles to the mesh.
VertexPNT* v = 0;
HR(mesh->LockVertexBuffer(0, (void**)&v));
std::vector<D3DXVECTOR3> verts;
std::vector<DWORD> indices;
GenTriGrid(mVertRows, mVertCols, mDX, mDZ, D3DXVECTOR3(0.0f, 0.0f, 0.0f), verts, indices);
float w = mWidth;
float d = mDepth;
for(UINT i = 0; i < mesh->GetNumVertices(); ++i)
{
// We store the grid vertices in a linear array, but we can
// convert the linear array index to an (r, c) matrix index.
int r = i / mVertCols;
int c = i % mVertCols;
v[i].pos = verts[i];
v[i].pos.y = mHeightmap(r, c);
v[i].tex0.x = (v[i].pos.x + (0.5f*w)) / w;
v[i].tex0.y = (v[i].pos.z - (0.5f*d)) / -d;
}
// Write triangle data so we can compute normals.
DWORD* indexBuffPtr = 0;
HR(mesh->LockIndexBuffer(0, (void**)&indexBuffPtr));
for(UINT i = 0; i < mesh->GetNumFaces(); ++i)
{
indexBuffPtr[i*3+0] = indices[i*3+0];
indexBuffPtr[i*3+1] = indices[i*3+1];
indexBuffPtr[i*3+2] = indices[i*3+2];
}
HR(mesh->UnlockIndexBuffer());
// Compute Vertex Normals.
HR(D3DXComputeNormals(mesh, 0));
//===============================================================
// Now break the grid up into subgrid meshes.
// Find out the number of subgrids we'll have. For example, if
// m = 513, n = 257, SUBGRID_VERT_ROWS = SUBGRID_VERT_COLS = 33,
// then subGridRows = 512/32 = 16 and sibGridCols = 256/32 = 8.
int subGridRows = (mVertRows-1) / (SubGrid::NUM_ROWS-1);
int subGridCols = (mVertCols-1) / (SubGrid::NUM_COLS-1);
for(int r = 0; r < subGridRows; ++r)
{
for(int c = 0; c < subGridCols; ++c)
{
// Rectangle that indicates (via matrix indices ij) the
// portion of grid vertices to use for this subgrid.
RECT R =
{
c * (SubGrid::NUM_COLS-1),
r * (SubGrid::NUM_ROWS-1),
(c+1) * (SubGrid::NUM_COLS-1),
(r+1) * (SubGrid::NUM_ROWS-1)
};
buildSubGridMesh(R, v);
}
}
HR(mesh->UnlockVertexBuffer());
ReleaseCOM(mesh); // Done with global mesh.
}
void Terrain::buildSubGridMesh(RECT& R, VertexPNT* gridVerts)
{
//===============================================================
// Get indices for subgrid (we don't use the verts here--the verts
// are given by the parameter gridVerts).
std::vector<D3DXVECTOR3> tempVerts;
std::vector<DWORD> tempIndices;
GenTriGrid(SubGrid::NUM_ROWS, SubGrid::NUM_COLS, mDX, mDZ,
D3DXVECTOR3(0.0f, 0.0f, 0.0f), tempVerts, tempIndices);
ID3DXMesh* subMesh = 0;
D3DVERTEXELEMENT9 elems[MAX_FVF_DECL_SIZE];
UINT numElems = 0;
HR(VertexPNT::Decl->GetDeclaration(elems, &numElems));
HR(D3DXCreateMesh(SubGrid::NUM_TRIS, SubGrid::NUM_VERTS,
D3DXMESH_MANAGED, elems, gd3dDevice, &subMesh));
//===============================================================
// Build Vertex Buffer. Copy rectangle of vertices from the
// grid into the subgrid structure.
VertexPNT* v = 0;
HR(subMesh->LockVertexBuffer(0, (void**)&v));
int k = 0;
for(int i = R.top; i <= R.bottom; ++i)
{
for(int j = R.left; j <= R.right; ++j)
{
v[k++] = gridVerts[i*mVertCols+j];
}
}
//===============================================================
// Compute the bounding box before unlocking the vertex buffer.
AABB bndBox;
HR(D3DXComputeBoundingBox((D3DXVECTOR3*)v, subMesh->GetNumVertices(),
sizeof(VertexPNT), &bndBox.minPt, &bndBox.maxPt));
HR(subMesh->UnlockVertexBuffer());
//===============================================================
// Build Index and Attribute Buffer.
WORD* indices = 0;
DWORD* attBuff = 0;
HR(subMesh->LockIndexBuffer(0, (void**)&indices));
HR(subMesh->LockAttributeBuffer(0, &attBuff));
for(int i = 0; i < SubGrid::NUM_TRIS; ++i)
{
indices[i*3+0] = (WORD)tempIndices[i*3+0];
indices[i*3+1] = (WORD)tempIndices[i*3+1];
indices[i*3+2] = (WORD)tempIndices[i*3+2];
attBuff[i] = 0; // All in subset 0.
}
HR(subMesh->UnlockIndexBuffer());
HR(subMesh->UnlockAttributeBuffer());
//===============================================================
// Optimize for the vertex cache and build attribute table.
DWORD* adj = new DWORD[subMesh->GetNumFaces()*3];
HR(subMesh->GenerateAdjacency(EPSILON, adj));
HR(subMesh->OptimizeInplace(D3DXMESHOPT_VERTEXCACHE|D3DXMESHOPT_ATTRSORT,
adj, 0, 0, 0));
delete[] adj;
//===============================================================
// Save the mesh and bounding box.
SubGrid g;
g.mesh = subMesh;
g.box = bndBox;
mSubGrids.push_back(g);
}
void Terrain::buildEffect()
{
ID3DXBuffer* errors = 0;
HR(D3DXCreateEffectFromFile(gd3dDevice, "Terrain.fx",
0, 0, D3DXSHADER_DEBUG, 0, &mFX, &errors));
if( errors )
MessageBox(0, (char*)errors->GetBufferPointer(), 0, 0);
mhTech = mFX->GetTechniqueByName("TerrainTech");
mhViewProj = mFX->GetParameterByName(0, "gViewProj");
mhDirToSunW = mFX->GetParameterByName(0, "gDirToSunW");
mhTex0 = mFX->GetParameterByName(0, "gTex0");
mhTex1 = mFX->GetParameterByName(0, "gTex1");
mhTex2 = mFX->GetParameterByName(0, "gTex2");
mhBlendMap = mFX->GetParameterByName(0, "gBlendMap");
HR(mFX->SetTexture(mhTex0, mTex0));
HR(mFX->SetTexture(mhTex1, mTex1));
HR(mFX->SetTexture(mhTex2, mTex2));
HR(mFX->SetTexture(mhBlendMap, mBlendMap));
}
(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.