/* ** 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); }