/* Copyright (c) 2002-2012 Croteam Ltd. This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "Engine/StdH.h" #include #include #include // !!! FIXME: This is messing up name mangling. Need to look at this. --ryan. #if ((defined PLATFORM_UNIX) && (defined __forceinline)) # undef __forceinline # define __forceinline #endif #include extern CTerrain *_ptrTerrain; extern FLOAT3D _vViewerAbs; #define BORDERTEST 0 extern CStaticStackArray _avLerpedVerices; CTerrainTile::CTerrainTile() { tt_iIndex = -1; tt_iArrayIndex = -1; tt_iLod = -1; tt_iRequestedLod = 0; tt_ulTileFlags = 0; } // Render tile void CTerrainTile::Render(void) { ASSERT(FALSE); } CTerrainTile::~CTerrainTile() { Clear(); } // Release tile void CTerrainTile::Clear() { } // TEMP!!!!! __forceinline void CTerrainTile::LerpVertexPos(GFXVertex4 &vtx, INDEX iVxTarget, INDEX iVxFirst,INDEX iVxLast) { GFXVertex4 &vxFirst = GetVertices()[iVxFirst]; GFXVertex4 &vxLast = GetVertices()[iVxLast]; GFXVertex4 &vxTarget = GetVertices()[iVxTarget]; FLOAT fLerpMaxPosY = Lerp(vxFirst.y,vxLast.y,0.5f); FLOAT fLerpResultY = Lerp(vxTarget.y,fLerpMaxPosY,tt_fLodLerpFactor); vtx.x = vxTarget.x; vtx.y = fLerpResultY; vtx.z = vxTarget.z; } /* * Tile memory alloc */ INDEX CTerrainTile::ChangeTileArrays(INDEX iRequestedArrayLod) { // if requested lod is same as current lod if(iRequestedArrayLod==tt_iLod) { // Just pop all arrays GetVertices().PopAll(); GetTexCoords().PopAll(); GetShadowMapTC().PopAll(); GetIndices().PopAll(); // if tile is in highest lod if(tt_iLod==0) { // pop detail uvmap GetDetailTC().PopAll(); // for each tile layer INDEX cttl=GetTileLayers().Count(); for(INDEX itl=0;itltr_aArrayHolders[iRequestedArrayLod]; ASSERT(tt_iArrayIndex==-1); tt_iArrayIndex = ah.GetNewArrays(); // if this is first lod if(iRequestedArrayLod==0) { // Add tile layers INDEX ctLayers = _ptrTerrain->tr_atlLayers.Count(); if(ctLayers>0) { GetTileLayers().Push(ctLayers); } } return iRequestedArrayLod; } void CTerrainTile::ReleaseTileArrays() { // if tile had some arrays if(tt_iArrayIndex != -1) { // Free them CArrayHolder &ahOld = _ptrTerrain->tr_aArrayHolders[tt_iLod]; ahOld.FreeArrays(tt_iArrayIndex); tt_iArrayIndex = -1; } } void CTerrainTile::EmptyTileArrays() { ASSERT(tt_iArrayIndex != -1); CArrayHolder &ahCurrent = _ptrTerrain->tr_aArrayHolders[tt_iLod]; ahCurrent.EmptyArrays(tt_iArrayIndex); } /* * Tile generation (TEMP) */ inline void CTerrainTile::AddTriangle(INDEX iind1,INDEX iind2,INDEX iind3) { // is this tile in highest lod if(tt_iLod==0) { // Is this triangle visible GFXVertex *pvx[3]; pvx[0] = &GetVertices()[iind1]; pvx[1] = &GetVertices()[iind2]; pvx[2] = &GetVertices()[iind3]; // check if all vertices all visible SLONG slTriangleMask = pvx[0]->shade + pvx[1]->shade + pvx[2]->shade; if(slTriangleMask!=255*3) { return; } // Add one triangle INDEX *pIndices = GetIndices().Push(3); pIndices[0] = iind1; pIndices[1] = iind2; pIndices[2] = iind3; // for each layer INDEX cttl = GetTileLayers().Count(); for(INDEX itl=0;itltr_atlLayers[itl]; // if this is tile layer if(tl.tl_ltType==LT_TILE) { continue; // skip it } COLOR ul = ttl.tl_acColors[iind1].ub.a + ttl.tl_acColors[iind2].ub.a + ttl.tl_acColors[iind3].ub.a; if(ul>0) { INDEX *pIndices = ttl.tl_auiIndices.Push(3); pIndices[0] = iind1; pIndices[1] = iind2; pIndices[2] = iind3; } } } else { INDEX *pIndices = GetIndices().Push(3); pIndices[0] = iind1; pIndices[1] = iind2; pIndices[2] = iind3; } } // Returns a height in heightmap inline FLOAT GetHeight(INDEX ic,INDEX ir,INDEX iTileIndex) { CTerrainTile &tt = _ptrTerrain->tr_attTiles[iTileIndex]; // Get Y position of vertex from heightmap INDEX icHMap = ic + tt.tt_iOffsetX*_ptrTerrain->GetQuadsPerTileRow(); INDEX irHMap = ir + tt.tt_iOffsetZ*_ptrTerrain->GetQuadsPerTileRow(); INDEX ivx = icHMap+irHMap*_ptrTerrain->tr_pixHeightMapWidth; return (FLOAT)_ptrTerrain->tr_auwHeightMap[ivx]; } BYTE GetVertexAlpha(INDEX ic,INDEX ir,INDEX iTileIndex,INDEX iLayer) { CTerrainTile &tt = _ptrTerrain->tr_attTiles[iTileIndex]; INDEX icHMap = ic + tt.tt_iOffsetX*_ptrTerrain->GetQuadsPerTileRow(); INDEX irHMap = ir + tt.tt_iOffsetZ*_ptrTerrain->GetQuadsPerTileRow(); INDEX ivx = icHMap+irHMap*_ptrTerrain->tr_pixHeightMapWidth; CTerrainLayer &tl = _ptrTerrain->tr_atlLayers[iLayer]; return tl.tl_aubColors[ivx]; } // Returns vertex at specified position inside one tile GFXVertex4 GetVertex(INDEX ic,INDEX ir,INDEX iTileIndex) { CTerrainTile &tt = _ptrTerrain->tr_attTiles[iTileIndex]; FLOAT fPosY = GetHeight(ic,ir,iTileIndex); GFXVertex4 vx; INDEX ix = ic + tt.tt_iOffsetX*_ptrTerrain->GetQuadsPerTileRow(); INDEX iz = ir + tt.tt_iOffsetZ*_ptrTerrain->GetQuadsPerTileRow(); vx.x = (FLOAT)(ix); vx.z = (FLOAT)(iz); vx.y = fPosY; // Fill 'shade' with edge map value INDEX iMask = ix + iz*_ptrTerrain->tr_pixHeightMapWidth; vx.shade = _ptrTerrain->tr_aubEdgeMap[iMask]; return vx; } // Add vertex to array of vertices void CTerrainTile::AddVertex(INDEX ic, INDEX ir) { GFXVertex4 &vxFinal = GetVertices().Push(); GFXTexCoord &tcShadow = GetShadowMapTC().Push(); const GFXVertex4 &vx = GetVertex(ic,ir,tt_iIndex); vxFinal.x = vx.x * _ptrTerrain->tr_vStretch(1); vxFinal.y = vx.y * _ptrTerrain->tr_vStretch(2); vxFinal.z = vx.z * _ptrTerrain->tr_vStretch(3); vxFinal.shade = vx.shade; // if this tile is in highest lod if(tt_iLod==0) { // for each layer INDEX cttl = GetTileLayers().Count(); for(INDEX itl=0;itltr_atlLayers[itl]; // Set vertex color GFXColor &col = ttl.tl_acColors.Push(); BYTE bAlpha = GetVertexAlpha(ic,ir,tt_iIndex,itl); col.ul.abgr = 0x00FFFFFF; col.ub.a = bAlpha; // if this is normal layer if(tl.tl_ltType == LT_NORMAL) { // Set its texcoords GFXTexCoord &tc = ttl.tl_atcTexCoords.Push(); tc.uv.u = (FLOAT)ic; tc.uv.v = (FLOAT)ir; } } GFXTexCoord &tcDetail = GetDetailTC().Push(); tcDetail.uv.u = ic * 2; tcDetail.uv.v = ir * 2; // if tile is in lowest lod } else if(tt_iLod==_ptrTerrain->tr_iMaxTileLod) { GFXTexCoord &tc = GetTexCoords().Push(); FLOAT fWidth = (_ptrTerrain->tr_pixHeightMapWidth-1); FLOAT fHeight = (_ptrTerrain->tr_pixHeightMapHeight-1); tc.uv.u = vx.x / fWidth; tc.uv.v = vx.z / fHeight; // tile is not in highest lod nor in lowest lod } else { GFXTexCoord &tc = GetTexCoords().Push(); tc.uv.u = ((vx.x - tt_iOffsetX * _ptrTerrain->GetQuadsPerTileRow()) / (_ptrTerrain->GetQuadsPerTileRow())); tc.uv.v = ((vx.z - tt_iOffsetZ * _ptrTerrain->GetQuadsPerTileRow()) / (_ptrTerrain->GetQuadsPerTileRow())); } tcShadow.uv.u = vx.x / (_ptrTerrain->tr_pixHeightMapWidth-1); tcShadow.uv.v = vx.z / (_ptrTerrain->tr_pixHeightMapHeight-1); } void CTerrainTile::ReGenerateTileLayer(INDEX iTileLayer) { FLOAT fStrX = _ptrTerrain->tr_vStretch(1); FLOAT fStrY = _ptrTerrain->tr_vStretch(2); FLOAT fStrZ = _ptrTerrain->tr_vStretch(3); PIX pixHMWidth = _ptrTerrain->tr_pixHeightMapWidth; PIX iOffsetX = tt_iOffsetX * _ptrTerrain->tr_ctQuadsInTileRow; PIX iOffsetZ = tt_iOffsetZ * _ptrTerrain->tr_ctQuadsInTileRow; INDEX ctQuadsPerRow = _ptrTerrain->tr_ctQuadsInTileRow; CTerrainLayer &tl = _ptrTerrain->tr_atlLayers[iTileLayer]; TileLayer &ttl = GetTileLayers()[iTileLayer]; INDEX ctVertices = ctQuadsPerRow*ctQuadsPerRow*4; // four vertices per one quad INDEX ctIndices = ctQuadsPerRow*ctQuadsPerRow*6; // six indices per one quad ASSERT(ttl.tl_avVertices.Count()==0 && ttl.tl_atcTexCoords.Count()==0 && ttl.tl_auiIndices.Count()==0); GFXVertex *pvtx = ttl.tl_avVertices.Push(ctVertices); GFXTexCoord *ptc = ttl.tl_atcTexCoords.Push(ctVertices); INDEX *pind = ttl.tl_auiIndices.Push(ctIndices); UBYTE *pubMask = tl.tl_aubColors; INDEX ivx = 0; INDEX iind = 0; BOOL bFacing = FALSE; INDEX iTilesInRowLog2 = FastLog2(tl.tl_ctTilesInRow); // for each quad in tile for(INDEX iz=0;iztr_auwHeightMap[pix] * fStrY; pvtx[ivx ].z = (FLOAT)(iOffsetZ+iz+0)*fStrZ; pvtx[ivx+1].x = (FLOAT)(iOffsetX+ix+1)*fStrX; pvtx[ivx+1].y = (FLOAT)_ptrTerrain->tr_auwHeightMap[pix+1] * fStrY; pvtx[ivx+1].z = (FLOAT)(iOffsetZ+iz+0)*fStrZ; pvtx[ivx+2].x = (FLOAT)(iOffsetX+ix+0)*fStrX; pvtx[ivx+2].y = (FLOAT)_ptrTerrain->tr_auwHeightMap[pix+pixHMWidth] * fStrY; pvtx[ivx+2].z = (FLOAT)(iOffsetZ+iz+1)*fStrZ; pvtx[ivx+3].x = (FLOAT)(iOffsetX+ix+1)*fStrX; pvtx[ivx+3].y = (FLOAT)_ptrTerrain->tr_auwHeightMap[pix+pixHMWidth+1] * fStrY; pvtx[ivx+3].z = (FLOAT)(iOffsetZ+iz+1)*fStrZ; UBYTE ubMask = pubMask[pix]; INDEX iTile = ubMask&TL_TILE_INDEX; // First 4 bits BOOL bFlipX = (ubMask&TL_FLIPX)>>TL_FLIPX_SHIFT; BOOL bFlipY = (ubMask&TL_FLIPY)>>TL_FLIPY_SHIFT; BOOL bSwapXY = (ubMask&TL_SWAPXY)>>TL_SWAPXY_SHIFT; BOOL bVisible = (ubMask&TL_VISIBLE)>>TL_VISIBLE_SHIFT; INDEX iTileX = iTile&(tl.tl_ctTilesInRow-1); INDEX iTileY = iTile>>iTilesInRowLog2; ASSERT(iTileXGetQuadsPerTileRow()>>tt_iLod; // Fill middle of tile with triangles for(ir=1;irtr_atlLayers.Count(); for(INDEX itl=0;itltr_atlLayers[itl]; // if this is tile layer if(tl.tl_ltType == LT_TILE) { // Regenerate it ReGenerateTileLayer(itl); } } } BOOL bAllowTopMapRegen = !(GetFlags()&TT_NO_TOPMAP_REGEN); // if top map is allowed to be regenerated if(bAllowTopMapRegen) { // if tile is not in highest nor in lowest lod if(tt_iLod>0 && tt_iLod<_ptrTerrain->tr_iMaxTileLod) { // if top map regen is forced or tile has changed lod BOOL bForceTopMapRegen = (GetFlags()&TT_FORCE_TOPMAP_REGEN); if(bForceTopMapRegen || iOldLod!=tt_iLod) { // Update tile top map _ptrTerrain->UpdateTopMap(tt_iIndex); // remove flag that forced top map regen RemoveFlag(TT_FORCE_TOPMAP_REGEN); // allow terrain to regenerete top map _ptrTerrain->AddFlag(TR_ALLOW_TOP_MAP_REGEN); } } // if not } else { // regenerate it next time RemoveFlag(TT_NO_TOPMAP_REGEN); } // if flag to resize quad tree node has been set if(GetFlags()&TT_QUADTREENODE_REGEN) { // update quad tree node UpdateQuadTreeNode(); // node has been updated RemoveFlag(TT_QUADTREENODE_REGEN); } INDEX ctBorderVertices = tt_ctBorderVertices[0] + tt_ctBorderVertices[1] + tt_ctBorderVertices[2] + tt_ctBorderVertices[3]; // if tile is in lowest lod, has not lerp factor and no border vertices if(tt_iLod==_ptrTerrain->tr_iMaxTileLod && tt_fLodLerpFactor==0.0f && ctBorderVertices == 0) { // mark it as available for batch rendering AddFlag(TT_IN_LOWEST_LOD); } else { RemoveFlag(TT_IN_LOWEST_LOD); } } INDEX CTerrainTile::CalculateLOD(void) { QuadTreeNode &qtn = _ptrTerrain->tr_aqtnQuadTreeNodes[tt_iIndex]; FLOAT fDistance = (qtn.qtn_aabbox.Center() - _vViewerAbs).Length() - qtn.qtn_aabbox.Size().Length() / 2; // if flag has been set for tile to regenerate without lod if(GetFlags()&TT_NO_LODING) { // set new lod at 0 fDistance = 0; // if tile is in highest lod, no need to regenerate texture // AddFlag(TT_NO_TOPMAP_REGEN); // remove flag for no loding RemoveFlag(TT_NO_LODING); } // Calculate new lod INDEX iNewLod = Clamp((INDEX)(fDistance/_ptrTerrain->tr_fDistFactor),(INDEX)0,_ptrTerrain->tr_iMaxTileLod); // if lod has changed if(iNewLod!=tt_iLod) { // add to regeneration queue _ptrTerrain->AddTileToRegenQueue(tt_iIndex); // for each neighbour for(INDEX in=0;in<4;in++) { INDEX ini = tt_aiNeighbours[in]; // if neighbour is valid if(ini>=0) { CTerrainTile &ttNeigbour = _ptrTerrain->tr_attTiles[ini]; // if neighbour is in higher lod if(TRUE) { /*ttNeigbour.tt_iLod > tt.tt_iNewLod*/ // add neighbour to regen queue _ptrTerrain->AddTileToRegenQueue(ini); } } } // Calculate num of vertices for row and col in current lod tt_ctLodVtxX = (_ptrTerrain->GetQuadsPerTileRow() >> iNewLod) + 1; tt_ctLodVtxY = (_ptrTerrain->GetQuadsPerTileRow() >> iNewLod) + 1; } // Calculate lerp factor tt_fLodLerpFactor = Clamp(fDistance/_ptrTerrain->tr_fDistFactor - iNewLod,0.0f,1.0f); // if tile is in lowest lod if(iNewLod == _ptrTerrain->tr_iMaxTileLod) { // no lerping for this tile tt_fLodLerpFactor = 0.0f; } // return new lod return iNewLod; } // Update quad tree node void CTerrainTile::UpdateQuadTreeNode() { // resize aabox for this node FLOATaabbox3D bboxNewBox; GFXVertex4 *pavVertices; INDEX *paiIndices; INDEX ctVertices; INDEX ctIndices; QuadTreeNode &qtn = _ptrTerrain->tr_aqtnQuadTreeNodes[tt_iIndex]; // prepare box that will extract vertices (x and z of old box are allready valid) bboxNewBox = qtn.qtn_aabbox; bboxNewBox.minvect(2) = 0; bboxNewBox.maxvect(2) = 65536 * _ptrTerrain->tr_vStretch(2); // extract vertices in box ExtractPolygonsInBox(_ptrTerrain,bboxNewBox,&pavVertices,&paiIndices,ctVertices,ctIndices); // if some vertices exists if(ctVertices>0) { qtn.qtn_aabbox = FLOAT3D(pavVertices->x,pavVertices->y,pavVertices->z); pavVertices++; } else { ASSERTALWAYS("Some vertices must exisits for tile bbox"); } // for each vertex in box after first for(INDEX ivx=1;ivxx,pavVertices->y,pavVertices->z); pavVertices++; } // notify terrain that it needs to update higher levels of quad tree _ptrTerrain->AddFlag(TR_REBUILD_QUADTREE); } // Regenerate top border void CTerrainTile::ReGenerateTopBorder() { INDEX iTopTileIndex = tt_aiNeighbours[NB_TOP]; INDEX iTopBorderLod = tt_iLod; // If top neighbour exists if(iTopTileIndex!=(-1)) { CTerrainTile &ttTop = _ptrTerrain->tr_attTiles[iTopTileIndex]; iTopBorderLod = ttTop.tt_iRequestedLod; // !!!! iLod 2 iRequested } #if BORDERTEST iTopBorderLod = 0; #endif INDEX iStep = 1<tr_attTiles[iLeftTileIndex]; iLeftBorderLod = ttLeft.tt_iRequestedLod; } #if BORDERTEST iLeftBorderLod = 0; #endif INDEX iStep = 1<tr_attTiles[iRightTileIndex]; iRightBorderLod = ttRight.tt_iRequestedLod; } #if BORDERTEST iRightBorderLod = 0; #endif INDEX iStep = 1<tr_attTiles[iBottomTileIndex]; iBottomBorderLod = ttBottom.tt_iRequestedLod; } #if BORDERTEST iBottomBorderLod = 0; #endif INDEX iStep = 1< &CTerrainTile::GetVertices() { ASSERT(tt_iArrayIndex!=-1); ASSERT(tt_iLod!=-1); CArrayHolder &ah = _ptrTerrain->tr_aArrayHolders[tt_iLod]; TileArrays &ta = ah.ah_ataTileArrays[tt_iArrayIndex]; return ta.ta_avVertices; } __forceinline CStaticStackArray &CTerrainTile::GetTexCoords() { ASSERT(tt_iArrayIndex!=-1); ASSERT(tt_iLod!=-1); CArrayHolder &ah = _ptrTerrain->tr_aArrayHolders[tt_iLod]; TileArrays &ta = ah.ah_ataTileArrays[tt_iArrayIndex]; return ta.ta_auvTexCoords; } __forceinline CStaticStackArray &CTerrainTile::GetShadowMapTC() { ASSERT(tt_iArrayIndex!=-1); ASSERT(tt_iLod!=-1); CArrayHolder &ah = _ptrTerrain->tr_aArrayHolders[tt_iLod]; TileArrays &ta = ah.ah_ataTileArrays[tt_iArrayIndex]; return ta.ta_auvShadowMap; } __forceinline CStaticStackArray &CTerrainTile::GetDetailTC() { ASSERT(tt_iArrayIndex!=-1); ASSERT(tt_iRequestedLod==0 || tt_iLod==0); CArrayHolder &ah = _ptrTerrain->tr_aArrayHolders[tt_iRequestedLod]; TileArrays &ta = ah.ah_ataTileArrays[tt_iArrayIndex]; return ta.ta_auvDetailMap; } __forceinline CStaticStackArray &CTerrainTile::GetIndices() { ASSERT(tt_iArrayIndex!=-1); ASSERT(tt_iLod!=-1); CArrayHolder &ah = _ptrTerrain->tr_aArrayHolders[tt_iLod]; TileArrays &ta = ah.ah_ataTileArrays[tt_iArrayIndex]; return ta.ta_auiIndices; } __forceinline CStaticStackArray &CTerrainTile::GetTileLayers() { ASSERT(tt_iArrayIndex!=-1); ASSERT(tt_iRequestedLod==0 || tt_iLod==0); CArrayHolder &ah = _ptrTerrain->tr_aArrayHolders[tt_iRequestedLod]; TileArrays &ta = ah.ah_ataTileArrays[tt_iArrayIndex]; return ta.ta_atlLayers; } __forceinline CTextureData *CTerrainTile::GetTopMap() { ASSERT(tt_iArrayIndex!=-1); ASSERT(tt_iLod!=-1); ASSERT(tt_iLod!=0); ASSERT(tt_iLod!=_ptrTerrain->tr_iMaxTileLod); CArrayHolder &ah = _ptrTerrain->tr_aArrayHolders[tt_iRequestedLod]; TileArrays &ta = ah.ah_ataTileArrays[tt_iArrayIndex]; return ta.ta_ptdTopMap; } // Count used memory SLONG CTerrainTile::GetUsedMemory(void) { SLONG slUsedMemory=0; slUsedMemory += sizeof(CTerrainTile); return slUsedMemory; }