/*
** Command & Conquer Generals(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: W3DTreeBuffer.cpp ////////////////////////////////////////////////
//-----------------------------------------------------------------------------
//
// Westwood Studios Pacific.
//
// Confidential Information
// Copyright (C) 2001 - All Rights Reserved
//
//-----------------------------------------------------------------------------
//
// Project: RTS3
//
// File name: W3DTreeBuffer.cpp
//
// Created: John Ahlquist, May 2001
//
// Desc: Draw buffer to handle all the trees in a scene.
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------
#include "W3DDevice/GameClient/W3DTreeBuffer.h"
#include
#include
#include
#include
#include "common/GlobalData.h"
#include "GameClient/ClientRandomValue.h"
#include "W3DDevice/GameClient/TerrainTex.h"
#include "W3DDevice/GameClient/HeightMap.h"
#include "W3DDevice/GameClient/W3DDynamicLight.h"
#include "WW3D2/Camera.h"
#include "WW3D2/DX8Wrapper.h"
#include "WW3D2/DX8Renderer.h"
#include "WW3D2/Mesh.h"
#include "WW3D2/MeshMdl.h"
//-----------------------------------------------------------------------------
// Private Data
//-----------------------------------------------------------------------------
// A W3D shader that does alpha, texturing, tests zbuffer, doesn't update zbuffer.
#define SC_ALPHA_DETAIL ( SHADE_CNST(ShaderClass::PASS_LEQUAL, ShaderClass::DEPTH_WRITE_DISABLE, ShaderClass::COLOR_WRITE_ENABLE, ShaderClass::SRCBLEND_SRC_ALPHA, \
ShaderClass::DSTBLEND_ONE_MINUS_SRC_ALPHA, ShaderClass::FOG_DISABLE, ShaderClass::GRADIENT_MODULATE, ShaderClass::SECONDARY_GRADIENT_DISABLE, ShaderClass::TEXTURING_ENABLE, \
ShaderClass::ALPHATEST_DISABLE, ShaderClass::CULL_MODE_DISABLE, \
ShaderClass::DETAILCOLOR_DISABLE, ShaderClass::DETAILALPHA_DISABLE) )
static ShaderClass detailAlphaShader(SC_ALPHA_DETAIL);
/*
#define SC_ALPHA_MIRROR ( SHADE_CNST(ShaderClass::PASS_LEQUAL, ShaderClass::DEPTH_WRITE_DISABLE, ShaderClass::COLOR_WRITE_ENABLE, ShaderClass::SRCBLEND_SRC_ALPHA, \
ShaderClass::DSTBLEND_ONE_MINUS_SRC_ALPHA, ShaderClass::FOG_DISABLE, ShaderClass::GRADIENT_MODULATE, ShaderClass::SECONDARY_GRADIENT_DISABLE, ShaderClass::TEXTURING_ENABLE, \
ShaderClass::DETAILCOLOR_DISABLE, ShaderClass::DETAILALPHA_DISABLE, ShaderClass::ALPHATEST_DISABLE, ShaderClass::CULL_MODE_DISABLE, \
ShaderClass::DETAILCOLOR_DISABLE, ShaderClass::DETAILALPHA_DISABLE) )
static ShaderClass mirrorAlphaShader(SC_ALPHA_DETAIL);
// ShaderClass::PASS_ALWAYS,
#define SC_ALPHA_2D ( SHADE_CNST(PASS_ALWAYS, DEPTH_WRITE_DISABLE, COLOR_WRITE_ENABLE, \
SRCBLEND_SRC_ALPHA, DSTBLEND_ONE_MINUS_SRC_ALPHA, FOG_DISABLE, GRADIENT_DISABLE, \
SECONDARY_GRADIENT_DISABLE, TEXTURING_ENABLE, DETAILCOLOR_DISABLE, DETAILALPHA_DISABLE, \
ALPHATEST_DISABLE, CULL_MODE_ENABLE, DETAILCOLOR_DISABLE, DETAILALPHA_DISABLE) )
ShaderClass ShaderClass::_PresetAlpha2DShader(SC_ALPHA_2D);
*/
//-----------------------------------------------------------------------------
// Private Functions
//-----------------------------------------------------------------------------
//=============================================================================
// W3DTreeBuffer::cull
//=============================================================================
/** Culls the trees, marking the visible flag. If a tree becomes visible, it sets
it's sortKey */
//=============================================================================
void W3DTreeBuffer::cull(CameraClass * camera)
{
Int curTree;
m_anythingChanged = false;
// Calulate the vector direction that the camera is looking at.
Matrix3D camera_matrix = camera->Get_Transform();
float zmod = -1;
float x = zmod * camera_matrix[0][2] ;
float y = zmod * camera_matrix[1][2] ;
float z = zmod * camera_matrix[2][2] ;
m_cameraLookAtVector.Set(x,y,z);
for (curTree=0; curTreeCull_Sphere(m_trees[curTree].bounds);
if (visible!=m_trees[curTree].visible) {
m_trees[curTree].visible=visible;
m_anythingChanged = true;
if (visible) {
doKey = true;
}
}
// Also calculate sort key if a tree is visible, and the view changed setting m_updateAllKeys to true.
if (doKey || (visible&&m_updateAllKeys)) {
// The sort key is essentially the distance of location in the direction of the
// camera look at.
m_trees[curTree].sortKey = Vector3::Dot_Product(m_trees[curTree].location, m_cameraLookAtVector);
}
}
m_updateAllKeys = false;
}
//=============================================================================
// W3DTreeBuffer::cullMirror
//=============================================================================
/** Culls the trees, marking the visible flag for the mirror view. Unlike cull(),
doesn't update anything except the visible flag. */
//=============================================================================
void W3DTreeBuffer::cullMirror(CameraClass * camera)
{
Int curTree;
m_anythingChanged = false;
for (curTree=0; curTreeCull_Sphere(m_trees[curTree].bounds);
m_trees[curTree].visible=visible;
}
}
//=============================================================================
// W3DTreeBuffer::sort
//=============================================================================
/** Sorts the trees. Does num_iterations of a bubble sort. This is good because
it ends immediately if the trees are already sorted (which is most of the time)
and will perform a fixed amount of work each frame until it becomes sorted. */
//=============================================================================
void W3DTreeBuffer::sort(Int numIterations)
{
// sort in descending order.
Int iter;
Bool swap = false;
for (iter = 0; iter m_trees[i].sortKey) {
TTree tmp = m_trees[cur];
m_trees[cur] = m_trees[i];
m_trees[i] = tmp;
swap = true;
}
cur = i;
}
}
if (!swap) {
return;
}
m_anythingChanged = true;
}
}
//=============================================================================
// W3DTreeBuffer::doLighting
//=============================================================================
/** Calculates the diffuse lighting as affected by dynamic lighting. */
//=============================================================================
Int W3DTreeBuffer::doLighting(Vector3 *loc, Real r, Real g, Real b, SphereClass &bounds, RefRenderObjListIterator *pDynamicLightsIterator)
{
if (pDynamicLightsIterator == NULL) {
return(REAL_TO_INT(b) | (REAL_TO_INT(g) << 8) | (REAL_TO_INT(r) << 16) | (255 << 24));
}
Real shadeR, shadeG, shadeB;
shadeR = r;
shadeG = g;
shadeB = b;
Bool didLight = false;
for (pDynamicLightsIterator->First(); !pDynamicLightsIterator->Is_Done(); pDynamicLightsIterator->Next())
{
W3DDynamicLight *pLight = (W3DDynamicLight*)pDynamicLightsIterator->Peek_Obj();
if (!pLight->isEnabled()) {
continue; // he is turned off.
}
if (CollisionMath::Overlap_Test(bounds, pLight->Get_Bounding_Sphere()) == CollisionMath::OUTSIDE) {
continue; // this tree is outside of the light's influence.
}
Vector3 lightDirection = *loc;
Real factor;
switch(pLight->Get_Type()) {
case LightClass::POINT:
case LightClass::SPOT: {
Vector3 lightLoc = pLight->Get_Position();
lightDirection -= lightLoc;
double range, midRange;
pLight->Get_Far_Attenuation_Range(midRange, range);
Real dist = lightDirection.Length();
if (dist >= range) continue;
if (midRange < 0.1) continue;
factor = 1.0f - (dist - midRange) / (range - midRange);
factor = WWMath::Clamp(factor,0.0f,1.0f);
}
break;
case LightClass::DIRECTIONAL:
pLight->Get_Spot_Direction(lightDirection);
factor = 1.0;
break;
};
Real shade = 0.5f * factor; // Assume adjustment for diffuse.
Vector3 vDiffuse;
pLight->Get_Diffuse(&vDiffuse);
Vector3 ambient;
pLight->Get_Ambient(&ambient);
if (shade > 1.0) shade = 1.0;
if(shade < 0.0f) shade = 0.0f;
shadeR += shade*vDiffuse.X;
shadeG += shade*vDiffuse.Y;
shadeB += shade*vDiffuse.Z;
shadeR += factor*ambient.X;
shadeG += factor*ambient.Y;
shadeB += factor*ambient.Z;
didLight = true;
}
if (!didLight) {
return(REAL_TO_INT(b) | (REAL_TO_INT(g) << 8) | (REAL_TO_INT(r) << 16) | (255 << 24));
}
if (shadeR > 1.0) shadeR = 1.0;
if(shadeR < 0.0f) shadeR = 0.0f;
if (shadeG > 1.0) shadeG = 1.0;
if(shadeG < 0.0f) shadeG = 0.0f;
if (shadeB > 1.0) shadeB = 1.0;
if(shadeB < 0.0f) shadeB = 0.0f;
shadeR*=255.0f;
shadeG*=255.0f;
shadeB*=255.0f;
return(REAL_TO_INT(shadeB) | (REAL_TO_INT(shadeG) << 8) | (REAL_TO_INT(shadeR) << 16) | (255 << 24));
}
//=============================================================================
// W3DTreeBuffer::loadTreesInVertexAndIndexBuffers
//=============================================================================
/** Loads the trees into the vertex buffer for drawing. */
//=============================================================================
void W3DTreeBuffer::loadTreesInVertexAndIndexBuffers(RefRenderObjListIterator *pDynamicLightsIterator)
{
if (!m_indexTree || !m_vertexTree || !m_initialized) {
return;
}
if (!m_anythingChanged) {
//return;
}
m_curNumTreeVertices = 0;
m_curNumTreeIndices = 0;
VertexFormatXYZDUV1 *vb;
UnsignedShort *ib;
// Lock the buffers.
DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_indexTree);
DX8VertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexTree);
vb=(VertexFormatXYZDUV1*)lockVtxBuffer.Get_Vertex_Array();
ib = lockIdxBuffer.Get_Index_Array();
// Add to the index buffer & vertex buffer.
Vector2 lookAtVector(m_cameraLookAtVector.X, m_cameraLookAtVector.Y);
lookAtVector.Normalize();
// We draw from back to front, so we put the indexes in the buffer
// from back to front.
UnsignedShort *curIb = ib+MAX_TREE_INDEX;
VertexFormatXYZDUV1 *curVb = vb;
Int curTree;
// Calculate a static lighting value to use for all the trees.
Real shadeR, shadeG, shadeB;
shadeR = TheGlobalData->m_terrainAmbient[0].red;
shadeG = TheGlobalData->m_terrainAmbient[0].green;
shadeB = TheGlobalData->m_terrainAmbient[0].blue;
shadeR += TheGlobalData->m_terrainDiffuse[0].red;
shadeG += TheGlobalData->m_terrainDiffuse[0].green;
shadeB += TheGlobalData->m_terrainDiffuse[0].blue;
if (shadeR>1.0f) shadeR=1.0f;
if (shadeG>1.0f) shadeG=1.0f;
if (shadeB>1.0f) shadeB=1.0f;
shadeR*=255.0f;
shadeG*=255.0f;
shadeB*=255.0f;
for (curTree=0; curTreeFirst(); !pDynamicLightsIterator->Is_Done(); pDynamicLightsIterator->Next())
{
W3DDynamicLight *pLight = (W3DDynamicLight*)pDynamicLightsIterator->Peek_Obj();
if (!pLight->isEnabled()) {
continue; // he is turned off.
}
if (CollisionMath::Overlap_Test(m_trees[curTree].bounds, pLight->Get_Bounding_Sphere()) == CollisionMath::OUTSIDE) {
continue; // this tree is outside of the light's influence.
}
doVertexLighting = true;
}
#endif
Int diffuse = 0;
if (!doVertexLighting) {
diffuse = doLighting(&loc, shadeR, shadeG, shadeB, m_trees[curTree].bounds, NULL);
}
Real typeOffset = type*0.5;
Int startVertex = m_curNumTreeVertices;
Int i, j;
Int numVertex = m_typeMesh[type]->Peek_Model()->Get_Vertex_Count();
Vector3 *pVert = m_typeMesh[type]->Peek_Model()->Get_Vertex_Array();
// If we happen to have too many trees, stop.
if (m_curNumTreeVertices+numVertex+2>= MAX_TREE_VERTEX) {
break;
}
Int numIndex = m_typeMesh[type]->Get_Model()->Get_Polygon_Count();
const Vector3i *pPoly = m_typeMesh[type]->Get_Model()->Get_Polygon_Array();
if (m_curNumTreeIndices+3*numIndex+6 >= MAX_TREE_INDEX) {
break;
}
const Vector2*uvs=m_typeMesh[type]->Get_Model()->Get_UV_Array_By_Index(0);
// If we are doing reduced resolution terrain, do reduced
// poly trees.
Bool doPanel = (TheGlobalData->m_useHalfHeightMap || TheGlobalData->m_stretchTerrain);
if (doPanel) {
if (m_trees[curTree].rotates) {
theSin = -lookAtVector.X;
theCos = lookAtVector.Y;
}
// panel start is index offset, there are 3 index per triangle.
if (m_trees[curTree].panelStart/3 + 2 > numIndex) {
continue; // not enought polygons for the offset. jba.
}
for (j=0; j<6; j++) {
i = ((Int *)pPoly)[j+m_trees[curTree].panelStart];
if (m_curNumTreeVertices >= MAX_TREE_VERTEX)
break;
// Update the uv values. The W3D models each have their own texture, and
// we use one texture with all images in one, so we have to change the uvs to
// match.
Real U, V;
if (type==SHRUB) {
// shrub texture is tucked in the corner
U = ((512-64)+uvs[i].U*64.0f)/512.0f;
V = ((256-64)+uvs[i].V*64.0f)/256.0f;
} else if (type==FENCE) {
U = uvs[i].U*0.5f;
V = 1.0f + uvs[i].V;
} else {
U = typeOffset+uvs[i].U*0.5f;
V = uvs[i].V;
}
curVb->u1 = U;
curVb->v1 = V/2.0;
Vector3 vLoc;
vLoc.X = pVert[i].X*scale*theCos - pVert[i].Z*scale*theSin;
vLoc.Y = pVert[i].Z*scale*theCos + pVert[i].X*scale*theSin;
vLoc.X += loc.X;
vLoc.Y += loc.Y;
vLoc.Z = loc.Z + pVert[i].Y*scale; // In W3D z is up, in 3dMax y is up.
curVb->x = vLoc.X;
curVb->y = vLoc.Y;
curVb->z = vLoc.Z;
if (doVertexLighting) {
curVb->diffuse = doLighting(&vLoc, shadeR, shadeG, shadeB, m_trees[curTree].bounds, pDynamicLightsIterator);
} else {
curVb->diffuse = diffuse;
}
curVb++;
m_curNumTreeVertices++;
}
for (i=0; i<6; i++) {
if (m_curNumTreeIndices+4 > MAX_TREE_INDEX)
break;
curIb--;
*curIb = startVertex + i;
m_curNumTreeIndices++;
}
} else {
for (i=0; i= MAX_TREE_VERTEX)
break;
// Update the uv values. The W3D models each have their own texture, and
// we use one texture with all images in one, so we have to change the uvs to
// match.
Real U, V;
if (type==SHRUB) {
// shrub texture is tucked in the corner
U = ((512-64)+uvs[i].U*64.0f)/512.0f;
V = ((256-64)+uvs[i].V*64.0f)/256.0f;
} else if (type==FENCE) {
U = uvs[i].U*0.5f;
V = 1.0f + uvs[i].V;
} else {
U = typeOffset+uvs[i].U*0.5f;
V = uvs[i].V;
}
curVb->u1 = U;
curVb->v1 = V/2.0;
Vector3 vLoc;
vLoc.X = pVert[i].X*scale*theCos - pVert[i].Z*scale*theSin;
vLoc.Y = pVert[i].Z*scale*theCos + pVert[i].X*scale*theSin;
vLoc.X += loc.X;
vLoc.Y += loc.Y;
vLoc.Z = loc.Z + pVert[i].Y*scale; // In W3D z is up, in 3dMax y is up.
curVb->x = vLoc.X;
curVb->y = vLoc.Y;
curVb->z = vLoc.Z;
if (doVertexLighting) {
curVb->diffuse = doLighting(&vLoc, shadeR, shadeG, shadeB, m_trees[curTree].bounds, pDynamicLightsIterator);
} else {
curVb->diffuse = diffuse;
}
curVb++;
m_curNumTreeVertices++;
}
for (i=0; i MAX_TREE_INDEX)
break;
curIb-=3;
*curIb++ = startVertex + pPoly[i].I;
*curIb++ = startVertex + pPoly[i].J;
*curIb++ = startVertex + pPoly[i].K;
curIb-=3;
m_curNumTreeIndices+=3;
}
}
}
m_curTreeIndexOffset = curIb - ib;
}
//-----------------------------------------------------------------------------
// Public Functions
//-----------------------------------------------------------------------------
//=============================================================================
// W3DTreeBuffer::~W3DTreeBuffer
//=============================================================================
/** Destructor. Releases w3d assets. */
//=============================================================================
W3DTreeBuffer::~W3DTreeBuffer(void)
{
freeTreeBuffers();
REF_PTR_RELEASE(m_treeTexture);
Int i;
for (i=0; iSet_U_Addr_Mode(TextureClass::TEXTURE_ADDRESS_CLAMP);
m_treeTexture->Set_V_Addr_Mode(TextureClass::TEXTURE_ADDRESS_CLAMP);
for (i=0; iCreate_Render_Obj("ALPINETREE.TREE 1" );
break;
case 1:
m_typeMesh[i] = (MeshClass*)WW3DAssetManager::Get_Instance()->Create_Render_Obj("DECIDUOUS.TREE 1" );
break;
case 2:
m_typeMesh[i] = (MeshClass*)WW3DAssetManager::Get_Instance()->Create_Render_Obj("SHRUB.TREE 1" );
break;
case 3:
m_typeMesh[i] = (MeshClass*)WW3DAssetManager::Get_Instance()->Create_Render_Obj("FENCE.PLANE09" );
break;
}
if (m_typeMesh[i] == NULL) continue;
Int numVertex = m_typeMesh[i]->Peek_Model()->Get_Vertex_Count();
Vector3 *pVert = m_typeMesh[i]->Peek_Model()->Get_Vertex_Array();
const Matrix3D xfm = m_typeMesh[i]->Get_Transform();
SphereClass bounds(pVert, numVertex);
// Model is in y up format, so swap y and z.
Real tmp;
tmp = bounds.Center.Y;
bounds.Center.Y = bounds.Center.Z;
bounds.Center.Z = tmp;
m_typeBounds[i] = bounds;
}
if (m_typeMesh[0] == NULL) {
//DEBUG_LOG("!!!!!!!!!!!!*************** W3DTreeBuffer failed to initialize.\n");
return; // didn't initialize.
}
m_initialized = true;
}
//=============================================================================
// W3DTreeBuffer::freeTreeBuffers
//=============================================================================
/** Frees the index and vertex buffers. */
//=============================================================================
void W3DTreeBuffer::freeTreeBuffers(void)
{
REF_PTR_RELEASE(m_vertexTree);
REF_PTR_RELEASE(m_indexTree);
}
//=============================================================================
// W3DTreeBuffer::allocateTreeBuffers
//=============================================================================
/** Allocates the index and vertex buffers. */
//=============================================================================
void W3DTreeBuffer::allocateTreeBuffers(void)
{
m_vertexTree=NEW_REF(DX8VertexBufferClass,(DX8_FVF_XYZDUV1,MAX_TREE_VERTEX+4,DX8VertexBufferClass::USAGE_DYNAMIC));
m_indexTree=NEW_REF(DX8IndexBufferClass,(2*MAX_TREE_INDEX+4, DX8IndexBufferClass::USAGE_DYNAMIC));
m_curNumTreeVertices=0;
m_curNumTreeIndices=0;
}
//=============================================================================
// W3DTreeBuffer::clearAllTrees
//=============================================================================
/** Removes all trees. */
//=============================================================================
void W3DTreeBuffer::clearAllTrees(void)
{
m_numTrees=0;
}
//=============================================================================
// W3DTreeBuffer::addTree
//=============================================================================
/** Adds a tree. Name is the W3D model name, supported models are
ALPINE, DECIDUOUS and SHRUB. */
//=============================================================================
void W3DTreeBuffer::addTree(Coord3D loc, Real scale, Real angle, AsciiString name, Bool mirrorVisible)
{
if (m_numTrees >= MAX_TREES) {
return;
}
if (!m_initialized) {
return;
}
TTreeType treeType = ALPINE_TREE;
if (!name.compare("DECIDUOUS")) {
treeType = DECIDUOUS_TREE;
} else if (!name.compare("SHRUB")) {
treeType = SHRUB;
} else if (!name.compare("FENCE")) {
treeType = FENCE;
}
m_trees[m_numTrees].panelStart = 0;
Real randomScale = GameClientRandomValueReal( 0.7f, 1.3f );
if (treeType == FENCE) {
// Fences don't randomly scale & orient
m_trees[m_numTrees].sin = WWMath::Sin(angle);
m_trees[m_numTrees].scale = scale;
m_trees[m_numTrees].cos = WWMath::Cos(angle);
m_trees[m_numTrees].rotates = false;
m_trees[m_numTrees].panelStart = 48;
} else {
// Randomizes the scale and orientation of trees.
m_trees[m_numTrees].sin = GameClientRandomValueReal( -1.0f, 1.0f );
m_trees[m_numTrees].scale = scale*randomScale;
m_trees[m_numTrees].cos = WWMath::Sqrt(1.0 - m_trees[m_numTrees].sin*m_trees[m_numTrees].sin);
m_trees[m_numTrees].rotates = true;
}
m_trees[m_numTrees].location = Vector3(loc.x, loc.y, loc.z);
m_trees[m_numTrees].treeType = treeType;
// Translate the bounding sphere of the model.
m_trees[m_numTrees].bounds = m_typeBounds[treeType];
m_trees[m_numTrees].bounds.Center *= scale;
m_trees[m_numTrees].bounds.Radius *= scale;
m_trees[m_numTrees].bounds.Center += m_trees[m_numTrees].location;
// Initially set it invisible. cull will update it's visiblity flag.
m_trees[m_numTrees].visible = false;
m_trees[m_numTrees].mirrorVisible = mirrorVisible;
m_numTrees++;
}
//=============================================================================
// W3DTreeBuffer::drawTrees
//=============================================================================
/** Draws the trees. Uses camera to cull. */
//=============================================================================
void W3DTreeBuffer::drawTrees(CameraClass * camera, RefRenderObjListIterator *pDynamicLightsIterator)
{
if (!m_isTerrainPass) {
return;
}
m_isTerrainPass = false;
if (ShaderClass::Is_Backface_Culling_Inverted()) {
// Mirror inverts backface culling.
cullMirror(camera);
} else {
// Normal draw.
cull(camera);
// Only sort once per frame.
sort(SORT_ITERATIONS_PER_FRAME);
}
loadTreesInVertexAndIndexBuffers(pDynamicLightsIterator);
if (m_curNumTreeIndices == 0) {
return;
}
// Setup the vertex buffer, shader & texture.
DX8Wrapper::Set_Index_Buffer(m_indexTree,0);
DX8Wrapper::Set_Vertex_Buffer(m_vertexTree);
DX8Wrapper::Set_Shader(detailAlphaShader);
DX8Wrapper::Set_Texture(0,m_treeTexture);
// Draw all the trees.
DX8Wrapper::Draw_Triangles( m_curTreeIndexOffset, m_curNumTreeIndices/3, 0, m_curNumTreeVertices);
}