/* ** Command & Conquer Generals Zero Hour(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: W3DTerrainBackground.cpp //////////////////////////////////////////////// //----------------------------------------------------------------------------- // // EA Pacific. // // Confidential Information // Copyright (C) 2003 - All Rights Reserved // //----------------------------------------------------------------------------- // // Project: RTS3 // // File name: W3DTerrainBackground.cpp // // Created: John Ahlquist, March 2003 // // Desc: Draw buffer to handle backup terrain at lower res. // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Includes //----------------------------------------------------------------------------- #include "W3DDevice/GameClient/W3DTerrainBackground.h" #include #include #include #include #include "common/GlobalData.h" #include "GameClient/View.h" #include "W3DDevice/GameClient/TerrainTex.h" #include "W3DDevice/GameClient/HeightMap.h" #include "WW3D2/DX8Wrapper.h" #include "WW3D2/DX8Renderer.h" #include "WW3D2/Camera.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif //----------------------------------------------------------------------------- // Private Data //----------------------------------------------------------------------------- // A W3D shader that does alpha, texturing, tests zbuffer, doesn't update zbuffer. #define SC_DETAIL ( SHADE_CNST(ShaderClass::PASS_LEQUAL, ShaderClass::DEPTH_WRITE_ENABLE, ShaderClass::COLOR_WRITE_ENABLE, ShaderClass::SRCBLEND_ONE, \ ShaderClass::DSTBLEND_ZERO, 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 detailShader(SC_DETAIL); const Int PIXELS_PER_GRID = 8; // default tex resolution allocated for each tile. jba. [3/24/2003] //----------------------------------------------------------------------------- // Private Functions //----------------------------------------------------------------------------- //============================================================================= // W3DTerrainBackground::loadTerrainInVertexAndIndexBuffers //============================================================================= /** Loads the terrain into the vertex buffer for drawing. */ //============================================================================= void W3DTerrainBackground::setFlip(WorldHeightMap *htMap) { if (m_map==NULL) return; if (htMap) { REF_PTR_SET(m_map, htMap); } if (!m_initialized) { return; } setFlipRecursive(0, 0, m_width); } const Int STEP=4; //============================================================================= // W3DTerrainBackground::doPartialUpdate //============================================================================= /** Updates a partial block of vertices from [x0,y0 to x1,y1] The coordinates in partialRange are map cell coordinates, relative to the entire map. The vertex coordinates and texture coordinates, as well as static lighting are updated. */ void W3DTerrainBackground::doPartialUpdate(const IRegion2D &partialRange, WorldHeightMap *htMap, Bool doTextures ) { if (m_map==NULL) return; if (htMap) { REF_PTR_SET(m_map, htMap); } if (!m_initialized) { return; } doTesselatedUpdate(partialRange, htMap, doTextures); return; Int requiredVertexSize = (m_width+1) * (m_width+1) + 6; if (m_vertexTerrainSizegetXExtent()-1; Int limitY = m_map->getYExtent()-1; if (maxX>limitX) maxX = limitX; if (maxY>limitY) maxY = limitY; if (partialRange.lo.x > maxX) return; if (partialRange.lo.y > maxY) return; if (partialRange.hi.x < minX) return; if (partialRange.hi.y < minY) return; m_curNumTerrainVertices = 0; //m_curNumTerrainIndices = 0; VertexFormatXYZDUV2 *vb; UnsignedShort *ib; // Lock the buffer. DX8VertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexTerrain); vb=(VertexFormatXYZDUV2*)lockVtxBuffer.Get_Vertex_Array(); // Add to the vertex buffer. VertexFormatXYZDUV2 *curVb = vb; MinMaxAABoxClass bounds; bounds.Init_Empty(); Int i, j; for (j=minY; j<=maxY; j+=STEP) { for (i=minX; i<=maxX; i+=STEP) { if (m_curNumTerrainVertices >= m_vertexTerrainSize) return; curVb->diffuse = (0<<24)|TheTerrainRenderObject->getStaticDiffuse(i,j); Vector3 pos; pos.Z = ((float)m_map->getHeight(i,j)*MAP_HEIGHT_SCALE); pos.X = (i)*MAP_XY_FACTOR - m_map->getBorderSizeInline()*MAP_XY_FACTOR; pos.Y = (j)*MAP_XY_FACTOR - m_map->getBorderSizeInline()*MAP_XY_FACTOR; curVb->u1 = (float)(i-minX)/(float)(m_width); curVb->v1 = 1.0f - (float)(j-minY)/(float)(m_width); curVb->x = pos.X; curVb->y = pos.Y; curVb->z = pos.Z; curVb++; m_curNumTerrainVertices++; bounds.Add_Point(pos); } } m_bounds.Init(bounds); if (m_terrainTexture == NULL || doTextures) { REF_PTR_RELEASE(m_terrainTexture); REF_PTR_RELEASE(m_terrainTexture2X); REF_PTR_RELEASE(m_terrainTexture4X); m_terrainTexture = m_map->getFlatTexture(m_xOrigin, m_yOrigin, m_width, PIXELS_PER_GRID); // DEBUG ONLY. jba. m_terrainTexture = (TerrainTextureClass *)NEW_REF(TextureClass, ("TBBib.tga")); m_terrainTexture->Get_Filter().Set_U_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); m_terrainTexture->Get_Filter().Set_V_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); } if (m_curNumTerrainIndices == 0) { // Only do the index buffer if it has never been done. Index values don't change. jba. DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_indexTerrain); ib = lockIdxBuffer.Get_Index_Array(); UnsignedShort *curIb = ib; Int yOffset = ((maxX - minX)/STEP+1); Int width = yOffset; Int height = (maxY - minY)/STEP; *curIb++ = width-1; m_curNumTerrainIndices++; for (j=0; j=0; i--) { if (m_curNumTerrainIndices+2 > m_indexTerrainSize) return; *curIb++ = j*yOffset + i; *curIb++ = j*yOffset + i+yOffset; m_curNumTerrainIndices+=2; } j++; if (j m_indexTerrainSize) return; *curIb++ = j*yOffset + i; *curIb++ = j*yOffset + i+yOffset; m_curNumTerrainIndices+=2; } } } } } //============================================================================= // W3DTerrainBackground::fillVBRecursive //============================================================================= /** Fills in vertex & index buffers. */ Bool W3DTerrainBackground::advanceLeft(ICoord2D &left, Int xOffset, Int yOffset, Int width) { while (left.y < yOffset+width) { left.y++; if (m_map->getFlipState(left.x+m_xOrigin, left.y+m_yOrigin)) { return true; } } while (left.x < xOffset+width-1) { left.x++; if (m_map->getFlipState(left.x+m_xOrigin, left.y+m_yOrigin)) { return true; } } return false; } //============================================================================= // W3DTerrainBackground::fillVBRecursive //============================================================================= /** Fills in vertex & index buffers. */ Bool W3DTerrainBackground::advanceRight(ICoord2D &right, Int xOffset, Int yOffset, Int width) { while (right.x < xOffset+width) { right.x++; if (m_map->getFlipState(right.x+m_xOrigin, right.y+m_yOrigin)) { return true; } } while (right.y < yOffset+width-1) { right.y++; if (m_map->getFlipState(right.x+m_xOrigin, right.y+m_yOrigin)) { return true; } } return false; } //============================================================================= // W3DTerrainBackground::fillVBRecursive //============================================================================= /** Fills in vertex & index buffers. */ void W3DTerrainBackground::fillVBRecursive(UnsignedShort *ib, Int xOffset, Int yOffset, Int width, UnsignedShort *ndx, Int &curIndex) { Int bottomLeftNdx = ndx[xOffset+yOffset*(m_width+1)]; Int topRightNdx = ndx[xOffset+width + (yOffset+width)*(m_width+1)]; Int limitX = m_map->getXExtent()-1; Int limitY = m_map->getYExtent()-1; Int i, j; Bool match = true; Int minX = m_xOrigin+xOffset; Int minY = m_yOrigin+yOffset; Int cornerHeight = m_map->getHeight(minX, minY); for (i=0; i<=width; i++) { for (j=0; j<=width; j++) { Int k = minX+i; k = kgetHeight(k, l)) { match = false; break; } } } if (width==1) { match = true; } if (match) { UnsignedShort prevNdxLeft; UnsignedShort prevNdxRight; ICoord2D left; left.x = xOffset; left.y = yOffset; ICoord2D right; right.x = xOffset; right.y = yOffset; advanceLeft(left, xOffset, yOffset, width); advanceRight(right, xOffset, yOffset, width); if (ib) { ib[curIndex] = bottomLeftNdx; } curIndex++; prevNdxRight = ndx[right.x+right.y*(m_width+1)]; if (ib) { ib[curIndex] = prevNdxRight; } curIndex++; prevNdxLeft = ndx[left.x+left.y*(m_width+1)]; if (ib) { ib[curIndex] = prevNdxLeft; } curIndex++; Bool didLeft = true; Bool didRight = true; while (didLeft || didRight) { didLeft = advanceLeft(left, xOffset, yOffset, width); if (didLeft) { if (ib) { ib[curIndex] = prevNdxLeft; } curIndex++; if (ib) { ib[curIndex] = prevNdxRight; } curIndex++; prevNdxLeft = ndx[left.x+left.y*(m_width+1)]; if (ib) { ib[curIndex] = prevNdxLeft; } curIndex++; } didRight = advanceRight(right, xOffset, yOffset, width); if (didRight) { if (ib) { ib[curIndex] = prevNdxLeft; } curIndex++; if (ib) { ib[curIndex] = prevNdxRight; } curIndex++; prevNdxRight = ndx[right.x+right.y*(m_width+1)]; if (ib) { ib[curIndex] = prevNdxRight; } curIndex++; } } if (ib) { ib[curIndex] = prevNdxLeft; } curIndex++; if (ib) { ib[curIndex] = prevNdxRight; } curIndex++; if (ib) { ib[curIndex] = topRightNdx; } curIndex++; return; } Int halfWidth = width/2; fillVBRecursive(ib, xOffset, yOffset, halfWidth, ndx, curIndex); fillVBRecursive(ib, xOffset, yOffset+halfWidth, halfWidth, ndx, curIndex); fillVBRecursive(ib, xOffset+halfWidth, yOffset, halfWidth, ndx, curIndex); fillVBRecursive(ib, xOffset+halfWidth, yOffset+halfWidth, halfWidth, ndx, curIndex); } //============================================================================= // W3DTerrainBackground::fillVBRecursive //============================================================================= /** Fills in vertex & index buffers. */ void W3DTerrainBackground::setFlipRecursive(Int xOffset, Int yOffset, Int width) { Int limitX = m_map->getXExtent()-1; Int limitY = m_map->getYExtent()-1; Int i, j; Bool match = true; Int minX = m_xOrigin+xOffset; Int minY = m_yOrigin+yOffset; Int cornerHeight = m_map->getHeight(minX, minY); for (i=0; i<=width; i++) { for (j=0; j<=width; j++) { Int k = minX+i; k = kgetHeight(k, l)) { match = false; break; } } } if (width==1) { match = true; } if (match) { m_map->setFlipState(minX, minY, true); m_map->setFlipState(minX+width, minY, true); m_map->setFlipState(minX+width, minY+width, true); m_map->setFlipState(minX, minY+width, true); return; } Int halfWidth = width/2; setFlipRecursive(xOffset, yOffset, halfWidth); setFlipRecursive(xOffset, yOffset+halfWidth, halfWidth); setFlipRecursive(xOffset+halfWidth, yOffset, halfWidth); setFlipRecursive(xOffset+halfWidth, yOffset+halfWidth, halfWidth); } //============================================================================= // W3DTerrainBackground::doTesselatedUpdate //============================================================================= /** Updates a partial block of vertices from [x0,y0 to x1,y1] The coordinates in partialRange are map cell coordinates, relative to the entire map. The vertex coordinates and texture coordinates, as well as static lighting are updated. */ void W3DTerrainBackground::doTesselatedUpdate(const IRegion2D &partialRange, WorldHeightMap *htMap, Bool doTextures ) { if (m_map==NULL) return; if (htMap) { REF_PTR_SET(m_map, htMap); } if (!m_initialized) { return; } Int minX = m_xOrigin; Int minY = m_yOrigin; Int maxX = m_xOrigin + m_width; Int maxY = m_yOrigin + m_width; Int limitX = m_map->getXExtent()-1; Int limitY = m_map->getYExtent()-1; if (partialRange.lo.x > maxX) return; if (partialRange.lo.y > maxY) return; if (partialRange.hi.x < minX) return; if (partialRange.hi.y < minY) return; setFlip(htMap); Int count = (m_width+1)*(m_width+1); UnsignedShort *ndx = new UnsignedShort[count]; Int requiredVertex = 0; Int i, j; for (j=minY; j<=maxY; j++) { for (i=minX; i<=maxX; i++) { Int ndxNdx = i-minX + (m_width+1)*(j-minY); DEBUG_ASSERTCRASH(ndxNdxgetFlipState(i, j)) { requiredVertex++; } } } if (m_vertexTerrainSizegetFlipState(i, j)) { curVb->diffuse = (0<<24)|TheTerrainRenderObject->getStaticDiffuse(i,j); Vector3 pos; Int k = igetHeight(k,l)*MAP_HEIGHT_SCALE); pos.X = (i)*MAP_XY_FACTOR - m_map->getBorderSizeInline()*MAP_XY_FACTOR; pos.Y = (j)*MAP_XY_FACTOR - m_map->getBorderSizeInline()*MAP_XY_FACTOR; curVb->u1 = (float)(i-minX)/(float)(m_width); curVb->v1 = 1.0f - (float)(j-minY)/(float)(m_width); curVb->x = pos.X; curVb->y = pos.Y; curVb->z = pos.Z; curVb++; Int ndxNdx = i-minX + (m_width+1)*(j-minY); DEBUG_ASSERTCRASH(ndxNdxgetHeight(k,l)*MAP_HEIGHT_SCALE); pos.X = (i)*MAP_XY_FACTOR - m_map->getBorderSizeInline()*MAP_XY_FACTOR; pos.Y = (j)*MAP_XY_FACTOR - m_map->getBorderSizeInline()*MAP_XY_FACTOR; bounds.Add_Point(pos); } } m_bounds.Init(bounds); if (m_terrainTexture == NULL || doTextures) { REF_PTR_RELEASE(m_terrainTexture); REF_PTR_RELEASE(m_terrainTexture2X); REF_PTR_RELEASE(m_terrainTexture4X); m_terrainTexture = m_map->getFlatTexture(m_xOrigin, m_yOrigin, m_width, PIXELS_PER_GRID); // DEBUG ONLY. jba. m_terrainTexture = (TerrainTextureClass *)NEW_REF(TextureClass, ("TBBib.tga")); m_terrainTexture->Get_Filter().Set_U_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); m_terrainTexture->Get_Filter().Set_V_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); } } //----------------------------------------------------------------------------- // Public Functions //----------------------------------------------------------------------------- //============================================================================= // W3DTerrainBackground::~W3DTerrainBackground //============================================================================= /** Destructor. Releases w3d assets. */ //============================================================================= W3DTerrainBackground::~W3DTerrainBackground(void) { freeTerrainBuffers(); REF_PTR_RELEASE(m_terrainTexture); REF_PTR_RELEASE(m_terrainTexture2X); REF_PTR_RELEASE(m_terrainTexture4X); } //============================================================================= // W3DTerrainBackground::W3DTerrainBackground //============================================================================= /** Constructor. Sets m_initialized to true if it finds the w3d models it needs for the bibs. */ //============================================================================= W3DTerrainBackground::W3DTerrainBackground(void): m_vertexTerrain(NULL), m_vertexTerrainSize(0), m_initialized(FALSE), m_indexTerrain(NULL), m_indexTerrainSize(0), m_terrainTexture(NULL), m_terrainTexture2X(NULL), m_terrainTexture4X(NULL), m_cullStatus(CULL_STATUS_UNKNOWN), m_texMultiplier(TEX1X) { } //============================================================================= // W3DTerrainBackground::freeTerrainBuffers //============================================================================= /** Frees the index and vertex buffers. */ //============================================================================= void W3DTerrainBackground::freeTerrainBuffers(void) { REF_PTR_RELEASE(m_vertexTerrain); REF_PTR_RELEASE(m_indexTerrain); m_curNumTerrainVertices=0; m_curNumTerrainIndices=0; m_initialized = false; REF_PTR_RELEASE(m_map); REF_PTR_RELEASE(m_map); } //============================================================================= // W3DTerrainBackground::allocateTerrainBuffers //============================================================================= /** Allocates the index and vertex buffers. */ //============================================================================= void W3DTerrainBackground::allocateTerrainBuffers(WorldHeightMap *htMap, Int xOrigin, Int yOrigin, Int width) { if (htMap==NULL) return; freeTerrainBuffers(); // in case already allocated. jba [3/24/2003] m_curNumTerrainVertices=0; m_curNumTerrainIndices=0; m_xOrigin = xOrigin; m_yOrigin = yOrigin; m_width = width; m_initialized = true; REF_PTR_SET(m_map, htMap); } //============================================================================= // W3DTerrainBackground::updateCenter //============================================================================= /** Updates the culling status. */ //============================================================================= void W3DTerrainBackground::updateCenter(CameraClass *camera) { if (camera->Cull_Box(m_bounds)) { m_cullStatus = CULL_STATUS_INVISIBLE; } else { m_cullStatus = CULL_STATUS_VISIBLE; } if (m_cullStatus==CULL_STATUS_INVISIBLE) { REF_PTR_RELEASE(m_terrainTexture2X); REF_PTR_RELEASE(m_terrainTexture4X); m_texMultiplier = TEX1X; return; } Vector3 cameraPos = camera->Get_Position(); const Real mipDistance = 310; const Real mipSlop = 40; const Real mip4xDistanceSqr = sqr(mipDistance+mipSlop); const Real mip2xDistanceSqr = sqr(2*mipDistance+mipSlop); const Real mipLODDistanceSqr = sqr(4*mipDistance+mipSlop); Real minDistSqr = 2*mip2xDistanceSqr; Int i, j, k; for (i=-1; i<2; i++) { for (j=-1; j<2; j++) { for (k=-1; k<2; k++) { Vector3 corner = m_bounds.Center; corner.X += m_bounds.Extent.X * i; corner.Y += m_bounds.Extent.Y * j; corner.Z += m_bounds.Extent.Z * k; Real distSqr = (cameraPos-corner).Length2(); if (distSqrmipLODDistanceSqr) { LOD = 1; } m_terrainTexture->setLOD(LOD); } } //============================================================================= // W3DTerrainBackground::updateCenter //============================================================================= /** Updates the culling status. */ //============================================================================= void W3DTerrainBackground::updateTexture(void) { if (m_cullStatus==CULL_STATUS_INVISIBLE) { REF_PTR_RELEASE(m_terrainTexture2X); REF_PTR_RELEASE(m_terrainTexture4X); return; } if (m_texMultiplier == TEX4X) { REF_PTR_RELEASE(m_terrainTexture2X); if (m_terrainTexture4X == NULL) { m_terrainTexture4X = m_map->getFlatTexture(m_xOrigin, m_yOrigin, m_width, 4*PIXELS_PER_GRID); m_terrainTexture4X->Get_Filter().Set_U_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); m_terrainTexture4X->Get_Filter().Set_V_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); } } else if (m_texMultiplier == TEX2X) { REF_PTR_RELEASE(m_terrainTexture4X); if (m_terrainTexture2X == NULL) { m_terrainTexture2X = m_map->getFlatTexture(m_xOrigin, m_yOrigin, m_width, 2*PIXELS_PER_GRID); m_terrainTexture2X->Get_Filter().Set_U_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); m_terrainTexture2X->Get_Filter().Set_V_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); } } else { REF_PTR_RELEASE(m_terrainTexture4X); REF_PTR_RELEASE(m_terrainTexture2X); } } //============================================================================= // W3DTerrainBackground::renderTerrain //============================================================================= //============================================================================= void W3DTerrainBackground::drawVisiblePolys(RenderInfoClass & rinfo, Bool disableTextures) { #if 1 if (m_curNumTerrainIndices == 0) { return; } if (m_cullStatus==CULL_STATUS_INVISIBLE) { return; } // Setup the vertex buffer, shader & texture. DX8Wrapper::Set_Index_Buffer(m_indexTerrain,0); DX8Wrapper::Set_Vertex_Buffer(m_vertexTerrain); if (!disableTextures) { if (m_terrainTexture4X) { DX8Wrapper::Set_Texture(1, m_terrainTexture4X); } else if (m_terrainTexture2X) { DX8Wrapper::Set_Texture(1, m_terrainTexture2X); } else { DX8Wrapper::Set_Texture(1, m_terrainTexture); } } DX8Wrapper::Draw_Triangles( 0, m_curNumTerrainIndices/3, 0, m_curNumTerrainVertices); #else if (m_curNumTerrainIndices == 0) { return; } if (m_cullStatus==CULL_STATUS_INVISIBLE) { return; } // Setup the vertex buffer, shader & texture. DX8Wrapper::Set_Index_Buffer(m_indexTerrain,0); DX8Wrapper::Set_Vertex_Buffer(m_vertexTerrain); if (!disableTextures) { if (m_terrainTexture4X) { DX8Wrapper::Set_Texture(0, m_terrainTexture4X); } else if (m_terrainTexture2X) { DX8Wrapper::Set_Texture(0, m_terrainTexture2X); } else { DX8Wrapper::Set_Texture(0, m_terrainTexture); } } DX8Wrapper::Draw_Triangles( 0, m_curNumTerrainIndices/3, 0, m_curNumTerrainVertices); #endif }