/* 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 <Engine/Base/Stream.h>
#include <Engine/Base/ListIterator.inl>
#include <Engine/Math/Projection.h>
#include <Engine/Math/FixInt.h>
#include <Engine/Graphics/DrawPort.h>
#include <Engine/Graphics/ImageInfo.h>
#include <Engine/Graphics/GfxLibrary.h>
#include <Engine/Terrain/Terrain.h>
#include <Engine/Terrain/TerrainRender.h>
#include <Engine/Terrain/TerrainEditing.h>
#include <Engine/Terrain/TerrainMisc.h>
#include <Engine/Templates/Stock_CTextureData.h>
#include <Engine/Entities/Entity.h>
#include <Engine/Entities/ShadingInfo.h>
#include <Engine/Graphics/Font.h>
#include <Engine/Base/Console.h>
#include <Engine/Rendering/Render.h>

extern CTerrain *_ptrTerrain;

extern BOOL _bWorldEditorApp; // is this world edtior app


static INDEX _iTerrainVersion = 9;   // Current terrain version
static INDEX ctGeneratedTopMaps = 0; // TEMP
static INDEX ctGlobalTopMaps    = 0; // TEMP
INDEX  _ctShadowMapUpdates = 0;      // TEMP
// TEMP

INDEX _ctNodesVis = 0;
INDEX _ctTris = 0;
INDEX _ctDelayedNodes = 0;
static void ShowTerrainInfo(CAnyProjection3D &apr, CDrawPort *pdp, CTerrain *ptrTerrain); // TEMP

/*
 * Terrain initialization
 */
CTerrain::CTerrain()
{
  tr_vStretch = FLOAT3D(1,0.05f,1);
  tr_fDistFactor = 32;
  tr_iMaxTileLod = 0;
  tr_iSelectedLayer = 0;
  tr_ctDriverChanges = -1;

  tr_pixHeightMapWidth       = 256;
  tr_pixHeightMapHeight      = 256;
  tr_pixTopMapWidth          = 256;
  tr_pixTopMapHeight         = 256;
  tr_pixFirstMipTopMapWidth  = 256;
  tr_pixFirstMipTopMapHeight = 256;
  tr_ptdDetailMap  = NULL;
  tr_auwHeightMap  = NULL;
  tr_aubEdgeMap    = NULL;
  tr_auwShadingMap = NULL;

  // TEMP
  try {
    tr_ptdDetailMap = _pTextureStock->Obtain_t((CTString)"Textures\\Detail\\Crumples04.tex");
  } catch(char *) {
  }

  // Set size of shadow and shading maps
  SetShadowMapsSize(0,0);

  // Set terrain size
  SetTerrainSize(FLOAT3D(256,80,256));
  // Set num of quads in one tile
  SetQuadsPerTileRow(32);

  SetFlags(TR_REGENERATE);
}

// Render visible terrain tiles
void CTerrain::Render(CAnyProjection3D &apr, CDrawPort *pdp)
{
  // prepare gfx stuff
  PrepareScene(apr, pdp, this);

  if(tr_ctDriverChanges!=_pGfx->gl_ctDriverChanges) {
    tr_ctDriverChanges = _pGfx->gl_ctDriverChanges;
    // RefreshTerrain();
  }

  // if terrain is not frozen
  extern INDEX ter_bNoRegeneration;
  if(GetFlags()&TR_REGENERATE && !ter_bNoRegeneration) {
    // Regenerate tiles
    ReGenerate();
  }
  // if shadow map must be regenerated
  if(GetFlags()&TR_UPDATE_SHADOWMAP) {
    UpdateShadowMap();
  }


  // if top map regen is allowed
  if(GetFlags()&TR_ALLOW_TOP_MAP_REGEN) {
    // if top map regen is requested
    if(GetFlags()&TR_REGENERATE_TOP_MAP) {
      // update terrain top map
      UpdateTopMap(-1);
      // remove request for top map regen
      RemoveFlag(TR_REGENERATE_TOP_MAP);
    }
    // remove flag that allows terrain to regenerate top map
    RemoveFlag(TR_ALLOW_TOP_MAP_REGEN);
  }

  // show
  RenderTerrain();

  // if flag show brush selection has been set
  if(GetFlags()&TR_SHOW_SELECTION) {
    ShowSelectionInternal(this);
    // remove show selection terrain flag
    RemoveFlag(TR_SHOW_SELECTION);
  }

  // if flag for quadtree rebuilding is set
  if(GetFlags()&TR_REBUILD_QUADTREE) {
    // Resize quadtree
    UpdateQuadTree();
    // remove flag for quadtree rebuilding
    RemoveFlag(TR_REBUILD_QUADTREE);
  }

  RemoveFlag(TR_HAS_FOG);
  RemoveFlag(TR_HAS_HAZE);

  extern INDEX ter_bShowWireframe;
  // if wireframe mode forced
  if(ter_bShowWireframe) {
    COLOR colWire = 0xFFFFFFFF;
    RenderTerrainWire(colWire);
  }

  extern INDEX ter_bShowQuadTree;
  // if showing of quad tree is required
  if(ter_bShowQuadTree) {
    DrawQuadTree();
  }

  extern INDEX ter_bShowInfo;
  if(ter_bShowInfo) {
    ShowTerrainInfo(apr, pdp, this);
  }

  _ptrTerrain = NULL;
}

void CTerrain::RenderWireFrame(CAnyProjection3D &apr, CDrawPort *pdp, COLOR &colEdges)
{
  // prepare gfx stuff
  PrepareScene(apr, pdp, this);
  // Regenerate tiles 
  if(tr_ctTiles>=0) {
    CTerrainTile &tt = tr_attTiles[0];
    if(tt.tt_iLod == -1) {
      ReGenerate();
    }
  }
  // show
  RenderTerrainWire(colEdges);
}

// Create empty terrain with given size
void CTerrain::CreateEmptyTerrain_t(PIX pixWidth,PIX pixHeight)
{
  _ptrTerrain = this;
  // Clear old terrain data if exists
  Clear();

  ASSERT(tr_auwHeightMap==NULL);
  ASSERT(tr_aubEdgeMap==NULL);
  
  AllocateHeightMap(pixWidth,pixHeight);

  AddDefaultLayer_t();
  // Rebuild terrain
  ReBuildTerrain();
  _ptrTerrain = NULL;
}

// Import height map from targa file
void CTerrain::ImportHeightMap_t(CTFileName fnHeightMap, BOOL bUse16b/*=TRUE*/)
{
  _ptrTerrain = this;
  BOOL bResizeTerrain = FALSE;

  // Load targa file 
  CImageInfo iiHeightMap;
  iiHeightMap.LoadAnyGfxFormat_t(fnHeightMap);

  // if new width and height are same 
  if(tr_pixHeightMapWidth==iiHeightMap.ii_Width && tr_pixHeightMapHeight==iiHeightMap.ii_Height) {
    // Clear terrain data without removing layers
    bResizeTerrain = FALSE;
  } else {
    // Clear all terrain data
    bResizeTerrain = TRUE;
  }
  bResizeTerrain = TRUE;

  FLOAT fLogWidht  = Log2(iiHeightMap.ii_Width-1);
  FLOAT fLogHeight = Log2(iiHeightMap.ii_Height-1);
  if(fLogWidht!=INDEX(fLogWidht) || fLogHeight!=INDEX(fLogHeight)) {
    ThrowF_t("Invalid terrain width or height");
  }
  if(iiHeightMap.ii_Width!= iiHeightMap.ii_Height) {
    ThrowF_t("Only terrains with same width and height are supported in this version");
  }

  // Reallocate memory for terrain with size
  ReAllocateHeightMap(iiHeightMap.ii_Width, iiHeightMap.ii_Height);

  INDEX iHeightMapSize = iiHeightMap.ii_Width * iiHeightMap.ii_Height;

  UBYTE *puwSrc = &iiHeightMap.ii_Picture[0];
  UWORD *puwDst = &tr_auwHeightMap[0];
  INDEX iBpp = iiHeightMap.ii_BitsPerPixel/8;

  // for each word in loaded image
  for(INDEX iw=0;iw<iHeightMapSize;iw++) {
    // use 16 bits for importing
    if(bUse16b) {
      *puwDst = *(UWORD*)puwSrc;
    // use 8 bits for importing
    } else {
      *puwDst = *(UBYTE*)puwSrc<<8;
    }
    puwDst++;
    puwSrc+=iBpp;
  }

  // Rebuild terrain
  ReBuildTerrain();

  _ptrTerrain = NULL;
}  

// Export height map to targa file
void CTerrain::ExportHeightMap_t(CTFileName fnHeightMap, BOOL bUse16b/*=TRUE*/)
{
  ASSERT(tr_auwHeightMap!=NULL);
  INDEX iSize = tr_pixHeightMapWidth*tr_pixHeightMapHeight;

  CImageInfo iiHeightMap;
  iiHeightMap.ii_Width  = tr_pixHeightMapWidth;
  iiHeightMap.ii_Height = tr_pixHeightMapHeight;
  iiHeightMap.ii_BitsPerPixel = 32;
  iiHeightMap.ii_Picture = (UBYTE*)AllocMemory(iSize*iiHeightMap.ii_BitsPerPixel/8);

  GFXColor *pacolImage = (GFXColor*)&iiHeightMap.ii_Picture[0];
  UWORD    *puwHeight  = tr_auwHeightMap;
  for(INDEX ipix=0;ipix<iSize;ipix++) {
    *pacolImage = 0x00000000;
    if(bUse16b) {
      UWORD *puwData = (UWORD*)&pacolImage[0];
      *puwData = *puwHeight;
    } else {
      UBYTE *pubData = (UBYTE*)&pacolImage[0];
      UWORD *puwHData = puwHeight;
      *pubData = (UBYTE)(*puwHData>>8);
    }
    pacolImage++;
    puwHeight++;
  }

  iiHeightMap.SaveTGA_t(fnHeightMap);
  iiHeightMap.Clear();
}

// Rebuild all terrain
void CTerrain::ReBuildTerrain(BOOL bDelayTileRegen/*=FALSE*/)
{
  _ptrTerrain = this;

  ClearTopMaps();
  ClearTiles();
  ClearArrays();
  ClearQuadTree();

  // Make sure terrain is same size (in metars)
  SetTerrainSize(tr_vTerrainSize);
  // Build terrain data
  BuildTerrainData();
  // Build terrain quadtree
  BuildQuadTree();
  // Generate global top map
  GenerateTerrainTopMap();
  // Clear current regen list
  ClearRegenList();
  // Add all tiles to reqen queue
  AddAllTilesToRegenQueue();
  
  // if not delaying tile regen
  if(!bDelayTileRegen) {
    // Regenerate tiles now
    ReGenerate();
    // Update shadow map
    UpdateShadowMap();
  }
}

// Refresh terrain
void CTerrain::RefreshTerrain(void)
{
  ReBuildTerrain();
}

// Set terrain size
void CTerrain::SetTerrainSize(FLOAT3D vSize)
{
  tr_vStretch(1) = vSize(1) / (tr_pixHeightMapWidth-1);
  tr_vStretch(2) = vSize(2) / 65535.0f;
  tr_vStretch(3) = vSize(3) / (tr_pixHeightMapHeight-1);
  // remember new size
  tr_vTerrainSize = vSize;
}

template <class Type>
static void CropMap(INDEX iNewWidth, INDEX iNewHeight, INDEX iOldWidth, INDEX iOldHeight, Type *pNewData, Type *pOldData)
{
  INDEX iWidth  = Min(iOldWidth,iNewWidth);
  INDEX iHeight = Min(iOldHeight,iNewHeight);
  INDEX iNewStepX = ClampDn(iNewWidth-iOldWidth,0L);
  INDEX iOldStepX = ClampDn(iOldWidth-iNewWidth,0L);

  INDEX iNew = 0;
  INDEX iOld = 0;
  for(INDEX iy=0;iy<iHeight;iy++) {
    for(INDEX ix=0;ix<iWidth;ix++) {
      pNewData[iNew] = pOldData[iOld];
      iNew++;
      iOld++;
    }
    iNew += iNewStepX;
    iOld += iOldStepX;
  }
}

template <class Type>
static void StretchMap(INDEX iNewWidth, INDEX iNewHeight, INDEX iOldWidth, INDEX iOldHeight, Type *pNewData, Type *pOldData)
{
  int a=0;
  CropMap(iNewWidth,iNewHeight,iOldWidth,iOldHeight,pNewData,pOldData);
}

template <class Type>
static void ShrinkMap(INDEX iNewWidth, INDEX iNewHeight, INDEX iOldWidth, INDEX iOldHeight, Type *pNewData, Type *pOldData)
{
  FLOAT fWidth  = iNewWidth;
  FLOAT fHeight = iNewHeight;
  FLOAT fDiffX = (FLOAT)iNewWidth  / iOldWidth;
  FLOAT fDiffY = (FLOAT)iNewHeight / iOldHeight;

  ULONG *pulNewData = (ULONG*)AllocMemory(iNewWidth * iNewHeight * sizeof(ULONG));
  memset(pulNewData,0,iNewWidth * iNewHeight * sizeof(ULONG));

  INDEX iOldPix = 0;
  for(FLOAT fy=0;fy<iNewHeight;fy+=fDiffY) {
    for(FLOAT fx=0;fx<iNewWidth;fx+=fDiffX) {
      INDEX iNewPix = floor(fx) + floor(fy) * iNewWidth;
      pulNewData[iNewPix] += pOldData[iOldPix];
      iOldPix++;
    }
  }

  ULONG ulDiv = ceil(1.0f/fDiffX) * ceil(1.0f/fDiffY);
  for(INDEX ii=0;ii<iNewWidth*iNewHeight;ii++) {
    pNewData[ii] = pulNewData[ii] / ulDiv;
  }

  FreeMemory(pulNewData);
  ASSERT(_CrtCheckMemory());
}

template <class Type>
static void ResizeMap(INDEX iNewWidth, INDEX iNewHeight, INDEX iOldWidth, INDEX iOldHeight, Type *pNewData, Type *pOldData)
{
  CropMap(iNewWidth,iNewHeight,iOldWidth,iOldHeight,pNewData,pOldData);
  /*
  if(iNewWidth>=iOldWidth && iNewHeight>=iOldHeight) {
    StretchMap(iNewWidth,iNewHeight,iOldWidth,iOldHeight,pNewData,pOldData);
  } else if(iNewWidth<=iOldWidth && iNewHeight<=iOldHeight) {
    ShrinkMap(iNewWidth,iNewHeight,iOldWidth,iOldHeight,pNewData,pOldData);
  } else {
    INDEX iTempWidth  = Max(iNewWidth ,iOldWidth);
    INDEX iTempHeight = Max(iNewHeight,iOldHeight);
    INDEX iTempSize   = iTempWidth*iTempHeight*sizeof(Type);
    Type *pTempData   = (Type*)AllocMemory(iTempSize);
    memset(pTempData,0,iTempSize);
    StretchMap(iTempWidth,iTempHeight,iOldWidth,iOldHeight,pTempData,pOldData);
    ShrinkMap(iNewWidth,iNewHeight,iTempWidth,iTempHeight,pNewData, pTempData);
    FreeMemory(pTempData);
  }
  */
}

void CTerrain::AllocateHeightMap(PIX pixWidth, PIX pixHeight)
{
  ASSERT(tr_auwHeightMap==NULL);
  ASSERT(tr_aubEdgeMap==NULL);

  FLOAT fLogWidht  = Log2(pixWidth-1);
  FLOAT fLogHeight = Log2(pixHeight-1);
  if(fLogWidht!=INDEX(fLogWidht) || fLogHeight!=INDEX(fLogHeight)) {
    ASSERTALWAYS("Invalid terrain width or height");
    return;
  }
  if(pixWidth != pixHeight) {
    ASSERTALWAYS("Only terrains with same width and height are supported in this version");
    return;
  }

  INDEX iSize = pixWidth * pixHeight * sizeof(UBYTE);

  // Allocate memory for maps
  tr_auwHeightMap  = (UWORD*)AllocMemory(iSize*2);
  tr_aubEdgeMap    = (UBYTE*)AllocMemory(iSize);
  memset(tr_auwHeightMap,0,iSize*2);
  memset(tr_aubEdgeMap,255,iSize);

  tr_pixHeightMapWidth  = pixWidth;
  tr_pixHeightMapHeight = pixHeight;

  // Update shadow map size cos it depends on size of height map
  SetShadowMapsSize(tr_iShadowMapSizeAspect,tr_iShadingMapSizeAspect);
}

void CTerrain::ReAllocateHeightMap(PIX pixWidth, PIX pixHeight)
{
  ASSERT(tr_auwHeightMap!=NULL);
  ASSERT(tr_aubEdgeMap!=NULL);

  FLOAT fLogWidht  = Log2(pixWidth-1);
  FLOAT fLogHeight = Log2(pixHeight-1);
  if(fLogWidht!=INDEX(fLogWidht) || fLogHeight!=INDEX(fLogHeight)) {
    ASSERTALWAYS("Invalid terrain width or height");
    return;
  }
  if(pixWidth != pixHeight) {
    ASSERTALWAYS("Only terrains with same width and height are supported in this version");
    return;
  }

  INDEX iSize = pixWidth * pixHeight * sizeof(UBYTE);

  // Allocate memory for maps
  UWORD *auwHeightMap = (UWORD*)AllocMemory(iSize*2);
  UBYTE *aubEdgeMap   = (UBYTE*)AllocMemory(iSize);

  // Resize height map
  memset(auwHeightMap,0,iSize*2);
  ResizeMap(pixWidth,pixHeight,tr_pixHeightMapWidth,tr_pixHeightMapHeight,auwHeightMap,tr_auwHeightMap);

  // Resize edge map
  memset(aubEdgeMap,255,iSize);
  ResizeMap(pixWidth,pixHeight,tr_pixHeightMapWidth,tr_pixHeightMapHeight,aubEdgeMap,tr_aubEdgeMap);

  // for each layer
  INDEX cttl = tr_atlLayers.Count();
  for(INDEX itl=0;itl<cttl;itl++) {
    CTerrainLayer &tl = tr_atlLayers[itl];
    // Allocate memory for layer mask
    UBYTE *aubLayerMask = (UBYTE*)AllocMemory(iSize);
    memset(aubLayerMask,0,iSize);
    ASSERT(tl.tl_iMaskWidth  == tr_pixHeightMapWidth);
    ASSERT(tl.tl_iMaskHeight == tr_pixHeightMapHeight);
    // resize layer
    ResizeMap(pixWidth,pixHeight,tl.tl_iMaskWidth,tl.tl_iMaskHeight,aubLayerMask,tl.tl_aubColors);
    // Free old mask
    FreeMemory(tl.tl_aubColors);
    // Apply changes
    tl.tl_aubColors = aubLayerMask;
    tl.tl_iMaskWidth  = pixWidth;
    tl.tl_iMaskHeight = pixHeight;
    // if this is first layer 
    if(itl==0) {
      // fill it
      tl.ResetLayerMask(255);
    }
  }

  // Free old maps
  FreeMemory(tr_auwHeightMap);
  FreeMemory(tr_aubEdgeMap);

  // Apply changes
  tr_auwHeightMap = auwHeightMap;
  tr_aubEdgeMap   = aubEdgeMap;
  tr_pixHeightMapWidth  = pixWidth;
  tr_pixHeightMapHeight = pixHeight;

  ASSERT(_CrtCheckMemory());

  // Update shadow map size cos it depends on size of height map
  SetShadowMapsSize(tr_iShadowMapSizeAspect,tr_iShadingMapSizeAspect);
  /*
  // Clear current maps if they exists
  ClearHeightMap();
  ClearEdgeMap();
  ClearLayers();
  */
/*
  ASSERT(tr_auwHeightMap==NULL);
  ASSERT(tr_aubEdgeMap==NULL);

  INDEX iSize = sizeof(UBYTE)*pixWidth*pixHeight;
  // Allocate memory for heightmap
  tr_auwHeightMap = (UWORD*)AllocMemory(iSize*2);
  // Allocate memory for edge map
  tr_aubEdgeMap = (UBYTE*)AllocMemory(iSize);

  // Reset height map to 0
  memset(tr_auwHeightMap,0,iSize*2);
  // Reset edge map to 255
  memset(tr_aubEdgeMap,255,iSize);

  tr_pixHeightMapWidth  = pixWidth;
  tr_pixHeightMapHeight = pixHeight;
*/
}

// Set shadow map size aspect (relative to height map size) and shading map aspect (relative to shadow map size)
void CTerrain::SetShadowMapsSize(INDEX iShadowMapAspect, INDEX iShadingMapAspect)
{
  // TEMP
  #pragma message(">> Clamp dn SetShadowMapsSize")

  if(iShadingMapAspect<0) {
    iShadingMapAspect = 0;
  }
  ASSERT(iShadingMapAspect>=0);


  tr_iShadowMapSizeAspect  = iShadowMapAspect;
  tr_iShadingMapSizeAspect = iShadingMapAspect;

  if(GetShadowMapWidth()<32 || GetShadingMapHeight()<32) {
    tr_iShadowMapSizeAspect = -(FastLog2(tr_pixHeightMapWidth-1)-5);
  }

  if(GetShadingMapWidth()<32 || GetShadingMapHeight()<32) {
    tr_iShadingMapSizeAspect = 0;
  }

  PIX pixShadowMapWidth   = GetShadowMapWidth();
  PIX pixShadowMapHeight  = GetShadowMapHeight();

  PIX pixShadingMapWidth  = GetShadingMapWidth();
  PIX pixShadingMapHeight = GetShadingMapHeight();


  // Clear current shadow map
  ClearShadowMap();
 
  ULONG ulShadowMapFlags = 0;
  // if current app is world editor app
  if(_bWorldEditorApp) {
    // force texture to be static
    ulShadowMapFlags = TEX_STATIC;
  }

  // Create new shadow map texture
  ASSERT(tr_tdShadowMap.td_pulFrames==NULL);
  CreateTexture(tr_tdShadowMap,pixShadowMapWidth,pixShadowMapHeight,ulShadowMapFlags);
  // Reset shadow map texture
  memset(&tr_tdShadowMap.td_pulFrames[0],0,sizeof(COLOR)*pixShadowMapWidth*pixShadowMapHeight);

  // Create new shading map
  ASSERT(tr_auwShadingMap==NULL);
  tr_auwShadingMap = (UWORD*)AllocMemory(pixShadingMapWidth*pixShadingMapHeight*sizeof(UWORD));
  // Reset shading map
  memset(&tr_auwShadingMap[0],0,pixShadingMapWidth*pixShadingMapHeight*sizeof(UWORD));
}

// Set size of terrain top map texture
void CTerrain::SetGlobalTopMapSize(PIX pixTopMapSize)
{
  FLOAT fLogSize = Log2(pixTopMapSize);
  if(fLogSize!=INDEX(fLogSize)) {
    ASSERTALWAYS("Invalid top map size");
    return;
  }

  tr_pixTopMapWidth  = pixTopMapSize;
  tr_pixTopMapHeight = pixTopMapSize;
}

// Set size of top map texture for tiles in lower lods
void CTerrain::SetTileTopMapSize(PIX pixLodTopMapSize)
{
  FLOAT fLogSize = Log2(pixLodTopMapSize);
  if(fLogSize!=INDEX(fLogSize)) {
    ASSERTALWAYS("Invalid top map size");
    return;
  }

  tr_pixFirstMipTopMapWidth  = pixLodTopMapSize;
  tr_pixFirstMipTopMapHeight = pixLodTopMapSize;
}

// Set lod distance factor
void CTerrain::SetLodDistanceFactor(FLOAT fLodDistance)
{
  tr_fDistFactor = ClampDn(fLodDistance,0.1f);
}

// Get shadow map size
PIX CTerrain::GetShadowMapWidth(void)
{
  if(tr_iShadowMapSizeAspect<0) {
    return (tr_pixHeightMapWidth-1)>>-tr_iShadowMapSizeAspect;
  } else {
    return (tr_pixHeightMapWidth-1)<<tr_iShadowMapSizeAspect;
  }
}
PIX CTerrain::GetShadowMapHeight(void)
{
  if(tr_iShadowMapSizeAspect<0) {
    return (tr_pixHeightMapHeight-1)>>-tr_iShadowMapSizeAspect;
  } else {
    return (tr_pixHeightMapHeight-1)<<tr_iShadowMapSizeAspect;
  }
}

// Get shading map size
PIX CTerrain::GetShadingMapWidth(void)
{
  ASSERT(tr_iShadingMapSizeAspect>=0);
  return GetShadowMapWidth()>>tr_iShadingMapSizeAspect;
}
PIX CTerrain::GetShadingMapHeight(void)
{
  ASSERT(tr_iShadingMapSizeAspect>=0);
  return GetShadowMapHeight()>>tr_iShadingMapSizeAspect;
}

// Get reference to layer
CTerrainLayer &CTerrain::GetLayer(INDEX iLayer)
{
  INDEX cttl = tr_atlLayers.Count();

  ASSERT(iLayer<cttl);
  ASSERT(iLayer>=0);
  return tr_atlLayers[iLayer];
}

// Add new layer
CTerrainLayer &CTerrain::AddLayer_t(CTFileName fnTexture, LayerType ltType/*=LT_NORMAL*/, BOOL bUpdateTerrain/*=TRUE*/)
{
  CTerrainLayer &tl = tr_atlLayers.Push();

  // Set layer properties
  tl.tl_ltType = ltType;
  tl.SetLayerSize(tr_pixHeightMapWidth, tr_pixHeightMapHeight);
  tl.SetLayerTexture_t(fnTexture);

  // if update terrain flag has been set
  if(bUpdateTerrain) {
    // Refresh whole terrain
    RefreshTerrain();
  }
  return tl;
}

// Remove one layer
void CTerrain::RemoveLayer(INDEX iLayer, BOOL bUpdateTerrain/*=TRUE*/)
{
  CStaticStackArray<class CTerrainLayer> atlLayers;
  INDEX cttl = tr_atlLayers.Count();

  if(iLayer<0 || iLayer>=cttl) {
    ASSERTALWAYS("Invalid layer index");
    return;
  }

  if(iLayer==0 && cttl==1) {
    ASSERTALWAYS("Can't remove last layer");
    return;
  }

  // for each exisiting layer
  for(INDEX itl=0;itl<cttl;itl++) {
    // if this layer index is not same as index of layer that need to be removed
    if(itl!=iLayer) {
      // Add new layer
      CTerrainLayer &tl = atlLayers.Push();
      // Copy this layer into new array
      tl = tr_atlLayers[itl];
    }
  }

  ASSERT(atlLayers.Count() == cttl-1);
  // Clear old layers
  tr_atlLayers.Clear();
  // Copy new layers insted of old one
  tr_atlLayers = atlLayers;

  // if update terrain flag has been set
  if(bUpdateTerrain) {
    // Refresh whole terrain
    RefreshTerrain();
  }
}

// Move layer to new position
INDEX CTerrain::SetLayerIndex(INDEX iLayer, INDEX iNewIndex, BOOL bUpdateTerrain/*=TRUE*/)
{
  CStaticStackArray<class CTerrainLayer> atlLayers;
  INDEX cttl = tr_atlLayers.Count();

  if(iLayer<0 || iLayer>=cttl) {
    ASSERTALWAYS("Invalid layer index");
    return iLayer;
  }

  if(iLayer==0 && cttl==1) {
    ASSERTALWAYS("Can't move only layer");
    return iLayer;
  }

  if(iLayer==iNewIndex) {
    ASSERTALWAYS("Old layer index is same as new one");
    return iLayer;
  }


  CStaticStackArray<class CTerrainLayer> &atlFrom = tr_atlLayers;
  CStaticStackArray<class CTerrainLayer> &atlTo = atlLayers;

  atlTo.Push(cttl);

  INDEX iOld = iLayer;
  INDEX iNew = iNewIndex;

  for(INDEX iFrom=0; iFrom<cttl; iFrom++) {
    INDEX iTo=-1;
    if (iNew==iOld) {
      iTo = iFrom;
    } if ((iFrom<iOld && iFrom<iNew) || (iFrom>iOld && iFrom>iNew)) {
      iTo = iFrom;
    } else if (iFrom==iOld) {
      iTo = iNew;
    } else {
      if (iNew>iOld) {
        iTo = iFrom-1;
      } else {
        iTo = iFrom+1;
      }
    }
    atlTo[iTo] = atlFrom[iFrom];
  }

  ASSERT(atlLayers.Count() == cttl);
  // Clear old layers
  tr_atlLayers.Clear();
  // Copy new layers insted of old one
  tr_atlLayers = atlLayers;

  // if update terrain flag has been set
  if(bUpdateTerrain) {
    // Refresh whole terrain
    RefreshTerrain();
  }
  return iNewIndex;
}

// Add tile to reqen queue
void CTerrain::AddTileToRegenQueue(INDEX iTileIndex)
{
  INDEX &iRegenIndex = tr_auiRegenList.Push();
  CTerrainTile &tt = tr_attTiles[iTileIndex];

  iRegenIndex = iTileIndex;
  tt.AddFlag(TT_REGENERATE);
}

// Add all tiles to regen queue
void CTerrain::AddAllTilesToRegenQueue()
{
  // for each terrain tile
  for(INDEX itt=0;itt<tr_ctTiles;itt++) {
    // Add tile to reqen queue
    CTerrainTile &tt = tr_attTiles[itt];
    AddTileToRegenQueue(itt);
  }
}

// Clear current regen list
void CTerrain::ClearRegenList(void)
{
  tr_auiRegenList.PopAll();
}

void CTerrain::UpdateShadowMap(FLOATaabbox3D *pbboxUpdate/*=NULL*/, BOOL bAbsoluteSpace/*=FALSE*/)
{
  // if this is world editor app
  if(!_bWorldEditorApp) {
    // Shadow map can only be updated from world editor
    return;
  }
  // if shadow update is allowed
  if(_wrpWorldRenderPrefs.GetShadowsType()==CWorldRenderPrefs::SHT_FULL) {
    // update terrain shadow map
    UpdateTerrainShadowMap(this,pbboxUpdate,bAbsoluteSpace);
    // don't update shadow map next frame
    RemoveFlag(TR_UPDATE_SHADOWMAP);
  // else 
  } else {
    // dont update shadow map but remeber that it's not up to date
    AddFlag(TR_UPDATE_SHADOWMAP);
  }
}

// Temp:
__forceinline void CopyPixel(COLOR *pubSrc,COLOR *pubDst,FLOAT fMaskStrength)
{
  GFXColor *pcolSrc = (GFXColor*)pubSrc;
  GFXColor *pcolDst = (GFXColor*)pubDst;
  pcolSrc->ub.r = Lerp(pcolSrc->ub.r,pcolDst->ub.r,fMaskStrength);
  pcolSrc->ub.g = Lerp(pcolSrc->ub.g,pcolDst->ub.g,fMaskStrength);
  pcolSrc->ub.b = Lerp(pcolSrc->ub.b,pcolDst->ub.b,fMaskStrength);
  pcolSrc->ub.a = 255;
}

static INDEX _ctSavedTopMaps=0;
static void SaveAsTga(CTextureData *ptdTex)
{
  INDEX iSize = ptdTex->td_mexWidth * ptdTex->td_mexHeight * 4;
  CImageInfo iiHeightMap;
  iiHeightMap.ii_Width  = ptdTex->td_mexWidth;
  iiHeightMap.ii_Height = ptdTex->td_mexHeight;
  iiHeightMap.ii_BitsPerPixel = 32;
  iiHeightMap.ii_Picture = (UBYTE*)AllocMemory(iSize);

  memcpy(&iiHeightMap.ii_Picture[0],&ptdTex->td_pulFrames[0],iSize);

  
  CTString strTopMap = CTString(0,"Temp\\Topmap%d.tga",++_ctSavedTopMaps);
  iiHeightMap.SaveTGA_t(strTopMap);
  iiHeightMap.Clear();


  /*
  GFXColor *pacolImage = (GFXColor*)&iiHeightMap.ii_Picture[0];
  UWORD    *puwHeight  = tr_auwHeightMap;
  for(INDEX ipix=0;ipix<iSize;ipix++) {
    *pacolImage = 0x00000000;
    if(bUse16b) {
      UWORD *puwData = (UWORD*)&pacolImage[0];
      *puwData = *puwHeight;
    } else {
      UBYTE *pubData = (UBYTE*)&pacolImage[0];
      UWORD *puwHData = puwHeight;
      *pubData = (UBYTE)(*puwHData>>8);
    }
    pacolImage++;
    puwHeight++;
  }
  */

}

static void AddTileLayerToTopMap(CTerrain *ptrTerrain, INDEX iTileIndex, INDEX iLayer)
{
  CTerrainLayer &tl = ptrTerrain->tr_atlLayers[iLayer];
  CTextureData *ptdSrc = tl.tl_ptdTexture;
  CTextureData *ptdDst;

  INDEX ctQuadsPerTile = ptrTerrain->tr_ctQuadsInTileRow;
  INDEX iOffsetX = 0;
  INDEX iOffsetZ = 0;

  if(iTileIndex==(-1)) {
    ptdDst = &ptrTerrain->tr_tdTopMap;
    ctQuadsPerTile = ptrTerrain->tr_ctTilesX * ptrTerrain->tr_ctQuadsInTileRow;
  } else {
    CTerrainTile  &tt = ptrTerrain->tr_attTiles[iTileIndex];
    ptdDst = tt.GetTopMap();
    iOffsetX = tt.tt_iOffsetX*ctQuadsPerTile;
    iOffsetZ = tt.tt_iOffsetZ*ctQuadsPerTile;
  }

  ULONG *pulFirstInTopMap = ptdDst->td_pulFrames;
  UBYTE *pubFirstInLayerMask = tl.tl_aubColors;

  // Calculate width and height of quad that will be draw in top map
  PIX pixDstQuadWidth = ptdDst->GetPixWidth() / ctQuadsPerTile;
  PIX pixSrcQuadWidth = tl.tl_pixTileWidth;

  // if dst quad is smaller then one pixel
  if(pixDstQuadWidth==0) {
    return; 
  }

  ASSERT(tl.tl_ctTilesInRow==tl.tl_ctTilesInCol);

  INDEX iSrcMipmap = FastLog2((ptdSrc->GetPixWidth() / tl.tl_ctTilesInRow) / pixDstQuadWidth);
  INDEX iSrcMipMapOffset = GetMipmapOffset(iSrcMipmap,ptdSrc->GetPixWidth(),ptdSrc->GetPixHeight());
  INDEX iSrcMipWidth = ptdSrc->GetPixWidth() >> iSrcMipmap;
  INDEX iSrcMipQuadWidth = pixSrcQuadWidth >> iSrcMipmap;
  INDEX iSrcMipQuadHeight = iSrcMipQuadWidth;

  ASSERT(pixDstQuadWidth==iSrcMipQuadWidth);
   
  ULONG *pulSrcMip = &ptdSrc->td_pulFrames[iSrcMipMapOffset];

  INDEX iMaskIndex = iOffsetX + iOffsetZ*ptrTerrain->tr_pixHeightMapWidth;
  INDEX iMaskStepX = ptrTerrain->tr_pixHeightMapWidth - ctQuadsPerTile;

  // for each quad in tile
  for(INDEX iQuadY=0;iQuadY<ctQuadsPerTile;iQuadY++) {
    for(INDEX iQuadX=0;iQuadX<ctQuadsPerTile;iQuadX++) {

      UBYTE ubMask = pubFirstInLayerMask[iMaskIndex];
      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 iTile = ubMask&TL_TILE_INDEX;
      INDEX iTileX = iTile%tl.tl_ctTilesInRow;
      INDEX iTileY = iTile/tl.tl_ctTilesInRow;

      // if not visible
      if(!bVisible) {
        iMaskIndex++;
        continue; // skip it
      }

      ASSERT(iTileX<tl.tl_ctTilesInRow);
      ASSERT(iTileY<tl.tl_ctTilesInCol);

      INDEX iFirstDstQuadPixel = (iQuadX*pixDstQuadWidth) + iQuadY*pixDstQuadWidth*ptdDst->GetPixWidth();
      ULONG *pulDstPixel = &pulFirstInTopMap[iFirstDstQuadPixel];
      PIX pixSrc = iTileX*iSrcMipQuadWidth + iTileY*iSrcMipQuadWidth*iSrcMipWidth;
      PIX pixDst = 0;
      PIX pixDstModulo = ptdDst->GetPixWidth() - pixDstQuadWidth;
      PIX pixSrcStepY = iSrcMipWidth;
      PIX pixSrcStepX = 1;

      if(bFlipY) {
        pixSrcStepY = -pixSrcStepY; 
        pixSrc+=iSrcMipQuadHeight*iSrcMipWidth;
      }
      if(bFlipX) {
        pixSrcStepX = -pixSrcStepX;
        pixSrc+=iSrcMipQuadWidth;
      }

      if(bSwapXY) {
        Swap(pixSrcStepX, pixSrcStepY);
      }

      pixSrcStepY -= pixDstQuadWidth*pixSrcStepX;

      // for each pixel in this quad
      for(PIX pixY=0;pixY<pixDstQuadWidth;pixY++) {
        for(PIX pixX=0;pixX<pixDstQuadWidth;pixX++) {
          if((pulSrcMip[pixSrc]&0xFF000000) > 0x80000000) {
            pulDstPixel[pixDst] = pulSrcMip[pixSrc];
          }
          pixSrc+=pixSrcStepX;
          pixDst++;
        }
        pixDst+=pixDstModulo;
        pixSrc+=pixSrcStepY;
      }
      iMaskIndex++;
    }
    iMaskIndex+=iMaskStepX;
  }
}

void CTerrain::UpdateTopMap(INDEX iTileIndex, Rect *prcDest/*=NULL*/)
{
  //ReGenerateTopMap(this, iTileIndex);
  
  if(iTileIndex==(-1)) {
    ctGlobalTopMaps++;
  } else {
    ctGeneratedTopMaps++;
  }

  FIX16_16 fiMaskDiv = 1;
  INDEX iFirstInMask = 0;
  INDEX iMaskWidth = tr_pixHeightMapWidth;
  INDEX iTiling = 1;
  INDEX iSrcMipWidth = 1;
  

  // destionation texture (must have set allocated memory)
  CTextureData *ptdDest;

  // if global top map
  if(iTileIndex==(-1)) {
    ptdDest = &tr_tdTopMap;
  // else tile top map
  } else {
    CTerrainTile &tt = tr_attTiles[iTileIndex];
    ptdDest = tt.GetTopMap();
    fiMaskDiv = tr_ctTilesX;
    iFirstInMask = iMaskWidth * tt.tt_iOffsetZ * (tr_ctVerticesInTileRow-1) + (tt.tt_iOffsetX * (tr_ctVerticesInTileRow-1));
  }

  ASSERT(ptdDest->td_pulFrames==NULL);
  PrepareSharedTopMapMemory(ptdDest, iTileIndex);

  ASSERT(ptdDest!=NULL);
  ASSERT(ptdDest->td_pulFrames!=NULL);
 
  // ASSERT(ptdDest->GetPixWidth()>0 && ptdDest->GetPixHeight()>0 && ptdDest->GetPixWidth()==ptdDest->GetPixHeight());
  // iTiling = ClampDn(iTiling,(INDEX)1);
  // INDEX iSrcMipWidth = ClampDn(ptdDest->GetWidth()/iTiling,(INDEX)1);


  INDEX ctLayers = tr_atlLayers.Count();
  // for each layer
  for(INDEX itl=0;itl<ctLayers;itl++) {
    CTerrainLayer &tl = tr_atlLayers[itl];
    // if layer isn't visible
    if(!tl.tl_bVisible) {
      // skip it
      continue;
    }
    if(tl.tl_ltType==LT_TILE) {
      AddTileLayerToTopMap(this,iTileIndex,itl);
      continue;
    }

    if(iTileIndex==(-1)) {
      // ptdDest = &tr_tdTopMap;
      iTiling = (INDEX)(tr_ctTilesX*tr_ctQuadsInTileRow*tl.tl_fStretchX);

    // else tile top map
    } else {
      CTerrainTile &tt = tr_attTiles[iTileIndex];
      // ptdDest = tt.GetTopMap();
      fiMaskDiv = tr_ctTilesX;
      iFirstInMask = iMaskWidth * tt.tt_iOffsetZ * (tr_ctVerticesInTileRow-1)
                   + (tt.tt_iOffsetX * (tr_ctVerticesInTileRow-1));
      iTiling = (INDEX)(tr_ctQuadsInTileRow*tl.tl_fStretchX);
    }

    ASSERT(ptdDest->GetPixWidth()>0 && ptdDest->GetPixHeight()>0 && ptdDest->GetPixWidth()==ptdDest->GetPixHeight());
    ASSERT(iTiling>=1);
    iTiling = ClampDn(iTiling,(INDEX)1);

    // get source texture
    CTextureData *ptdSrc = tl.tl_ptdTexture;
    INDEX iSrcMipWidth  = ClampDn( ptdSrc->GetPixWidth() /iTiling, 1L);
    INDEX iSrcMipHeight = ClampDn( ptdSrc->GetPixHeight()/iTiling, 1L);

    // Get mipmap of source texture
    INDEX immW = FastLog2( ptdSrc->GetPixWidth()  / iSrcMipWidth);
    INDEX immH = FastLog2( ptdSrc->GetPixHeight() / iSrcMipHeight);
    // get address of first byte in source mipmap
    INDEX imm = Max( immW, immH);
    INDEX iMipAdr = GetMipmapOffset(imm,ptdSrc->GetPixWidth(),ptdSrc->GetPixHeight());
 
    // Mask thing
    // get first byte in layer mask
    UBYTE *ubFirstInMask = &tl.tl_aubColors[iFirstInMask];
    // get first byte in edge map
    UBYTE *ubFirstInEdgeMap = &tr_aubEdgeMap[iFirstInMask];
    FIX16_16 fiHMaskStep = FIX16_16(iMaskWidth-1) / FIX16_16(ptdDest->GetWidth()-1) / fiMaskDiv;
    FIX16_16 fiVMaskStep = FIX16_16(iMaskWidth-1) / FIX16_16(ptdDest->GetWidth()-1) / fiMaskDiv;

    SLONG xHMaskStep = fiHMaskStep.slHolder;
    SLONG xVMaskStep = fiVMaskStep.slHolder;
    SLONG xMaskVPos=0;
    
    // get first byte in destination texture
    ULONG *pulTexDst = (ULONG*)&ptdDest->td_pulFrames[0];
    // get first byte in source texture
    ULONG *pulFirstInMipSrc = (ULONG*)&ptdSrc->td_pulFrames[iMipAdr];
  
    // for each row
    for(UINT ir=0;ir<ptdDest->GetPixHeight();ir++)
    {
      // get first byte for src mip texture in this row
      ULONG *pulSrcRow = &pulFirstInMipSrc[(ir&(iSrcMipWidth-1))*iSrcMipWidth];//%
      INDEX iMaskVPos = (INDEX)(xMaskVPos>>16) * (iMaskWidth);
      UBYTE *pubMaskRow = &ubFirstInMask[iMaskVPos];
      UBYTE *pubEdgeMaskRow = &ubFirstInEdgeMap[iMaskVPos];
      SLONG xMaskHPos = 0;
      // for each column
      for(UINT ic=0;ic<ptdDest->GetPixWidth();ic++)
      {
        ULONG *ulSrc = &pulSrcRow[ic&(iSrcMipWidth-1)];
        INDEX iMask = (INDEX)(xMaskHPos>>16);

        SLONG x1 = (SLONG)(pubMaskRow[iMask+0]) <<0; //NormByteToFixInt(pubMaskRow[iMask]);
        SLONG x2 = (SLONG)(pubMaskRow[iMask+1]) <<0; //NormByteToFixInt(pubMaskRow[iMask+1]);
        SLONG x3 = (SLONG)(pubMaskRow[iMask+iMaskWidth+0]) <<0;//NormByteToFixInt(pubMaskRow[iMask+iMaskWidth+0]);
        SLONG x4 = (SLONG)(pubMaskRow[iMask+iMaskWidth+1]) <<0;//NormByteToFixInt(pubMaskRow[iMask+iMaskWidth+1]);
        SLONG xFactH = xMaskHPos - (xMaskHPos&0xFFFF0000);
        SLONG xFactV = xMaskVPos - (xMaskVPos&0xFFFF0000);
        
        SLONG xStrengthX1 = (x1<<7) + (SLONG)(((x2-x1)*xFactH)>>9); //Lerp(fi1,fi2,fiFactH);
        SLONG xStrengthX2 = (x3<<7) + (SLONG)(((x4-x3)*xFactH)>>9); //Lerp(fi3,fi4,fiFactH);
        SLONG xStrength   = (xStrengthX1<<1) + (SLONG)((((xStrengthX2>>0)-(xStrengthX1>>0))*xFactV)>>15);   //Lerp(fiStrengthX1,fiStrengthX2,fiFactV);
        
        GFXColor *pcolSrc = (GFXColor*)pulTexDst;
        GFXColor *pcolDst = (GFXColor*)ulSrc;
        pcolSrc->ub.r = (BYTE)( (ULONG)pcolSrc->ub.r + ((((ULONG)pcolDst->ub.r - (ULONG)pcolSrc->ub.r) * xStrength)>>16));
        pcolSrc->ub.g = (BYTE)( (ULONG)pcolSrc->ub.g + ((((ULONG)pcolDst->ub.g - (ULONG)pcolSrc->ub.g) * xStrength)>>16));
        pcolSrc->ub.b = (BYTE)( (ULONG)pcolSrc->ub.b + ((((ULONG)pcolDst->ub.b - (ULONG)pcolSrc->ub.b) * xStrength)>>16));
        pcolSrc->ub.a = pubEdgeMaskRow[iMask];
        
        pulTexDst++;
        xMaskHPos += xHMaskStep;
      }
      xMaskVPos += xVMaskStep;
    }
  }
  // make mipmaps
  INDEX ctMipMaps = GetNoOfMipmaps(ptdDest->GetPixWidth(),ptdDest->GetPixHeight());
  MakeMipmaps(ctMipMaps, ptdDest->td_pulFrames, ptdDest->GetPixWidth(), ptdDest->GetPixHeight());

  #pragma message(">> Fix DitherMipmaps")
  INDEX iDithering = 4;
  DitherMipmaps(iDithering,ptdDest->td_pulFrames,ptdDest->td_pulFrames,ptdDest->GetPixWidth(),ptdDest->GetPixHeight());
  // force topmap upload
  ptdDest->SetAsCurrent(0,TRUE);

  // Free shared memory
  FreeSharedTopMapMemory(ptdDest, iTileIndex);
}

void CTerrain::GetAllTerrainBBox(FLOATaabbox3D &bbox)
{
  // Get last quad tree level
  INDEX ctqtl = tr_aqtlQuadTreeLevels.Count();
  QuadTreeLevel &qtl = tr_aqtlQuadTreeLevels[ctqtl-1];

  ASSERT(qtl.qtl_ctNodes==1);
  // Get quad tree node for last level
  QuadTreeNode  &qtn = tr_aqtnQuadTreeNodes[qtl.qtl_iFirstNode];
  bbox = qtn.qtn_aabbox;
}

// Get shading color from tex coords in shading map
COLOR CTerrain::GetShadeColor(CShadingInfo *psi)
{
  ASSERT(psi!=NULL);
  ASSERT(tr_auwShadingMap!=NULL);

  PIX pixShadowU = Clamp(psi->si_pixShadowU,0L,GetShadingMapWidth()-2L);
  PIX pixShadowV = Clamp(psi->si_pixShadowV,0L,GetShadingMapHeight()-2L);
  FLOAT fUDRatio = psi->si_fUDRatio;
  FLOAT fLRRatio = psi->si_fLRRatio;

  PIX pixWidth  = GetShadingMapWidth();
  PIX pixShadow = pixShadowU + pixShadowV*pixWidth;
  UWORD auwShade[4];
  SLONG aslr[4], aslg[4], aslb[4];

  auwShade[0] = tr_auwShadingMap[pixShadow];
  auwShade[1] = tr_auwShadingMap[pixShadow+1];
  auwShade[2] = tr_auwShadingMap[pixShadow+pixWidth+0];
  auwShade[3] = tr_auwShadingMap[pixShadow+pixWidth+1];

  for(INDEX ish=0;ish<4;ish++) {
    aslr[ish] = (auwShade[ish]&0x7C00)>>10;
    aslg[ish] = (auwShade[ish]&0x03E0)>> 5;
    aslb[ish] = (auwShade[ish]&0x001F)>> 0;

    aslr[ish] = (aslr[ish]<<3) | (aslr[ish]>>2);
    aslg[ish] = (aslg[ish]<<3) | (aslg[ish]>>2);
    aslb[ish] = (aslb[ish]<<3) | (aslb[ish]>>2);
  }

  SLONG slRed   = Lerp( Lerp(aslr[0], aslr[1], fLRRatio), Lerp(aslr[2], aslr[3], fLRRatio), fUDRatio);
  SLONG slGreen = Lerp( Lerp(aslg[0], aslg[1], fLRRatio), Lerp(aslg[2], aslg[3], fLRRatio), fUDRatio);
  SLONG slBlue  = Lerp( Lerp(aslb[0], aslb[1], fLRRatio), Lerp(aslb[2], aslb[3], fLRRatio), fUDRatio);

  ULONG ulPixel = ((slRed  <<24)&0xFF000000) |
                  ((slGreen<<16)&0x00FF0000) | 
                  ((slBlue << 8)&0x0000FF00) | 0xFF;

  return ulPixel;
}

// Get plane from given point
FLOATplane3D CTerrain::GetPlaneFromPoint(FLOAT3D &vAbsPoint)
{
  ASSERT(tr_penEntity!=NULL);
  FLOAT3D vRelPoint = (vAbsPoint-tr_penEntity->en_plPlacement.pl_PositionVector) * !tr_penEntity->en_mRotation;
  vRelPoint(1) /= tr_vStretch(1);
  vRelPoint(3) /= tr_vStretch(3);
  PIX pixX = (PIX) floor(vRelPoint(1));
  PIX pixZ = (PIX) floor(vRelPoint(3));
  PIX pixWidth = tr_pixHeightMapWidth;
  FLOAT fXRatio = vRelPoint(1) - pixX;
  FLOAT fZRatio = vRelPoint(3) - pixZ;

  INDEX iPix = pixX + pixZ*pixWidth;
  BOOL bFacing = (iPix)&1;

  FLOAT3D vx0 = FLOAT3D((pixX+0)*tr_vStretch(1),tr_auwHeightMap[iPix]           * tr_vStretch(2) ,(pixZ+0)*tr_vStretch(3));
  FLOAT3D vx1 = FLOAT3D((pixX+1)*tr_vStretch(1),tr_auwHeightMap[iPix+1]         * tr_vStretch(2) ,(pixZ+0)*tr_vStretch(3));
  FLOAT3D vx2 = FLOAT3D((pixX+0)*tr_vStretch(1),tr_auwHeightMap[iPix+pixWidth]  * tr_vStretch(2) ,(pixZ+1)*tr_vStretch(3));
  FLOAT3D vx3 = FLOAT3D((pixX+1)*tr_vStretch(1),tr_auwHeightMap[iPix+pixWidth+1]* tr_vStretch(2) ,(pixZ+1)*tr_vStretch(3));

  vx0 = vx0 * tr_penEntity->en_mRotation + tr_penEntity->en_plPlacement.pl_PositionVector;
  vx1 = vx1 * tr_penEntity->en_mRotation + tr_penEntity->en_plPlacement.pl_PositionVector;
  vx2 = vx2 * tr_penEntity->en_mRotation + tr_penEntity->en_plPlacement.pl_PositionVector;
  vx3 = vx3 * tr_penEntity->en_mRotation + tr_penEntity->en_plPlacement.pl_PositionVector;

  if(bFacing) {
    if(fXRatio>=fZRatio) {
      return FLOATplane3D(vx0,vx2,vx1);
    } else {
      return FLOATplane3D(vx1,vx2,vx3);
    }
  } else {
    if(fXRatio>=fZRatio) {
      return FLOATplane3D(vx2,vx3,vx0);
    } else {
      return FLOATplane3D(vx0,vx3,vx1);
    }
  }
}

// Sets number of quads in row of one tile
void CTerrain::SetQuadsPerTileRow(INDEX ctQuadsPerTileRow)
{
  tr_ctQuadsInTileRow = Clamp(ctQuadsPerTileRow,(INDEX)4,(INDEX)(tr_pixHeightMapWidth-1));
  if(tr_ctQuadsInTileRow!=ctQuadsPerTileRow) {
    CPrintF("Warning: Quads per tile has been changed from requested %d to %d\n",ctQuadsPerTileRow,tr_ctQuadsInTileRow);
  }
  // TODO: Assert that it is 2^n
  tr_ctVerticesInTileRow = tr_ctQuadsInTileRow+1;
}

// Set Terrain stretch
void CTerrain::SetTerrainStretch(FLOAT3D vStretch)
{
  tr_vStretch = vStretch;
}

// Build terrain data
void CTerrain::BuildTerrainData()
{
  // Allocate space for terrain tiles
  tr_ctTilesX = (tr_pixHeightMapWidth-1)  / tr_ctQuadsInTileRow;
  tr_ctTilesY = (tr_pixHeightMapHeight-1) / tr_ctQuadsInTileRow;
  tr_ctTiles  = tr_ctTilesX*tr_ctTilesY;
  tr_attTiles.New(tr_ctTiles);

  // Calculate max posible lod
  INDEX ctVtxInLod = tr_ctQuadsInTileRow;
  tr_iMaxTileLod = 0;
  while(ctVtxInLod>2) {
    tr_iMaxTileLod++;
    ctVtxInLod = ctVtxInLod>>1;
  }

  // Allocate memory for terrain tile arrays
  tr_aArrayHolders.New(tr_iMaxTileLod+1);
  INDEX ctah  = tr_aArrayHolders.Count();
  // for each array handler
  for(INDEX iah=0;iah<ctah;iah++) {
    CArrayHolder &ah = tr_aArrayHolders[iah];
    // set its lod index
    ah.ah_iLod = iah;
    ah.ah_ptrTerrain = this;
  }

  // for each tile row
  for(INDEX iy=0;iy<tr_ctTilesY;iy++) {
    // for each tile col
    for(INDEX ix=0;ix<tr_ctTilesX;ix++) {
      // Initialize terrain tile
      UINT iTileIndex = ix+iy*tr_ctTilesX;
      CTerrainTile &tt = tr_attTiles[iTileIndex];
      tt.tt_iIndex = iTileIndex;
      tt.tt_iOffsetX = ix;
      tt.tt_iOffsetZ = iy;
      tt.tt_ctVtxX   = tr_ctVerticesInTileRow;
      tt.tt_ctVtxY   = tr_ctVerticesInTileRow;
      tt.tt_ctLodVtxX = tt.tt_ctVtxX;
      tt.tt_ctLodVtxY = tt.tt_ctVtxY;
      tt.tt_iLod = 0;
      tt.tt_fLodLerpFactor = 0;
      // Reset tile neighbours
      tt.tt_aiNeighbours[NB_TOP]   =-1; tt.tt_aiNeighbours[NB_LEFT]  =-1;
      tt.tt_aiNeighbours[NB_BOTTOM]=-1; tt.tt_aiNeighbours[NB_RIGHT] =-1;
      // Set tile neighbours
      if(iy>0) tt.tt_aiNeighbours[NB_TOP]  = iTileIndex-tr_ctTilesX;
      if(ix>0) tt.tt_aiNeighbours[NB_LEFT] = iTileIndex-1;
      if(iy<tr_ctTilesX-1) tt.tt_aiNeighbours[NB_BOTTOM] = iTileIndex+tr_ctTilesX;
      if(ix<tr_ctTilesY-1) tt.tt_aiNeighbours[NB_RIGHT]  = iTileIndex+1;
    }
  }
}

/*
#if 1
void CTerrain::GenerateTopMap(INDEX iTileIndex)
{
  FIX16_16 fiMaskDiv = 1;
  INDEX iFirstInMask = 0;
  INDEX iMaskWidth = tr_pixHeightMapWidth;
  INDEX iTiling = (INDEX)(tr_ctTilesX*tr_ctQuadsInTileRow*tr_fTexStretch);

  // destionation texture (must have set allocated memory)
  CTextureData *ptdDest;
  // if global top map
  if(iTileIndex==(-1)) {
    ptdDest = &tr_tdTopMap;
  // else tile top map
  } else {
    CTerrainTile &tt = tr_attTiles[iTileIndex];
    ptdDest = tt.GetTopMap();
    fiMaskDiv = tr_ctTilesX;
    iFirstInMask = iMaskWidth * tt.tt_iOffsetZ * (tr_ctVerticesInTileRow-1) + (tt.tt_iOffsetX * (tr_ctVerticesInTileRow-1));
    iTiling = (INDEX)(tr_ctQuadsInTileRow*tr_fTexStretch);
  }
 
  ASSERT(ptdDest->GetPixWidth()>0 && ptdDest->GetPixHeight()>0 && ptdDest->GetPixWidth()==ptdDest->GetPixHeight());
  INDEX iSrcMipWidth = ClampDn(ptdDest->GetWidth()/iTiling,(INDEX)1);


  INDEX ctLayers = tr_atlLayers.Count();
  // for each layer
  for(INDEX itl=0;itl<ctLayers;itl++) {
    CTerrainLayer &tl = tr_atlLayers[itl];
    // get source texture
    CTextureData *ptdSrc = tl.tl_ptdTexture;
    // Get mipmap of source texture
    INDEX imm = FastLog2(ptdSrc->GetPixWidth()/iSrcMipWidth);
    // get address of first byte in source mipmap
    INDEX iMipAdr = GetMipmapOffset(imm,ptdSrc->GetPixWidth(),ptdSrc->GetPixHeight());
 
    // Mask thing
    // get first byte in layer mask
    UBYTE *ubFirstInMask = &tl.tl_aubColors[iFirstInMask];
    FIX16_16 fiHMaskStep = FIX16_16(iMaskWidth-1)/FIX16_16(ptdDest->GetWidth()-1)/fiMaskDiv;
    FIX16_16 fiVMaskStep = FIX16_16(iMaskWidth-1)/FIX16_16(ptdDest->GetWidth()-1)/fiMaskDiv;

    SLONG xHMaskStep = fiHMaskStep.slHolder;
    SLONG xVMaskStep = fiVMaskStep.slHolder;
    SLONG xMaskVPos=0;
    
    // get first byte in destination texture
    ULONG *pulTexDst = (ULONG*)&ptdDest->td_pulFrames[0];
    // get first byte in source texture
    ULONG *pulFirstInMipSrc = (ULONG*)&ptdSrc->td_pulFrames[iMipAdr];
  
    // for each row
    for(UINT ir=0;ir<ptdDest->GetHeight();ir++) {
      // get first byte for src mip texture in this row
      ULONG *pulSrcRow = &pulFirstInMipSrc[(ir&(iSrcMipWidth-1))*iSrcMipWidth];//%
      INDEX iMaskVPos = (INDEX)(xMaskVPos>>16) * (iMaskWidth);
      UBYTE *pubMaskRow = &ubFirstInMask[iMaskVPos];
      SLONG xMaskHPos = 0;
      // for each column
      for(UINT ic=0;ic<ptdDest->GetWidth();ic++) {

        ULONG *ulSrc = &pulSrcRow[ic&(iSrcMipWidth-1)];
        INDEX iMask = (INDEX)(xMaskHPos>>16);

        SLONG x1 = (SLONG)(pubMaskRow[iMask+0]) <<0; //NormByteToFixInt(pubMaskRow[iMask]);
        SLONG x2 = (SLONG)(pubMaskRow[iMask+1]) <<0; //NormByteToFixInt(pubMaskRow[iMask+1]);
        SLONG x3 = (SLONG)(pubMaskRow[iMask+iMaskWidth+0]) <<0;//NormByteToFixInt(pubMaskRow[iMask+iMaskWidth+0]);
        SLONG x4 = (SLONG)(pubMaskRow[iMask+iMaskWidth+1]) <<0;//NormByteToFixInt(pubMaskRow[iMask+iMaskWidth+1]);
        SLONG xFactH = xMaskHPos - (xMaskHPos&0xFFFF0000);
        SLONG xFactV = xMaskVPos - (xMaskVPos&0xFFFF0000);
        
        SLONG xStrengthX1 = (x1<<7) + (SLONG)(((x2-x1)*xFactH)>>9); //Lerp(fi1,fi2,fiFactH);
        SLONG xStrengthX2 = (x3<<7) + (SLONG)(((x4-x3)*xFactH)>>9); //Lerp(fi3,fi4,fiFactH);
        SLONG xStrength   = (xStrengthX1<<1) + (SLONG)((((xStrengthX2>>0)-(xStrengthX1>>0))*xFactV)>>15);   //Lerp(fiStrengthX1,fiStrengthX2,fiFactV);
        
        GFXColor *pcolSrc = (GFXColor*)pulTexDst;
        GFXColor *pcolDst = (GFXColor*)ulSrc;
        pcolSrc->r = (BYTE)( (ULONG)pcolSrc->r + ((((ULONG)pcolDst->r - (ULONG)pcolSrc->r) * xStrength)>>16));
        pcolSrc->g = (BYTE)( (ULONG)pcolSrc->g + ((((ULONG)pcolDst->g - (ULONG)pcolSrc->g) * xStrength)>>16));
        pcolSrc->b = (BYTE)( (ULONG)pcolSrc->b + ((((ULONG)pcolDst->b - (ULONG)pcolSrc->b) * xStrength)>>16));
        pcolSrc->a = 255;
        
        pulTexDst++;
        xMaskHPos += xHMaskStep;
      }
      xMaskVPos += xVMaskStep;
    }
  }
  // make mipmaps
  MakeMipmaps(32, ptdDest->td_pulFrames, ptdDest->GetWidth(), ptdDest->GetHeight());
  // force topmap upload
  ptdDest->SetAsCurrent(0,TRUE);
}
#else
void CTerrain::GenerateTopMap(INDEX iTileIndex)
{
  INDEX iMaskDiv = 1;
  INDEX iFirstInMask = 0;
  INDEX iMaskWidth = tr_pixHeightMapWidth;
  INDEX iTiling = (INDEX)(tr_ctTilesX*tr_ctQuadsInTileRow*tr_fTexStretch);

  // destionation texture (must have set required width and height)
  CTextureData *ptdDest;
  // if global top map
  if(iTileIndex==(-1)) {
    ptdDest = &tr_tdTopMap;
  // else tile top map
  } else {
    CTerrainTile &tt = tr_attTiles[iTileIndex];
    ptdDest = &tt.GetTopMap();
    iMaskDiv = tr_ctTilesX;
    iFirstInMask = iMaskWidth * tt.tt_iOffsetZ * (tr_ctVerticesInTileRow-1) + (tt.tt_iOffsetX * (tr_ctVerticesInTileRow-1));
    iTiling = (INDEX)(tr_ctQuadsInTileRow*tr_fTexStretch);
  }
 
  ASSERT(ptdDest->GetPixWidth()>0 && ptdDest->GetPixHeight()>0 && ptdDest->GetPixWidth()==ptdDest->GetPixHeight());

  INDEX iSrcMipWidth = ptdDest->GetWidth()/iTiling;

  INDEX ctLayers = tr_atlLayers.Count();
  // for each layer
  for(INDEX ilr=0;ilr<ctLayers;ilr++) {
    CTerrainLayer &tl = tr_atlLayers[ilr];
    // get source texture
    CTextureData *ptdSrc = tl.tl_ptdTexture;
    // Get mipmap of source texture
    INDEX imm = FastLog2(ptdSrc->GetPixWidth()/iSrcMipWidth);
    // get address of first byte in source mipmap
    INDEX iMipAdr = GetMipmapOffset(imm,ptdSrc->GetPixWidth(),ptdSrc->GetPixHeight());
 
    // Mask thing
    // get first byte in layer mask
    UBYTE *ubFirstInMask = &tl.tl_aubColors[iFirstInMask];
    FLOAT fHMaskStep = FLOAT(iMaskWidth-1)/(ptdDest->GetWidth()-1)/iMaskDiv;
    FLOAT fVMaskStep = FLOAT(iMaskWidth-1)/(ptdDest->GetWidth()-1)/iMaskDiv;
    FLOAT fMaskVPos=0;
    
    // get first byte in destination texture
    ULONG *pulTexDst = (ULONG*)&ptdDest->td_pulFrames[0];
    // get first byte in source texture
    ULONG *pulFirstInMipSrc = &ptdSrc->td_pulFrames[iMipAdr];
  
    // for each row
    for(UINT ir=0;ir<ptdDest->GetWidth();ir++) {
      // get first byte for src mip texture in this row
      ULONG *pulSrcRow = &pulFirstInMipSrc[(ir&(iSrcMipWidth-1))*iSrcMipWidth];//%
      INDEX iMaskVPos = (INDEX)fMaskVPos * (iMaskWidth);
      UBYTE *pubMaskRow = &ubFirstInMask[iMaskVPos];
      FLOAT fMaskHPos = 0;
      // for each column
      for(UINT ic=0;ic<ptdDest->GetWidth();ic++) {

        ULONG *ulSrc = &pulSrcRow[ic&(iSrcMipWidth-1)];
        INDEX iMask = (INDEX)fMaskHPos;
        FLOAT f1 = NormByteToFloat(pubMaskRow[iMask]);
        FLOAT f2 = NormByteToFloat(pubMaskRow[iMask+1]);
        FLOAT f3 = NormByteToFloat(pubMaskRow[iMask+iMaskWidth+0]);
        FLOAT f4 = NormByteToFloat(pubMaskRow[iMask+iMaskWidth+1]);
        FLOAT fStrengthX1 = Lerp(f1,f2,fMaskHPos-(INDEX)fMaskHPos);
        FLOAT fStrengthX2 = Lerp(f3,f4,fMaskHPos-(INDEX)fMaskHPos);
        FLOAT fStrength   = Lerp(fStrengthX1,fStrengthX2,fMaskVPos-(INDEX)fMaskVPos);

        CopyPixel(pulTexDst,ulSrc,fStrength);
        pulTexDst++;
        fMaskHPos+=fHMaskStep;
      }
      fMaskVPos+=fVMaskStep;
    }
  }
  // make mipmaps
  MakeMipmaps( 32, ptdDest->td_pulFrames, ptdDest->GetWidth(), ptdDest->GetHeight());
  // force topmap upload
  ptdDest->SetAsCurrent(0,TRUE);
}
#endif
*/

void CTerrain::GenerateTerrainTopMap()
{
  CreateTopMap(tr_tdTopMap,tr_pixTopMapWidth,tr_pixTopMapHeight);
  UpdateTopMap(-1);
}

// Add default layer
void CTerrain::AddDefaultLayer_t(void)
{
  // Add one layer using default texture, but do not refresh terrain 
  CTerrainLayer &tl = AddLayer_t((CTString)"Textures\\Editor\\Default.TEX", LT_NORMAL, FALSE);
  // fill this layer
  tl.ResetLayerMask(255);
}

// Build quadtree for terrain
void CTerrain::BuildQuadTree(void)
{
  INDEX ctQuadNodeRows = tr_ctTilesX;
  INDEX ctQuadNodeCols = tr_ctTilesY;
  INDEX ctQuadNodes = 0;

  // Create quad tree levels
  while(TRUE) {
    QuadTreeLevel &qtl = tr_aqtlQuadTreeLevels.Push();
    // Remember first node
    qtl.qtl_iFirstNode = ctQuadNodes;
    // Add nodes in this level to total node count
    ctQuadNodes += ClampDn(ctQuadNodeRows,(INDEX)1) * ClampDn(ctQuadNodeCols,(INDEX)1);

    // Count nodes in this level
    qtl.qtl_ctNodes    = ctQuadNodes - qtl.qtl_iFirstNode;
    qtl.qtl_ctNodesCol = ctQuadNodeCols;
    qtl.qtl_ctNodesRow = ctQuadNodeRows;
    
    // if only one node is in this level
    if(qtl.qtl_ctNodes == 1) {
      // this is last level so exit loop
      break;
    }
    if(ctQuadNodeCols%2 == 1 && ctQuadNodeCols != 1) {
      ctQuadNodeCols = (ctQuadNodeCols+1)>>1;
    } else {
      ctQuadNodeCols = ctQuadNodeCols>>1;
    }

    if(ctQuadNodeRows%2 == 1 && ctQuadNodeRows != 1) {
      ctQuadNodeRows = (ctQuadNodeRows+1)>>1;
    } else {
      ctQuadNodeRows = ctQuadNodeRows>>1;
    }
  }
  
  QuadTreeLevel &qtlFirst = tr_aqtlQuadTreeLevels[0];
  // Add quadtree nodes for first level
  tr_aqtnQuadTreeNodes.Push(qtlFirst.qtl_ctNodes);
  // for each quad tree node in first level
  for(INDEX iqn=0;iqn<qtlFirst.qtl_ctNodes;iqn++) {
    // Generate vertices for tile in first lod with no vertex lerping
    CTerrainTile &tt = tr_attTiles[iqn];
    QuadTreeNode &qtn = tr_aqtnQuadTreeNodes[iqn];
    tt.tt_iLod = -1;
    tt.tt_iRequestedLod  = 0;
    tt.tt_fLodLerpFactor = 0;
    tt.AddFlag(TT_REGENERATE|TT_NO_TOPMAP_REGEN);
    ReGenerateTile(iqn);
    tt.RemoveFlag(TT_REGENERATE|TT_NO_TOPMAP_REGEN);

    // Set quad tree bbox as first vertex in tile
    GFXVertex4 &vtxFirst = tt.GetVertices()[0];
    qtn.qtn_aabbox = FLOATaabbox3D(FLOAT3D(vtxFirst.x,vtxFirst.y,vtxFirst.z));
    // for each vertex after first
    INDEX ctVtx = tt.GetVertices().Count();
    for(INDEX ivx=1;ivx<ctVtx;ivx++) {
      // Add vertex in quad tree node bbox
      GFXVertex4 &vtx = tt.GetVertices()[ivx];
      qtn.qtn_aabbox |= FLOATaabbox3D(FLOAT3D(vtx.x,vtx.y,vtx.z));
    }
    // release arrays of tile
    tt.ReleaseTileArrays();
    tt.tt_iLod = -1;
    tt.tt_iRequestedLod = 0;
    qtn.qtn_iTileIndex = iqn;
    // nodes in first level does not have children
    qtn.qtn_iChild[0] = -1;
    qtn.qtn_iChild[1] = -1;
    qtn.qtn_iChild[2] = -1;
    qtn.qtn_iChild[3] = -1;
  }

  // Create all other levels of quad tree
  INDEX ctQuadLevels = tr_aqtlQuadTreeLevels.Count();
  // for each quadtree level after first
  for(INDEX iql=1;iql<ctQuadLevels;iql++) {
    QuadTreeLevel &qtl = tr_aqtlQuadTreeLevels[iql];
    QuadTreeLevel &qtlPrev = tr_aqtlQuadTreeLevels[iql-1];
    // for each quadtree node row
    for(INDEX ir=0;ir<qtlPrev.qtl_ctNodesRow;ir+=2) {
      // for each quadtree node col
      for(INDEX ic=0;ic<qtlPrev.qtl_ctNodesCol;ic+=2) {
        // Set quadtree node children 
        INDEX iqt = qtlPrev.qtl_iFirstNode + ic+ir*qtlPrev.qtl_ctNodesCol;
        QuadTreeNode &qtn = tr_aqtnQuadTreeNodes.Push();
        QuadTreeNode *pqtnFirst = &tr_aqtnQuadTreeNodes[iqt];
        // Set first child node
        qtn.qtn_aabbox = pqtnFirst->qtn_aabbox;
        qtn.qtn_iChild[0] = iqt;
        qtn.qtn_iChild[1] = -1;
        qtn.qtn_iChild[2] = -1;
        qtn.qtn_iChild[3] = -1;
        qtn.qtn_iTileIndex = -1; 
        // If second child node exists
        if(ic+1<qtlPrev.qtl_ctNodesCol) {
          // Set second child
          qtn.qtn_iChild[1] = iqt+1;
          qtn.qtn_aabbox |= pqtnFirst[1].qtn_aabbox;
        }
        // if fourth child exist
        if(ir+1<qtlPrev.qtl_ctNodesRow) {
          // Set third child
          qtn.qtn_iChild[2] = iqt+qtlPrev.qtl_ctNodesCol;
          qtn.qtn_aabbox |= pqtnFirst[qtlPrev.qtl_ctNodesCol].qtn_aabbox;
          // Set fourth child
          if(ic+1<qtlPrev.qtl_ctNodesCol) {
            qtn.qtn_iChild[3] = iqt+qtlPrev.qtl_ctNodesCol+1;
            qtn.qtn_aabbox |= pqtnFirst[qtlPrev.qtl_ctNodesCol+1].qtn_aabbox;
          }
        }
      }
    }
  }
}


// Update higher quadtree levels from first one
void CTerrain::UpdateQuadTree()
{
  // for all quad tree levels after first one
  INDEX ctqtl=tr_aqtlQuadTreeLevels.Count();
  for(INDEX iqtl=1;iqtl<ctqtl;iqtl++) {
    QuadTreeLevel &qtl = tr_aqtlQuadTreeLevels[iqtl];

    // for each quad tree node in this level
    INDEX ctqtn=qtl.qtl_iFirstNode+qtl.qtl_ctNodes;
    for(INDEX iqtn=qtl.qtl_iFirstNode;iqtn<ctqtn;iqtn++) {
      QuadTreeNode &qtn = tr_aqtnQuadTreeNodes[iqtn];
      // trash qtn box
      qtn.qtn_aabbox.maxvect = FLOAT3D(-1,-1,-1);
      qtn.qtn_aabbox.minvect = FLOAT3D( 1, 1, 1);

      // for each child of qtn
      for(INDEX ichild=0;ichild<4;ichild++) {
        INDEX iqtnChild = qtn.qtn_iChild[ichild];
        // if child exists
        if(iqtnChild!=(-1)) {
          QuadTreeNode &qtnChild = tr_aqtnQuadTreeNodes[iqtnChild];
          // if qtn box is empty 
          if(qtn.qtn_aabbox.IsEmpty()) {
            // set qtn box as box of this child 
            qtn.qtn_aabbox = qtnChild.qtn_aabbox;
          // if it had some data
          } else {
            // just add child box to qtn box
            qtn.qtn_aabbox |= qtnChild.qtn_aabbox;
          }
        }
      }
    }
  }
}


/*
 * Generation
 */ 

void CTerrain::ReGenerate(void)
{
  // for each tile in terrain
  for(INDEX it=0;it<tr_ctTiles;it++) {
    CTerrainTile &tt = tr_attTiles[it];
    // calculate new lod
    tt.tt_iRequestedLod = tt.CalculateLOD();
  }

  // for each tile that is waiting in regen queue
  INDEX ctrt = tr_auiRegenList.Count();
  INDEX irt;
  for(irt=0;irt<ctrt;irt++) {
    // mark tile as ready for regeneration
    INDEX iTileIndex = tr_auiRegenList[irt];
    CTerrainTile &tt = tr_attTiles[iTileIndex];
    tt.AddFlag(TT_REGENERATE);
  }

  // for each tile that is waiting in regen queue
  for(irt=0;irt<ctrt;irt++) {
    INDEX iTileIndex = tr_auiRegenList[irt];
    CTerrainTile &tt = tr_attTiles[iTileIndex];
    // if tile needs to be regenerated
    if(tt.GetFlags() & TT_REGENERATE) {
      // Regenerate it now
      ReGenerateTile(tt.tt_iIndex);
      // remove flag for regeneration
      tt.RemoveFlag(TT_REGENERATE);
    }
  }

  // clear regenration list
  ClearRegenList();
}

extern CStaticStackArray<GFXVertex4> _avLerpedVerices;
static void ShowTerrainInfo(CAnyProjection3D &apr, CDrawPort *pdp, CTerrain *ptrTerrain)
{
  pdp->SetFont( _pfdConsoleFont);
  pdp->SetTextAspect( 1.0f);
  pdp->SetOrtho();
  CTString strInfo;
  INDEX ctTopMaps = ptrTerrain->tr_atdTopMaps.Count() + 1;
  strInfo.PrintF("Tris = %d\nNodes = %d\nDelayed nodes = %d\nTop maps = %d\nTexgens = %d, %d\nShadowmap updates = %d\n",
                 _ctTris,_ctNodesVis,_ctDelayedNodes,ctTopMaps,ctGeneratedTopMaps,ctGlobalTopMaps,_ctShadowMapUpdates);

  CStaticStackArray<INDEX> iaLodInfo;
  iaLodInfo.Push(ptrTerrain->tr_iMaxTileLod+1);
  memset(&iaLodInfo[0],0,sizeof(INDEX)*iaLodInfo.sa_Count);
  // build lod info
  for(INDEX it=0;it<ptrTerrain->tr_ctTiles;it++) {
    CTerrainTile &tt = ptrTerrain->tr_attTiles[it];
    INDEX &ili = iaLodInfo[tt.tt_iLod];
    ili++;
  }
  // Show how many tiles are in witch lod
  CTString strTemp = "LodInfo:\n";
  for(INDEX itti=0;itti<ptrTerrain->tr_iMaxTileLod+1;itti++) {
    CTString str;
    CArrayHolder &ah = ptrTerrain->tr_aArrayHolders[itti];
    str.PrintF("L%d = mem = %d KB, nodes = %d\n",itti, ah.GetUsedMemory()/1024, iaLodInfo[itti]);
    strTemp += str;
  }
  strTemp += "\n";
  strInfo +=strTemp;

  // Show memory usage
  SLONG slUsedMemory=0;
  // Height map usage
  SLONG slHeightMap = ptrTerrain->tr_pixHeightMapWidth*ptrTerrain->tr_pixHeightMapHeight*sizeof(UWORD);
  // Edge map usage
  SLONG slEdgeMap = ptrTerrain->tr_pixHeightMapWidth*ptrTerrain->tr_pixHeightMapHeight*sizeof(UBYTE);
  // Shadow map usage
  SLONG slShadowMap = ptrTerrain->tr_tdShadowMap.GetUsedMemory();
  // Quad tree usage
  SLONG slQTNodes  = sizeof(QuadTreeNode)*ptrTerrain->tr_aqtnQuadTreeNodes.Count();
  SLONG slQTLevels = sizeof(QuadTreeLevel)*ptrTerrain->tr_aqtlQuadTreeLevels.Count();
  // Tiles usage 
  SLONG slTiles = 0;
  INDEX cttt = ptrTerrain->tr_ctTiles;
  for(INDEX itt=0;itt<cttt;itt++) {
    CTerrainTile &tt = ptrTerrain->tr_attTiles[itt];
    slTiles+=tt.GetUsedMemory();
  }
  // Arrays holders usage
  SLONG slArrayHoldes=0;
  INDEX ctah=ptrTerrain->tr_aArrayHolders.Count();
  for(INDEX iah=0;iah<ctah;iah++) {
    CArrayHolder &ah = ptrTerrain->tr_aArrayHolders[iah];
    slArrayHoldes+=ah.GetUsedMemory();
  }
  SLONG slLayers=0;
  // Terrain layers usage
  INDEX cttl = ptrTerrain->tr_atlLayers.Count();
  for(INDEX itl=0;itl<cttl;itl++) {
    CTerrainLayer &tl = ptrTerrain->tr_atlLayers[itl];
    slLayers+=tl.GetUsedMemory();
  }
  SLONG slTopMaps=0;
  // Top maps usage
  INDEX cttm=ptrTerrain->tr_atdTopMaps.Count();
  for(INDEX itm=0;itm<cttm;itm++) {
    CTextureData *ptdTopMap = &ptrTerrain->tr_atdTopMaps[itm];
    slTopMaps+=ptdTopMap->GetUsedMemory();
  }
  SLONG slGlobalTopMap = ptrTerrain->tr_tdTopMap.GetUsedMemory();
  SLONG slTileBatchingSize = GetUsedMemoryForTileBatching();
  SLONG slVertexSmoothing  = _avLerpedVerices.sa_Count * sizeof(GFXVertex4);
  extern SLONG  _slSharedTopMapSize; // Shared top map size
  // Global top map usage
  SLONG slTotal = slHeightMap+slEdgeMap+slShadowMap+slQTNodes+slQTLevels+slTiles+slArrayHoldes+slLayers+
                  slTopMaps+slGlobalTopMap+slTileBatchingSize+slVertexSmoothing;
  CTString strMemoryUsed;
  strMemoryUsed.PrintF("Heightmap = %d KB\nEdgemap   = %d KB\nShadowMap = %d KB\nQuadTree  = %d KB\nTiles     = %d KB\nArrays    = %d KB\nLayers    = %d KB\nTopMaps   = %d KB\nGlobal TM = %d KB\nShared TM = %d KB\nVtx lerp  = %d KB\nBatching  = %d KB\nTotal     = %d KB\n",
                        slHeightMap/1024,slEdgeMap/1024,slShadowMap/1024,(slQTNodes+slQTLevels)/1024,slTiles/1024,slArrayHoldes/1024,slLayers/1024,slTopMaps/1024,slGlobalTopMap/1024,_slSharedTopMapSize/1024,slVertexSmoothing/1024,slTileBatchingSize/1024,slTotal/1024);


  strInfo += strMemoryUsed;

  extern FLOAT3D _vDirection;
  strInfo += CTString(0,"Shadow map size = %d,%d [%d]\nShading map size= %d,%d [%d]\n",
                      ptrTerrain->GetShadowMapWidth(), ptrTerrain->GetShadowMapHeight(),
                      ptrTerrain->tr_iShadowMapSizeAspect,ptrTerrain->GetShadingMapWidth(),
                      ptrTerrain->GetShadingMapHeight(),ptrTerrain->tr_iShadingMapSizeAspect);
  pdp->PutText(strInfo,0,40);
}







static void ReadOldShadowMap(CTerrain *ptrTerrain, CTStream *istrFile)
{
  // Read terrain shadow map
  // Read shadow map size
  INDEX pixShadowMapWidth;
  INDEX pixShadowMapHeight;
  (*istrFile)>>pixShadowMapWidth;
  (*istrFile)>>pixShadowMapHeight;
  BOOL bHaveShadowMap;
  (*istrFile)>>bHaveShadowMap;
  // is shadow map saved
  if(bHaveShadowMap) {
    // skip reading of first mip of shadow map
    istrFile->Seek_t(pixShadowMapWidth*pixShadowMapHeight*sizeof(GFXColor),CTStream::SD_CUR);
    //istrFile->Read_t(&tr_tdShadowMap.td_pulFrames[0],tr_pixShadowMapWidth*tr_pixShadowMapHeight*sizeof(GFXColor));
  }
  // Set default shadow map size
  ptrTerrain->SetShadowMapsSize(0,0);
}

void CTerrain::ReadVersion_t( CTStream *istrFile, INDEX iSavedVersion)
{
  // set current terrain
  _ptrTerrain = this;

  ASSERT(_CrtCheckMemory());
  PIX pixWidth;
  PIX pixHeight;
  // read height map width and height
  (*istrFile)>>pixWidth;
  (*istrFile)>>pixHeight;

  // Reallocate memory for terrain with size
  AllocateHeightMap(pixWidth,pixHeight);

  // read terrain stretch
  (*istrFile)>>tr_vStretch;
  // read texture stretch
  if(iSavedVersion<6) {
    FLOAT fTemp; // Read temp stretch
    (*istrFile)>>fTemp;
  }
  // read lod distance factor
  (*istrFile)>>tr_fDistFactor;

  if(iSavedVersion>6) {
    // Read terrain size
    (*istrFile)>>tr_vTerrainSize;

    istrFile->ExpectID_t("TRSM"); // 'Terrain shadowmap'

    // if version is smaller than 8
    if(iSavedVersion<8) {
      // read old shadow map format
      ReadOldShadowMap(this,istrFile);
    } else {
      INDEX iShadowMapAspect;
      INDEX iShadingMapAspect;
      (*istrFile)>>iShadowMapAspect;
      (*istrFile)>>iShadingMapAspect;
      SetShadowMapsSize(iShadowMapAspect,iShadingMapAspect);
      INDEX iShadowMapSize = GetShadowMapWidth() * GetShadowMapHeight();
      INDEX iShadingMapSize = GetShadingMapWidth() * GetShadingMapHeight();
      // Read shadow map
      ASSERT(tr_tdShadowMap.td_pulFrames!=NULL);
      for (INDEX i = 0; i < iShadowMapSize; i++)
        (*istrFile)>>tr_tdShadowMap.td_pulFrames[i];
      // Read shading map
      ASSERT(tr_auwShadingMap!=NULL);
      for (INDEX i = 0; i < iShadingMapSize; i++)
        (*istrFile)>>tr_auwShadingMap[i];
    }

    // Create shadow map mipmaps
    INDEX ctMipMaps = GetNoOfMipmaps(GetShadowMapWidth(),GetShadowMapHeight());
    MakeMipmaps(ctMipMaps, tr_tdShadowMap.td_pulFrames, GetShadowMapWidth(), GetShadowMapHeight());
    // Upload shadow map
    tr_tdShadowMap.SetAsCurrent(0,TRUE);

    istrFile->ExpectID_t("TSEN"); // 'Terrain shadowmap end'


    // if there is edge map saved
    if(istrFile->PeekID_t()==CChunkID("TREM")) { // 'Terrain edge map'
      // Read terrain edge map
      istrFile->ExpectID_t("TREM"); // 'Terrain edge map'
      // read edge map
      istrFile->Read_t(&tr_aubEdgeMap[0],tr_pixHeightMapWidth*tr_pixHeightMapHeight);
      istrFile->ExpectID_t("TEEN"); // 'Terrain edge map end'
    }
  }
  
  (*istrFile).ExpectID_t("TRHM");  // 'Terrain heightmap'

  // read height map
  for (ULONG i = 0; i < tr_pixHeightMapWidth*tr_pixHeightMapHeight; i++)
    (*istrFile)>>tr_auwHeightMap[i];
  (*istrFile).ExpectID_t("THEN");  // 'Terrain heightmap end'

  // Terrain will be rebuild in entity.cpp
  _ptrTerrain = NULL;
}

// Read from stream.
void CTerrain::Read_t( CTStream *istrFile)
{
  (*istrFile).ExpectID_t("TERR");  // 'Terrain'
  // read the version number
  INDEX iSavedVersion;
  (*istrFile)>>iSavedVersion;
  // is this version 6
  if(iSavedVersion>4) {
    ReadVersion_t(istrFile,iSavedVersion);
  // else unknown version
  } else {
    // report error
    ThrowF_t( TRANS("The terrain version on disk is %d.\n"
      "Current supported version is %d."), iSavedVersion, _iTerrainVersion);
  }

  // Read Terrain layers
  (*istrFile).ExpectID_t("TRLR");  // 'Terrain layers'
  // Read terrain layers
  INDEX cttl;
  (*istrFile)>>cttl;
  // Create layers
  tr_atlLayers.Push(cttl);
  // for each terrain layer
  for(INDEX itl=0;itl<cttl;itl++) {
    // Read layer texture and mask
    CTerrainLayer &tl = tr_atlLayers[itl];
    tl.Read_t(istrFile,iSavedVersion);
  }
  (*istrFile).ExpectID_t("TLEN");  // 'Terrain layers end'

  // End of reading
  (*istrFile).ExpectID_t("TREN");  // 'terrain end'
}

// Write to stream.
void CTerrain::Write_t( CTStream *ostrFile)
{
  (*ostrFile).WriteID_t("TERR");  // 'Terrain'

  // write the terrain version
  (*ostrFile)<<_iTerrainVersion;
  // write height map width and height
  (*ostrFile)<<tr_pixHeightMapWidth;
  (*ostrFile)<<tr_pixHeightMapHeight;
  // write terrain stretch
  (*ostrFile)<<tr_vStretch;
  // write lod distance factor
  (*ostrFile)<<tr_fDistFactor;
  // Write terrain size
  (*ostrFile)<<tr_vTerrainSize;

  // Write terrain shadow map
  ostrFile->WriteID_t("TRSM");    // 'Terrain shadowmap'

  (*ostrFile)<<tr_iShadowMapSizeAspect;
  (*ostrFile)<<tr_iShadingMapSizeAspect;
  INDEX iShadowMapSize = GetShadowMapWidth() * GetShadowMapHeight() * sizeof(ULONG);
  INDEX iShadingMapSize = GetShadingMapWidth() * GetShadingMapHeight() * sizeof(UWORD);
  // Write shadow map
  ASSERT(tr_tdShadowMap.td_pulFrames!=NULL);
  ostrFile->Write_t(&tr_tdShadowMap.td_pulFrames[0],iShadowMapSize);
  // Write shading map
  ASSERT(tr_auwShadingMap!=NULL);
  ostrFile->Write_t(&tr_auwShadingMap[0],iShadingMapSize);

  ostrFile->WriteID_t("TSEN");    // 'Terrain shadowmap end'

  // if edge map exists
  if(tr_aubEdgeMap!=NULL) {
    ostrFile->WriteID_t("TREM");    // 'Terrain edge map'
    // Write edge map
    ostrFile->Write_t(&tr_aubEdgeMap[0],sizeof(UBYTE)*tr_pixHeightMapWidth*tr_pixHeightMapHeight);
    ostrFile->WriteID_t("TEEN");    // 'Terrain edge map end'
  }

  (*ostrFile).WriteID_t("TRHM");  // 'Terrain heightmap'
  // write height map
  (*ostrFile).Write_t(&tr_auwHeightMap[0],sizeof(UWORD)*tr_pixHeightMapWidth*tr_pixHeightMapHeight);
  (*ostrFile).WriteID_t("THEN");  // 'Terrain heightmap end'

  (*ostrFile).WriteID_t("TRLR");  // 'Terrain layers'
  // write terrain layers
  INDEX cttl = tr_atlLayers.Count();
  (*ostrFile)<<cttl;
  for(INDEX itl=0;itl<cttl;itl++) {
    CTerrainLayer &tl = tr_atlLayers[itl];
    tl.Write_t(ostrFile);
  }

  (*ostrFile).WriteID_t("TLEN");  // 'Terrain layers end'

  (*ostrFile).WriteID_t("TREN");  // 'terrain end'
}

// Copy terrain data from other terrain
void CTerrain::Copy(CTerrain &trOther)
{
  ASSERT(FALSE);
}

CTerrain::~CTerrain()
{
  Clear();
}

// Discard all cached shading info for models
void CTerrain::DiscardShadingInfos(void)
{
  FORDELETELIST( CShadingInfo, si_lnInPolygon, tr_lhShadingInfos, itsi) {
    itsi->si_penEntity->en_ulFlags &= ~ENF_VALIDSHADINGINFO;
    itsi->si_lnInPolygon.Remove();
    itsi->si_pbpoPolygon = NULL;
  }
}

// Clear height map
void CTerrain::ClearHeightMap(void)
{
  // if height map space was allocated
  if(tr_auwHeightMap!=NULL) {
    // release it
    FreeMemory(tr_auwHeightMap);
    tr_auwHeightMap = NULL;
  }
}

// Clear shadow map
void CTerrain::ClearShadowMap(void)
{
  // Clear current terrain shadow map
  tr_tdShadowMap.Clear();

  // Also clear shading map
  if(tr_auwShadingMap!=NULL) {
    FreeMemory(tr_auwShadingMap);
    tr_auwShadingMap = NULL;
  }
}

void CTerrain::ClearEdgeMap(void)
{
  // if space for edge map was allocated
  if(tr_aubEdgeMap!=NULL) {
    // release it
    FreeMemory(tr_aubEdgeMap);
    tr_aubEdgeMap = NULL;
  }
}

// Clear all topmaps
void CTerrain::ClearTopMaps(void)
{
  // for each topmap in terrain
  INDEX cttm = tr_atdTopMaps.Count();
  for(INDEX itm=0;itm<cttm;itm++) {
    CTextureData *ptdTopMap = &tr_atdTopMaps[0];
    // Remove memory pointer cos it is shared memory
    ptdTopMap->td_pulFrames = NULL;
    // Clear tile topmap
    ptdTopMap->Clear();
    // remove topmap from container
    tr_atdTopMaps.Remove(ptdTopMap);
    delete ptdTopMap;
    ptdTopMap = NULL;
  }
  tr_atdTopMaps.Clear();
  ASSERT(_CrtCheckMemory());

  // Remove memory pointer from global top map cos it is shared memory
  tr_tdTopMap.td_pulFrames = NULL;
  // Clear global topmap
  tr_tdTopMap.Clear();
}

// Clear tiles
void CTerrain::ClearTiles(void)
{
  // for each tile
  for(INDEX itt=0;itt<tr_ctTiles;itt++) {
    CTerrainTile &tt = tr_attTiles[itt];
    // Clear tile
    tt.Clear();
  }
  tr_attTiles.Clear();
  tr_ctTiles  = 0;
  tr_ctTilesX = 0;
  tr_ctTilesY = 0;
}

// Clear arrays
void CTerrain::ClearArrays(void)
{
  // Clear Array holders
  tr_aArrayHolders.Clear();
}

// Clear quadtree
void CTerrain::ClearQuadTree(void)
{
  // Destroy quad tree nodes
  tr_aqtnQuadTreeNodes.Clear();
  // Clear quad tree levels
  tr_aqtlQuadTreeLevels.Clear();
}

// Clear layers
void CTerrain::ClearLayers(void)
{
  // Clear layers
  tr_atlLayers.Clear();
}

// Clean terrain data (does not remove layers)
void CTerrain::Clean(BOOL bCleanLayers/*=TRUE*/)
{
  ASSERT(FALSE);
  ClearHeightMap();
  ClearShadowMap();
  ClearEdgeMap();
  ClearTopMaps();
  ClearTiles();
  ClearArrays();
  ClearQuadTree();

  // if layers clear is required
  if(bCleanLayers) {
    ClearLayers();
  }
}

// Clear terrain data
void CTerrain::Clear(void)
{
  DiscardShadingInfos();
  ClearHeightMap();
  ClearShadowMap();
  ClearEdgeMap();
  ClearTopMaps();
  ClearTiles();
  ClearArrays();
  ClearQuadTree();
  ClearLayers();

  if(tr_ptdDetailMap!=NULL) {
    _pTextureStock->Release(tr_ptdDetailMap);
    tr_ptdDetailMap = NULL;
  }
}


