/* 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. */

// TerrainInterface.cpp : implementation file
//

#include "stdafx.h"
#include "WorldEditor.h"
#include "TerrainInterface.h"
#include <Engine/Templates/Stock_CTextureData.h>

#ifdef _DEBUG
#undef new
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define RENDERED_BRUSH_SIZE_IN_METERS 32.0f
#define CHANGE_ANGLE_SENSITIVITY (1.0f/1.0f)
#define CHANGE_INDEX_SENSITIVITY (1.0f/16.0f)
#define CHANGE_NORMALIZED_SENSITIVITY (1.0f/100.0f)
#define CHANGE_PREASSURE_SENSITIVITY (1.0f)
#define CHANGE_OFFSET_SENSITIVITY 0.1f
#define CHANGE_STRETCH_SENSITIVITY 1.0f
#define LAYER_START_X 48
#define LAYER_HEIGHT 48
#define LAYER_SPACING_V PIX(4)
#define COL_ODD_LAYERS (RGBToColor(25,25,30)|CT_OPAQUE)
#define COL_EVEN_LAYERS (RGBToColor(40,40,50)|CT_OPAQUE)
COLOR _colSelectedLayerBcg=0x500000FF;
#define COL_SELECTED_LAYER _colSelectedLayerBcg
#define SLIDER_WIDTH 12
#define SLIDER_HOLDER_HEIGHT 32

#define COL_DEFAULT_ITEM (C_YELLOW|CT_OPAQUE)
#define COL_SETTINGS  (C_vlBLUE|CT_OPAQUE)
#define COL_SEPARATOR (C_WHITE|CT_OPAQUE)

static FLOAT _fScrollLayers=0;
INDEX _iGenerateForLayer;
Rect _rectInvalid;
CUpdateableRT _udAutoGenerateDistribution;
CChangeableRT _chAutoGenerateDistribution;
CTerrain *_ptrLastEditedTerrain=NULL;

CTerrainEditBrush atebDefaultEditBrushValues[]=
{
  {0,1}, {0,2}, {0,4}, {0,6},
  {0,8}, {0,12}, {0,16}, {0,24},
  {0,32}, {0,40}, {0,52}, {0,63},
  
  {1,1}, {2,2}, {4,4}, {6,6},
  {8,8}, {12,12}, {16,16}, {24,24},
  {32,32}, {40,40}, {52,52}, {63,63},
  
  {2,4}, {3,6}, {4,8}, {6,12},
  {8,16}, {12,24}, {16,32}, {32,63},
};
CTerrainEditBrush atebCustomEditBrushes[CT_BRUSHES];
static CTextureObject _toIcons;
static CDynamicContainer<CTIButton> dcButtons;

void GenerateTerrainBrushTexture( INDEX iBrush, FLOAT fHotSpot, FLOAT fFallOff);

/////////////////////////////////////////////////////////////////////////////
// CTerrainInterface

static CTString _strToolTip;
static void GetToolTipText(void *pTerrainInterface, char *pToolTipText)
{
  pToolTipText+= sprintf(pToolTipText, "%s", _strToolTip);
}

CTFileName GetBrushTextureName(INDEX iBrush)
{
  CTString strBrushFile;
  strBrushFile.PrintF("Textures\\Editor\\TerrainBrush%02d.tex", iBrush);
  return strBrushFile;
}

void CTerrainInterface::HideCursor(void)
{
  RECT rect;
  GetClientRect( &rect);
  ClientToScreen( &rect);
  ClipCursor(&rect);
  while (ShowCursor(FALSE)>=0);
}

void CTerrainInterface::UnhideCursor(void)
{
  ClipCursor(NULL);
  while (ShowCursor(TRUE)<0);
}

void GetButtonOffset(CTIButton &tib, PIX &pixOffsetX, PIX &pixOffsetY)
{
  if(tib.tib_iLayer!=-1)
  {
    CTerrain *ptrTerrain=GetTerrain();
    if( ptrTerrain==NULL) return;

    pixOffsetX=LAYER_START_X;
    pixOffsetY=(ptrTerrain->tr_atlLayers.Count()-1-tib.tib_iLayer)*LAYER_HEIGHT-_fScrollLayers;
  }
}

#define TERRAIN_SURFACE_VER "V002"
void LoadSurface(CTFileName fnSurface)
{
  CTerrain *ptrTerrain=GetTerrain();
  if( ptrTerrain==NULL) return;

  try
  {
    CTFileStream strmFile;
    strmFile.Open_t( fnSurface);

    strmFile.ExpectID_t( CChunkID( "TRSF"));  // terrain surface
    // check version number
    if( !( CChunkID(TERRAIN_SURFACE_VER) == strmFile.GetID_t()) )
    {
      throw( "Invalid version of terrain surface file. Unable to load");
    }

    INDEX ctLayers=ptrTerrain->tr_atlLayers.Count();
    for(INDEX iDelLayer=0; iDelLayer<ctLayers-1; iDelLayer++)
    {
      ptrTerrain->RemoveLayer(0, FALSE);
    }
    ptrTerrain->tr_atlLayers[0].ResetLayerMask(0xFF);

    ctLayers=0;
    strmFile>>ctLayers;

    for( INDEX iLayer=0; iLayer<ctLayers; iLayer++)
    {
      CTFileName fnTexture;
      strmFile>>fnTexture;
      // check if texture exists
      if(!FileExists( fnTexture))
      {
        fnTexture=CTFILENAME("Textures\\Editor\\Default.tex");
      }

      CTerrainLayer *ptlLayer=&ptrTerrain->tr_atlLayers[0];
      if( iLayer!=0)
      {
        ptlLayer=&ptrTerrain->AddLayer_t(fnTexture, LT_NORMAL, FALSE);
      }
      else
      {
        ptlLayer->SetLayerTexture_t(fnTexture);
      }

      // load surface
      strmFile>>ptlLayer->tl_fRotateX;
      strmFile>>ptlLayer->tl_fRotateY;
      strmFile>>ptlLayer->tl_fStretchX;
      strmFile>>ptlLayer->tl_fStretchY;
      strmFile>>ptlLayer->tl_fOffsetX;
      strmFile>>ptlLayer->tl_fOffsetY;

      strmFile>>ptlLayer->tl_bAutoRegenerated;
      strmFile>>ptlLayer->tl_fCoverage;
      strmFile>>ptlLayer->tl_fCoverageNoise;
      strmFile>>ptlLayer->tl_fCoverageRandom;

      strmFile>>ptlLayer->tl_bApplyMinAltitude;
      strmFile>>ptlLayer->tl_fMinAltitude;
      strmFile>>ptlLayer->tl_fMinAltitudeFade;
      strmFile>>ptlLayer->tl_fMinAltitudeNoise;
      strmFile>>ptlLayer->tl_fMinAltitudeRandom;

      strmFile>>ptlLayer->tl_bApplyMaxAltitude;
      strmFile>>ptlLayer->tl_fMaxAltitude;
      strmFile>>ptlLayer->tl_fMaxAltitudeFade;
      strmFile>>ptlLayer->tl_fMaxAltitudeNoise;
      strmFile>>ptlLayer->tl_fMaxAltitudeRandom;

      strmFile>>ptlLayer->tl_bApplyMinSlope;
      strmFile>>ptlLayer->tl_fMinSlope;
      strmFile>>ptlLayer->tl_fMinSlopeFade;
      strmFile>>ptlLayer->tl_fMinSlopeNoise;
      strmFile>>ptlLayer->tl_fMinSlopeRandom;

      strmFile>>ptlLayer->tl_bApplyMaxSlope;
      strmFile>>ptlLayer->tl_fMaxSlope;
      strmFile>>ptlLayer->tl_fMaxSlopeFade;
      strmFile>>ptlLayer->tl_fMaxSlopeNoise;
      strmFile>>ptlLayer->tl_fMaxSlopeRandom;
      
      strmFile>>ptlLayer->tl_colMultiply;
    }

    strmFile.Close();
  }
  catch (const char *strError)
  {
    WarningMessage(strError);
    return;
  }

  GenerateLayerDistribution(-1);
  theApp.m_ctTerrainPage.MarkChanged();
  ptrTerrain->RefreshTerrain();
}

void SaveSurface(CTFileName fnSurface)
{
  CTerrain *ptrTerrain=GetTerrain();
  if( ptrTerrain==NULL) return;

  try
  {
    CTFileStream strmFile;
    strmFile.Create_t( fnSurface);

    strmFile.WriteID_t( CChunkID( "TRSF"));  // terrain surface
    // write version number
    strmFile.WriteID_t(TERRAIN_SURFACE_VER);

    INDEX ctLayers=ptrTerrain->tr_atlLayers.Count();
    strmFile<<ctLayers;

    for( INDEX iLayer=0; iLayer<ctLayers; iLayer++)
    {
      CTerrainLayer *ptlLayer=&ptrTerrain->tr_atlLayers[iLayer];

      if(ptlLayer->tl_ptdTexture==NULL) continue;
      CTFileName fnTexture=ptlLayer->tl_ptdTexture->GetName();

      strmFile<<fnTexture;

      // save surface
      strmFile<<ptlLayer->tl_fRotateX;
      strmFile<<ptlLayer->tl_fRotateY;
      strmFile<<ptlLayer->tl_fStretchX;
      strmFile<<ptlLayer->tl_fStretchY;
      strmFile<<ptlLayer->tl_fOffsetX;
      strmFile<<ptlLayer->tl_fOffsetY;

      strmFile<<ptlLayer->tl_bAutoRegenerated;
      strmFile<<ptlLayer->tl_fCoverage;
      strmFile<<ptlLayer->tl_fCoverageNoise;
      strmFile<<ptlLayer->tl_fCoverageRandom;

      strmFile<<ptlLayer->tl_bApplyMinAltitude;
      strmFile<<ptlLayer->tl_fMinAltitude;
      strmFile<<ptlLayer->tl_fMinAltitudeFade;
      strmFile<<ptlLayer->tl_fMinAltitudeNoise;
      strmFile<<ptlLayer->tl_fMinAltitudeRandom;

      strmFile<<ptlLayer->tl_bApplyMaxAltitude;
      strmFile<<ptlLayer->tl_fMaxAltitude;
      strmFile<<ptlLayer->tl_fMaxAltitudeFade;
      strmFile<<ptlLayer->tl_fMaxAltitudeNoise;
      strmFile<<ptlLayer->tl_fMaxAltitudeRandom;

      strmFile<<ptlLayer->tl_bApplyMinSlope;
      strmFile<<ptlLayer->tl_fMinSlope;
      strmFile<<ptlLayer->tl_fMinSlopeFade;
      strmFile<<ptlLayer->tl_fMinSlopeNoise;
      strmFile<<ptlLayer->tl_fMinSlopeRandom;

      strmFile<<ptlLayer->tl_bApplyMaxSlope;
      strmFile<<ptlLayer->tl_fMaxSlope;
      strmFile<<ptlLayer->tl_fMaxSlopeFade;
      strmFile<<ptlLayer->tl_fMaxSlopeNoise;
      strmFile<<ptlLayer->tl_fMaxSlopeRandom;

      strmFile<<ptlLayer->tl_colMultiply;
    }

    strmFile.Close();
  }
  catch (const char *strError)
  {
    WarningMessage(strError);
    return;
  }
}

void PointToIconSpace(CPoint &pt, CTIButton *ptib)
{
  PIX pixOffsetX=0;
  PIX pixOffsetY=0;
  GetButtonOffset(*ptib, pixOffsetX, pixOffsetY);
  PIX x=ptib->tib_fx+pixOffsetX;
  PIX y=ptib->tib_fy+pixOffsetY;
  pt.x=Clamp(pt.x-x, INDEX(0), INDEX(ptib->tib_fdx));
  pt.y=Clamp(pt.y-y, INDEX(0), INDEX(ptib->tib_fdy));
}

void PointToScreenSpace(CPoint &pt, CDrawPort *pdp)
{
  HWND hWnd=pdp->dp_Raster->ra_pvpViewPort->vp_hWnd;
  ClientToScreen(hWnd, &pt);
}

PIXaabbox2D GetButtonScreenBox(CTIButton &tib)
{
  PIX pixOffsetX=0;
  PIX pixOffsetY=0;
  GetButtonOffset(tib, pixOffsetX, pixOffsetY);
  PIX x=tib.tib_fx+pixOffsetX;
  PIX y=tib.tib_fy+pixOffsetY;
  PIX dx=tib.tib_fdx;
  PIX dy=tib.tib_fdy;
  PIXaabbox2D boxScreen=PIXaabbox2D( PIX2D(x,y), PIX2D(x+dx, y+dy));
  return boxScreen;
}

void GenerateDefaultBrush(INDEX iBrush)
{
  atebCustomEditBrushes[iBrush].teb_fHotSpot=atebDefaultEditBrushValues[iBrush].teb_fHotSpot;
  atebCustomEditBrushes[iBrush].teb_fFallOff=atebDefaultEditBrushValues[iBrush].teb_fFallOff;
  GenerateTerrainBrushTexture( iBrush, 
    atebCustomEditBrushes[iBrush].teb_fHotSpot-0.1f, atebCustomEditBrushes[iBrush].teb_fFallOff-0.1f);
}

void SetDefaultEditBrushes(void)
{
  for(INDEX iBrush=0; iBrush<CT_BRUSHES; iBrush++)
  {
    GenerateDefaultBrush(iBrush);
  }
}

void GenerateNonExistingTerrainEditBrushes(void)
{
  for(INDEX iBrush=0; iBrush<CT_BRUSHES; iBrush++)
  {
    // copy default values as custom ones
    atebCustomEditBrushes[iBrush].teb_fHotSpot=atebDefaultEditBrushValues[iBrush].teb_fHotSpot;
    atebCustomEditBrushes[iBrush].teb_fFallOff=atebDefaultEditBrushValues[iBrush].teb_fFallOff;

    CTFileName fnBrush=GetBrushTextureName(iBrush);
    if(!FileExists(fnBrush))
    {
      GenerateDefaultBrush(iBrush);
    }
    else
    {
      // precache existing brushes
      try
      {
        _pTextureStock->Obtain_t(fnBrush);
      }
      catch (const char *strError)
      {
        (void) strError;
      }
    }
  }
}

CTerrainInterface::CTerrainInterface()
{
  m_pDrawPort = NULL;
  m_pViewPort = NULL;
  _fScrollLayers=0;
  _udAutoGenerateDistribution.MarkUpdated();
}

CTerrainInterface::~CTerrainInterface()
{
}


BEGIN_MESSAGE_MAP(CTerrainInterface, CWnd)
	//{{AFX_MSG_MAP(CTerrainInterface)
	ON_WM_PAINT()
	ON_WM_DESTROY()
	ON_WM_LBUTTONDOWN()
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONUP()
	ON_WM_RBUTTONDOWN()
	ON_WM_RBUTTONUP()
	ON_WM_LBUTTONDBLCLK()
	ON_WM_DROPFILES()
	ON_WM_CREATE()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CTerrainInterface message handlers

void CTerrainInterface::OnPaint() 
{
#if ALLOW_TERRAINS
  {
  CPaintDC dc(this);
  }

  if( (m_pViewPort == NULL) && (m_pDrawPort == NULL) )
  {
    // initialize canvas for active texture button
    _pGfx->CreateWindowCanvas( m_hWnd, &m_pViewPort, &m_pDrawPort);
  }

  // if there is a valid drawport, and the drawport can be locked
  if( (m_pDrawPort != NULL) && (m_pDrawPort->Lock()) )
  {
    if(dcButtons.Count()==0 || GetTerrain()!=_ptrLastEditedTerrain)
    {
      InitializeInterface(m_pDrawPort);
      _ptrLastEditedTerrain=GetTerrain();
    }

    PIXaabbox2D rectPict;
    rectPict = PIXaabbox2D( PIX2D(0, 0),
                            PIX2D(m_pDrawPort->GetWidth(), m_pDrawPort->GetHeight()));
    // clear texture area to black
    m_pDrawPort->Fill( C_BLACK | CT_OPAQUE);
    // erase z-buffer
    m_pDrawPort->FillZBuffer(ZBUF_BACK);

    RenderInterface(m_pDrawPort);
    
    // unlock the drawport
    m_pDrawPort->Unlock();

    if (m_pViewPort!=NULL)
    {
      m_pViewPort->SwapBuffers();
    }
  }
#endif
}

void CTerrainInterface::RenderInterface(CDrawPort *pdp) 
{
#if ALLOW_TERRAINS
  // render terrain interface buttons
  FOREACHINDYNAMICCONTAINER( dcButtons, CTIButton, ittib)
  {
    CTIButton &tib=*ittib;
    PIX pixOffsetX=0;
    PIX pixOffsetY=0;
    GetButtonOffset(tib, pixOffsetX, pixOffsetY);
    if( tib.tib_pPreRender!=NULL) tib.tib_pPreRender(&tib, pdp);

    PIX x=tib.tib_fx+pixOffsetX;
    PIX y=tib.tib_fy+pixOffsetY;
    PIX dx=tib.tib_fdx;
    PIX dy=tib.tib_fdy;

    // bcg icon fill
    if( tib.tib_colFill&0xFF)
    {
      if(pdp->Lock())
      {
        pdp->Fill(x,y,dx,dy, tib.tib_colFill);
        pdp->Unlock();
      }
    }
    
    // icon texture
    if( tib.tib_iIcon!=-1)
    {
      PIXaabbox2D boxScreen=PIXaabbox2D( PIX2D(x,y), PIX2D(x+dx, y+dy));
      MEXaabbox2D boxTexture=MEXaabbox2D( MEX2D(16*tib.tib_iIcon,0), MEX2D(16*tib.tib_iIcon+16,16));
      COLOR colMultiply=C_WHITE|CT_OPAQUE;
      if(tib.tib_pIsEnabled!=NULL && !tib.tib_pIsEnabled(&tib))
      {
        colMultiply=C_dGRAY|CT_OPAQUE;
      }
      pdp->PutTexture( &_toIcons, boxScreen, boxTexture, colMultiply);
    }
    
    // custom render
    if(tib.tib_pOnRender!=NULL) tib.tib_pOnRender(&tib, pdp);
    
    // icon border
    if( tib.tib_colBorderColor&0xFF)
    {
      if(pdp->Lock())
      {
        pdp->DrawBorder(x,y,dx,dy, tib.tib_colBorderColor);
        pdp->Unlock();
      }
    }
  }
#endif
}

PIXaabbox2D GetLayersBox(CDrawPort *pdp)
{
  return PIXaabbox2D( PIX2D(LAYER_START_X,0), PIX2D(pdp->GetWidth()-LAYER_START_X, pdp->GetHeight()));
}

PIX2D GetLayerSize(CDrawPort *pdp)
{
  return PIX2D(pdp->GetWidth()-LAYER_START_X, LAYER_HEIGHT);
}

void CTerrainInterface::OnDestroy() 
{
	CWnd::OnDestroy();

  if( m_pViewPort != NULL)
  {
    _pGfx->DestroyWindowCanvas( m_pViewPort);
    m_pViewPort = NULL;
  }

  m_pViewPort = NULL;
  m_pDrawPort = NULL;
}

void GenerateTerrainBrushTexture( INDEX iBrush, FLOAT fHotSpot, FLOAT fFallOff)
{
#if ALLOW_TERRAINS
  INDEX iLog2=INDEX(ceil(Log2(fFallOff*2+2)));
  PIX pixSize=1UL<<(iLog2+1);
  FLOAT fcx=pixSize/2.0f;
  FLOAT fcy=pixSize/2.0f;
  CImageInfo ii;
  ii.ii_Width=pixSize;
  ii.ii_Height=pixSize;
  ii.ii_BitsPerPixel=32;
  ii.ii_Picture=(UBYTE*) AllocMemory(ii.ii_Width*ii.ii_Height*ii.ii_BitsPerPixel/8);
  COLOR *pcol=(COLOR *)ii.ii_Picture;

  for(INDEX iy=0; iy<ii.ii_Height; iy++)
  {
    for(INDEX ix=0; ix<ii.ii_Width; ix++)
    {
      FLOAT fDist=sqrt((fcx-ix)*(fcx-ix)+(fcy-iy)*(fcy-iy));
      FLOAT fcolPower=1.0f;
      if(fDist>fFallOff) fcolPower=0.0f;
      else if(fDist>fHotSpot)
      {
        fcolPower=CalculateRatio(fDist, fHotSpot, fFallOff, 0.0f, 1.0f);
        fcolPower=1.0f-(1.0f-fcolPower)*(1.0f-fcolPower);
      }
      UBYTE ubCol=fcolPower*255.0f;
      COLOR col=RGBToColor(ubCol,ubCol,ubCol)|CT_OPAQUE;
      *pcol=ByteSwap(col);
      pcol++;
    }
  }
  CTextureData tdBrush;
  try
  {
    tdBrush.Create_t( &ii, pixSize, 16, TRUE);
    CTString strBrushFile;
    strBrushFile.PrintF("Textures\\Editor\\TerrainBrush%02d.tex", iBrush);
    tdBrush.Save_t( strBrushFile);
    tdBrush.Reload();
  }
  catch (const char *strError)
  {
    (void) strError;
    WarningMessage("Unable to create terrain brush texture!");
  }
#endif
}

CTIButton::CTIButton()
{
  tib_fx=0.0f;
  tib_fy=0.0f;
  tib_fdx=0.0f;
  tib_fdy=0.0f;
  tib_iIcon=-1;
  tib_colBorderColor=0;
  tib_fDataMin=0.0f;
  tib_fDataMax=0.0f;
  tib_bWrapData=TRUE;
  tib_fDataDelta=0.0f;
  tib_pfData1=NULL;
  tib_pfData2=NULL;
  tib_iLayer=-1;
  tib_strToolTip="Unknown tool tip";
  tib_pPreRender=NULL;
  tib_pOnRender=NULL;
  tib_pOnLeftClick=NULL;
  tib_pOnLeftClickMove=NULL;
  tib_pOnRightClick=NULL;
  tib_pOnRightClickMove=NULL;
  tib_pOnDropFiles=NULL;
  tib_pGetClickMoveData=NULL;
  tib_pIsEnabled=NULL;
      
  tib_bMouseTrapForMove=TRUE;
  tib_bContinueTesting=FALSE;
}

CTIButton *AddButton(CDynamicContainer<CTIButton> &dc, FLOAT x, FLOAT y, FLOAT dx, FLOAT dy, INDEX iLayer, INDEX iIcon,
                     CTString strToolTip, COLOR colBorder=C_RED|CT_TRANSPARENT, COLOR colFill=C_BLACK|CT_TRANSPARENT)
{
  CTIButton &tib=*(new CTIButton);
  tib.tib_fx=x;
  tib.tib_fy=y;
  tib.tib_fdx=dx;
  tib.tib_fdy=dy;
  tib.tib_iLayer=iLayer;
  tib.tib_iIcon=iIcon;
  tib.tib_strToolTip=strToolTip;
  tib.tib_colBorderColor=colBorder;
  tib.tib_colFill=colFill;
  dc.Add(&tib);
  return &tib;
}

void CTIButton::SetData( FLOAT fDataMin, FLOAT fDataMax, FLOAT fDataDelta, BOOL bWrap/*=FALSE*/, FLOAT *pfData1/*=NULL*/, FLOAT *pfData2/*=NULL*/)
{
  tib_fDataMin=fDataMin;
  tib_fDataMax=fDataMax;
  tib_fDataDelta=fDataDelta;
  tib_bWrapData=bWrap;
  tib_pfData1=pfData1;
  tib_pfData2=pfData2;
}

void CTIButton::SetFunctions(
  void (*pOnRender)(CTIButton *ptib, CDrawPort *pdp)/*=NULL*/,
  void (*pOnLeftClick)(CTIButton *ptib, CPoint pt, CDrawPort *pdp)/*=NULL*/,
  void (*pOnLeftClickMove)(CTIButton *ptib, FLOAT fdx, FLOAT fdy, CDrawPort *pdp)/*=NULL*/,
  void (*pOnRightClick)(CTIButton *ptib, CPoint pt, CDrawPort *pdp)/*=NULL*/,
  void (*pOnRightClickMove)(CTIButton *ptib, FLOAT fdx, FLOAT fdy, CDrawPort *pdp)/*=NULL*/,
  void (*pPreRender)(CTIButton *ptib, CDrawPort *pdp)/*=NULL*/)
{
  tib_pOnRender=pOnRender;
  tib_pOnLeftClick=pOnLeftClick;
  tib_pOnLeftClickMove=pOnLeftClickMove;
  tib_pOnRightClick=pOnRightClick;
  tib_pOnRightClickMove=pOnRightClickMove;
  tib_pPreRender=pPreRender;
}

void RenderBrushNo(CTIButton *ptib, CDrawPort *pdp)
{
  pdp->SetFont( _pfdConsoleFont);
  pdp->SetTextAspect( 1.0f);
  pdp->SetTextScaling( 1.0f);
  CTString strText;
  strText.PrintF("%d", INDEX(theApp.m_fCurrentTerrainBrush));
  pdp->PutTextC( strText, ptib->tib_fx+ptib->tib_fdx/2, ptib->tib_fy+4, C_YELLOW|CT_OPAQUE);
}

void RenderBrushShape( INDEX iBrush, PIXaabbox2D rect, CDrawPort *pdp)
{
  CDrawPort dpBrush=CDrawPort(pdp, rect);
  CTextureObject to;
  CTString strBrushFile;
  strBrushFile.PrintF("Textures\\Editor\\TerrainBrush%02d.tex", iBrush);
  try
  {
    to.SetData_t(strBrushFile);
    CTextureData *ptd=(CTextureData *)to.GetData();
    if( dpBrush.Lock())
    {
      dpBrush.Fill(C_BLACK|CT_OPAQUE);
      PIX pixTexW=ptd->GetPixWidth();
      PIX pixTexH=ptd->GetPixHeight();

      FLOAT fScreenW=dpBrush.GetWidth()*pixTexW/RENDERED_BRUSH_SIZE_IN_METERS;
      FLOAT fScreenH=dpBrush.GetHeight()*pixTexH/RENDERED_BRUSH_SIZE_IN_METERS;

      PIXaabbox2D rectPic;
      rectPic = PIXaabbox2D(
        PIX2D(dpBrush.GetWidth()/2.0f-fScreenW/2.0f, dpBrush.GetHeight()/2.0f-fScreenH/2.0f),
        PIX2D(dpBrush.GetWidth()/2.0f-fScreenW/2.0f+fScreenW, dpBrush.GetHeight()/2.0f-fScreenH/2.0f+fScreenH));
      dpBrush.PutTexture( &to, rectPic);
      dpBrush.Unlock();
    }
  }
  catch (const char *strError)
  {
    (void) strError;
  }
}

void RenderBrushShape(CTIButton *ptib, CDrawPort *pdp)
{
  INDEX iBrush=INDEX(theApp.m_fCurrentTerrainBrush);
  CTerrainEditBrush &teb=atebCustomEditBrushes[iBrush];
  PIXaabbox2D rectButton = PIXaabbox2D(
    PIX2D(ptib->tib_fx, ptib->tib_fy),
    PIX2D(ptib->tib_fx+ptib->tib_fdx, ptib->tib_fy+ptib->tib_fdy));
  RenderBrushShape(iBrush,rectButton,pdp);
}

void RenderBrushPressure(CTIButton *ptib, CDrawPort *pdp)
{
  pdp->SetFont( _pfdConsoleFont);
  pdp->SetTextAspect( 1.0f);
  pdp->SetTextScaling( 1.0f);
  CTString strText;
  strText.PrintF("%d%%", INDEX(theApp.m_fTerrainBrushPressure/1024.0f*100.0f));
  pdp->PutTextC( strText, ptib->tib_fx+ptib->tib_fdx/2, ptib->tib_fy, C_YELLOW|CT_OPAQUE);
}

void RenderLayerTexture(CTIButton *ptib, CDrawPort *pdp)
{
  INDEX iLayer=ptib->tib_iLayer;
  CTerrainLayer *ptlLayer=GetLayer(iLayer);
  if(ptlLayer!=NULL && ptlLayer->tl_ptdTexture!=NULL)
  {
    CTextureObject to;
    to.SetData(ptlLayer->tl_ptdTexture);
    PIXaabbox2D boxScreen=GetButtonScreenBox(*ptib);
    pdp->PutTexture( &to, boxScreen);
  }
}

void RenderLayerBcg(CTIButton *ptib, CDrawPort *pdp)
{
  INDEX iLayer=ptib->tib_iLayer;
  
  CTIButton &tib=*ptib;
  PIX pixOffsetX=0;
  PIX pixOffsetY=0;
  GetButtonOffset(tib, pixOffsetX, pixOffsetY);
  if( tib.tib_pPreRender!=NULL) tib.tib_pPreRender(&tib, pdp);

  PIX x=tib.tib_fx+pixOffsetX;
  PIX y=tib.tib_fy+pixOffsetY;
  PIX dx=tib.tib_fdx;
  PIX dy=tib.tib_fdy;

  COLOR colBcg=C_WHITE|CT_OPAQUE;
  if( tib.tib_iLayer==GetLayerIndex())                colBcg=COL_SELECTED_LAYER;
  else if( iLayer&1)                                  colBcg=COL_ODD_LAYERS;
  else                                                colBcg=COL_EVEN_LAYERS;

  if(pdp->Lock())
  {
    pdp->Fill(x,y,dx,dy, colBcg);
    pdp->Unlock();
  }
}

void RenderLayerMask(CTIButton *ptib, CDrawPort *pdp)
{
  INDEX iLayer=ptib->tib_iLayer;
  CTerrainLayer *ptlLayer=GetLayer(iLayer);
  if(ptlLayer!=NULL && ptlLayer->tl_ptdTexture!=NULL)
  {
    CTextureObject to;
    CTextureData *ptd=ptlLayer->GetThumbnail(64,64);
    to.SetData(ptd);
    PIXaabbox2D boxScreen=GetButtonScreenBox(*ptib);
    pdp->PutTexture( &to, boxScreen);
  }
}

void GetBrushModeInfo(INDEX iMode, INDEX &iIcon, CTString &strText)
{
  strText="Unknown";
  iIcon=0;
  switch(INDEX (iMode))
  {
  case TBM_PAINT:
    {
      strText="Paint (J)";
      if(INDEX(theApp.m_iTerrainEditMode)==TEM_HEIGHTMAP) iIcon=12;
      else                                                iIcon=8;
      break;
    }
  case TBM_SMOOTH:          strText="Smooth tool (O)"; iIcon=10; break;
  case TBM_FILTER:          strText="Filter tool (F)"; iIcon=30; break;
  case TBM_MINIMUM:         strText="Limit down (M)"; iIcon=27; break;
  case TBM_MAXIMUM:         strText="Limit up (X)"; iIcon=11; break;
  case TBM_FLATTEN:         strText="Flatten (=)"; iIcon=18; break;
  case TBM_POSTERIZE:       strText="Posterize (;)"; iIcon=28; break;
  case TBM_RND_NOISE:       strText="Random noise (K)"; iIcon=21; break;
  case TBM_CONTINOUS_NOISE: strText="Texture noise (Y)"; iIcon=31; break;
  case TBM_ERASE:           strText="Erase (D)"; iIcon=9; break;
  }
}

void UpdateEditModeIcon(CTIButton *ptib, CDrawPort *pdp)
{
  if(INDEX(theApp.m_iTerrainEditMode)==TEM_HEIGHTMAP) ptib->tib_iIcon=20;
  else                                                ptib->tib_iIcon=19;
}

void UpdateBrushModeIcon(CTIButton *ptib, CDrawPort *pdp)
{
  INDEX iIcon;
  CTString strText;
  GetBrushModeInfo(INDEX(theApp.m_iTerrainBrushMode), iIcon, strText);
  ptib->tib_iIcon=iIcon;
}

void UpdateLayerVisibleFlag(CTIButton *ptib, CDrawPort *pdp)
{
  INDEX iLayer=ptib->tib_iLayer;
  CTerrainLayer *ptlLayer=GetLayer(iLayer);
  if(ptlLayer!=NULL)
  {
    if( ptlLayer->tl_bVisible) ptib->tib_iIcon=3;
    else                       ptib->tib_iIcon=4;
  }
}

void UpdatePressure(CTIButton *ptib, CDrawPort *pdp)
{
  if(theApp.m_fTerrainBrushPressureEnum>=0)
  {
    theApp.m_fTerrainBrushPressure=(theApp.m_fTerrainBrushPressureEnum+1)/10.0f*1024.0f+1;
    theApp.m_fTerrainBrushPressureEnum=-1;
  }
}

void SetAsActiveLayer(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  SelectLayer(ptib->tib_iLayer);
  CWorldEditorDoc* pDoc = theApp.GetActiveDocument();
  if(pDoc!=NULL)
  {
    pDoc->m_chSelections.MarkChanged();
  }
}


void ApplyLayerCommand(INDEX iSelectedItem)
{
  CMainFrame* pMainFrame = STATIC_DOWNCAST(CMainFrame, AfxGetMainWnd());
  CTerrain *ptrTerrain=GetTerrain();
  if( ptrTerrain==NULL) return;
  CTerrainLayer *ptlLayer=GetLayer();
  INDEX iLayer=GetLayerIndex();
  if(ptlLayer!=NULL)
  {
    switch( iSelectedItem)
    {
    case 0: // "Delete layer"
    {
      if(ptrTerrain->tr_atlLayers.Count()>1)
      {
        if( ::MessageBoxA( pMainFrame->m_hWnd, "Are you sure that you want to delete this layer?",
          "Warning !", MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON1| MB_SYSTEMMODAL | MB_TOPMOST) == IDYES)
        {
          ptrTerrain->RemoveLayer(iLayer);
          if(GetLayerIndex()>=ptrTerrain->tr_atlLayers.Count())
          {
            SelectLayer(ptrTerrain->tr_atlLayers.Count()-1);
          }
        }
      }
      break;
    }
    case 1: // "Insert texture layer"
    {
      try
      {
        ptrTerrain->AddLayer_t( CTFILENAME("Textures\\Editor\\Default.tex"));
        SelectLayer( ptrTerrain->tr_atlLayers.Count()-1);
      }
      catch (const char *strError)
      {
        WarningMessage("Unable to obtain default texture for new texture layer!\nError: %s", strError);
      }
      break;
    }
    case 4: // "Insert tile layer"
    {
      try
      {
        ptrTerrain->AddLayer_t( CTFILENAME("Textures\\Editor\\Default.tex"), LT_TILE);
        SelectLayer( ptrTerrain->tr_atlLayers.Count()-1);
      }
      catch (const char *strError)
      {
        WarningMessage("Unable to obtain default texture for new tile layer!\nError: %s", strError);
      }
      break;
    }
    case 2: // "Move up"
    {
      if(iLayer<ptrTerrain->tr_atlLayers.Count()-1)
      {
        ptrTerrain->SetLayerIndex( iLayer, iLayer+1);
        SelectLayer(iLayer+1);
      }
      break;
    }
    case 3: // "Move down"
    {
      if(iLayer>0)
      {
        ptrTerrain->SetLayerIndex( iLayer, iLayer-1);
        SelectLayer(iLayer-1);
      }
      break;
    }
    }
  }
  theApp.m_ctTerrainPage.MarkChanged();
}

void DisplayLayerTexture(INDEX iLayer)
{
  CWndDisplayTexture *pDisplay=new CWndDisplayTexture;

  CTerrainLayer *ptlLayer=GetLayer(iLayer);
  if(ptlLayer!=NULL && ptlLayer->tl_ptdTexture!=NULL)
  {
    POINT pt;
    GetCursorPos(&pt);
    CTString strText1=ptlLayer->tl_ptdTexture->GetName();
    CTString strText2=ptlLayer->tl_ptdTexture->GetDescription();
    pDisplay->Initialize(pt.x, pt.y, ptlLayer->tl_ptdTexture, strText1, strText2);
  }
}

void ApplyLayerTextureCommand(INDEX iSelectedItem)
{
  CTerrain *ptrTerrain=GetTerrain();
  if( ptrTerrain==NULL) return;
  CTerrainLayer *ptlLayer=GetLayer();
  INDEX iLayer=GetLayerIndex();
  if(ptlLayer!=NULL)
  {
    switch( iSelectedItem)
    {
    case 0: // "Create izohipse textutre"
    {
      CTFileName fnGradient=_EngineGUI.FileRequester(
        "Select izohipse gradient", FILTER_TEX FILTER_ALL FILTER_END,
        "Texture directory", "Textures\\");
      if( fnGradient=="") return;

      CTextureData *ptdGradient;
      try
      {
        ptdGradient=_pTextureStock->Obtain_t( fnGradient);
        ptdGradient->Force(TEX_STATIC|TEX_CONSTANT);
      }
      catch (const char *strError)
      {
        (void) strError;
        WarningMessage("Unable to obtain izohipse gradient texture!");
        return;
      }

      CTFileName fnIzohipseTexture=_EngineGUI.FileRequester(
        "Choose name for izohipse texture", FILTER_TEX FILTER_ALL FILTER_END,
        "Texture directory", "Textures\\");
      if( fnIzohipseTexture=="") return;

      INDEX iHMWidth=ptrTerrain->tr_pixHeightMapWidth;
      INDEX iHMHeight=ptrTerrain->tr_pixHeightMapHeight;
      CImageInfo ii;
      ii.ii_Width=iHMWidth-1;
      ii.ii_Height=iHMHeight-1;
      ii.ii_BitsPerPixel=32;
      ii.ii_Picture=(UBYTE*) AllocMemory(ii.ii_Width*ii.ii_Height*ii.ii_BitsPerPixel/8);
      COLOR *pcol=(COLOR *)ii.ii_Picture;

      INDEX iGradientHeight=ptdGradient->GetPixHeight();
      UWORD *puw=ptrTerrain->tr_auwHeightMap;
      if(puw==NULL) return;
      for( INDEX iy=0; iy<ii.ii_Height; iy++)
      {
        for( INDEX ix=0; ix<ii.ii_Width; ix++)
        {
          UWORD uwHeight=*(puw+iy*iHMWidth+ix);
          FLOAT fHeightRatio=uwHeight/65535.0f;
          INDEX iGradPix1=iGradientHeight*fHeightRatio;
          INDEX iGradPix2=ClampUp(iGradPix1+1,iGradientHeight-1);
          COLOR colPix1=ptdGradient->GetTexel(0,iGradPix1);
          COLOR colPix2=ptdGradient->GetTexel(0,iGradPix2);
          FLOAT fLerpFactor=iGradientHeight*fHeightRatio-INDEX(iGradientHeight*fHeightRatio);
          COLOR colResult=LerpColor(colPix1,colPix2,fLerpFactor);
          *(pcol+iy*ii.ii_Width+ix)=ByteSwap(colResult);
        }
      }
      _pTextureStock->Release(ptdGradient);

      CTextureData tdIzohipse;
      try
      {
        tdIzohipse.Create_t( &ii, 1024, 16, TRUE);
        tdIzohipse.Save_t( fnIzohipseTexture);
        ptlLayer->SetLayerTexture_t(fnIzohipseTexture);
        ptlLayer->tl_fStretchX=1.0f/ptrTerrain->tr_vTerrainSize(1);
        ptlLayer->tl_fStretchY=1.0f/ptrTerrain->tr_vTerrainSize(2);
      }
      catch (const char *strError)
      {
        (void) strError;
        WarningMessage("Unable to save izohipse texture!");
        return;
      }
      ptrTerrain->RefreshTerrain();
      theApp.m_ctTerrainPage.MarkChanged();
      break;
    }
    case 1: // "Browse texture"
    {
      CTFileName fnTexture=_EngineGUI.FileRequester(
        "Browse texture", FILTER_TEX FILTER_ALL FILTER_END,
        "Texture directory", "Textures\\");
      if( fnTexture=="") return;

      try
      {
        ptlLayer->SetLayerTexture_t(fnTexture);
        theApp.m_ctTerrainPageCanvas.MarkChanged();
        ptrTerrain->RefreshTerrain();
      }
      catch (const char *strError)
      {
        (void) strError;
      }
      break;
    }
    case 2: // "View layer texture"
    {
      DisplayLayerTexture(iLayer);
    }
    }
  }
}

void DisplayLayerMask(INDEX iLayer)
{
  CWndDisplayTexture *pDisplay=new CWndDisplayTexture;

  CTerrainLayer *ptlLayer=GetLayer(iLayer);
  if(ptlLayer!=NULL && ptlLayer->tl_ptdTexture!=NULL)
  {
    CTextureData *ptd=ptlLayer->GetThumbnail(ptlLayer->tl_iMaskWidth-1,ptlLayer->tl_iMaskHeight-1);

    POINT pt;
    GetCursorPos(&pt);

    CTString strText1;
    strText1.PrintF("%dx%d",ptlLayer->tl_iMaskWidth-1, ptlLayer->tl_iMaskHeight-1);
    pDisplay->Initialize(pt.x, pt.y, ptd, strText1);
  }
}

void ApplyLayerMaskCommand(INDEX iSelectedItem)
{
  CMainFrame* pMainFrame = STATIC_DOWNCAST(CMainFrame, AfxGetMainWnd());
  CTerrain *ptrTerrain=GetTerrain();
  if( ptrTerrain==NULL) return;
  CTerrainLayer *ptlLayer=GetLayer();
  INDEX iLayer=GetLayerIndex();
  if(ptlLayer!=NULL)
  {
    switch( iSelectedItem)
    {
    case 0: // "Fill mask"
    {
      if( ::MessageBoxA( pMainFrame->m_hWnd, "Are you sure that you want to fill this layer's mask?",
        "Warning !", MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON1| MB_SYSTEMMODAL | MB_TOPMOST) == IDYES)
      {
        EditTerrain(NULL, FLOAT3D(0,0,0), 1.0f, TE_FILL_LAYER_MASK);
      }
      break;
    }
    case 1: // "Clear mask"
    {
      if( ::MessageBoxA( pMainFrame->m_hWnd, "Are you sure that you want to clear this layer's mask?",
        "Warning !", MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON1| MB_SYSTEMMODAL | MB_TOPMOST) == IDYES)
      {
        EditTerrain(NULL, FLOAT3D(0,0,0), 1.0f, TE_CLEAR_LAYER_MASK);
      }
      break;
    }
    case 2: // "Import mask"
    {
      CTFileName fnMaskMap=_EngineGUI.FileRequester(
        "Import layer mask", FILTER_TGA FILTER_PCX FILTER_ALL FILTER_END,
        "Layer mask directory", "Textures\\");
      if( fnMaskMap=="") return;
      try
      {
        ptlLayer->ImportLayerMask_t(fnMaskMap);
        ptrTerrain->RefreshTerrain();
        pMainFrame->Invalidate(FALSE);
        theApp.GetActiveDocument()->SetModifiedFlag( TRUE);
      }
      catch (const char *strError)
      {
        AfxMessageBox( CString(strError));
      }
      break;
    }
    case 3: // "Export mask"
    {
      CTFileName fnMaskMap=_EngineGUI.FileRequester(
        "Export layer mask", FILTER_TGA FILTER_PCX FILTER_ALL FILTER_END,
        "Layer mask directory", "Textures\\");
      if( fnMaskMap=="") return;
      try
      {
        ptlLayer->ExportLayerMask_t(fnMaskMap);
        ptrTerrain->RefreshTerrain();
        pMainFrame->Invalidate(FALSE);
      }
      catch (const char *strError)
      {
        AfxMessageBox( CString(strError));
      }
      break;
    }
    case 4: // noise
    {
      EditTerrain(NULL, FLOAT3D(0,0,0), 1.0f, TE_LAYER_RND_NOISE);
      break;
    }
    case 5: // "View mask"
    {
      DisplayLayerMask(iLayer);
    }
    }
    theApp.m_ctTerrainPageCanvas.MarkChanged();
  }
}

void PickLayerColor(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  INDEX iLayer=ptib->tib_iLayer;
  CTerrainLayer *ptlLayer=GetLayer(iLayer);
  if(ptlLayer==NULL) return;

  CMainFrame* pMainFrame = STATIC_DOWNCAST(CMainFrame, AfxGetMainWnd());
  COLORREF colMfc=CLRF_CLR( ptlLayer->tl_colMultiply);
  if( MyChooseColor( colMfc, *pMainFrame))
  {
    ptlLayer->tl_colMultiply=CLR_CLRF( colMfc) | ptlLayer->tl_colMultiply&0x000000FF;
  }
}

void NumericAlpha(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  INDEX iLayer=ptib->tib_iLayer;
  CTerrainLayer *ptlLayer=GetLayer(iLayer);
  if(ptlLayer==NULL) return;

  CDlgNumericAlpha dlgNumericAlpha( ptlLayer->tl_colMultiply&0xFF);
  if( dlgNumericAlpha.DoModal() == IDOK)
  {
    ptlLayer->tl_colMultiply&=0xFFFFFF00;
    ptlLayer->tl_colMultiply|=(dlgNumericAlpha.m_iAlpha&0xFF);
  }
}

void InvokeLayerOptions(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  INDEX iLayer=ptib->tib_iLayer;
  if(iLayer==-1) return;

  CDlgEditTerrainLayer dlg;
  if( dlg.DoModal()==IDOK)
  {
    GenerateLayerDistribution(iLayer);
  }
}

void PickSelectedLayerBcgColor(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  PointToScreenSpace(pt, pdp);
  CMainFrame* pMainFrame = STATIC_DOWNCAST(CMainFrame, AfxGetMainWnd());
  pMainFrame->CustomColorPicker( pt.x-50, pt.y);

  COLORREF TmpColor = CLRF_CLR( _colSelectedLayerBcg);
  if( MyChooseColor( TmpColor, *pMainFrame))
  {
    _colSelectedLayerBcg = CLR_CLRF( TmpColor)|CT_OPAQUE;
  }
  theApp.m_ctTerrainPageCanvas.MarkChanged();
}

INDEX InsertItemMacro( CCustomComboWnd *pCC, CTString strText, INDEX iIcon=-1, INDEX iValue=-1, COLOR col=COL_DEFAULT_ITEM)
{
  INDEX iItem;
  if(iIcon!=-1)
  {
    MEXaabbox2D boxIcon=MEXaabbox2D( MEX2D(16*iIcon,0), MEX2D(16*iIcon+16,16));
    DECLARE_CTFILENAME( fnTerrainEditIcons, "Textures\\Editor\\TerrainEditingIcons.tex");
    iItem=pCC->InsertItem( strText, fnTerrainEditIcons, boxIcon);
  }
  else
  {
    iItem=pCC->InsertItem( strText);
  }

  if(iValue!=-1) 
  {
    pCC->SetItemValue(iItem, iValue);
  }

  pCC->SetItemColor( iItem, col);
  return iItem;
}

void InvokeLayerPopup(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  CTerrain *ptrTerrain=GetTerrain();
  if( ptrTerrain==NULL) return;

  CCustomComboWnd *pCombo=new CCustomComboWnd;
  InsertItemMacro( pCombo, "Delete layer", -1, 0);
  InsertItemMacro( pCombo, "Insert texture layer", -1, 1);
  InsertItemMacro( pCombo, "Insert tile layer", -1, 4);
  InsertItemMacro( pCombo, "Move up", -1, 2);
  InsertItemMacro( pCombo, "Move down", -1, 3);

  PointToScreenSpace(pt, pdp);
  pCombo->Initialize(NULL, ApplyLayerCommand, pt.x, pt.y);
}

void InvokeLayerTexturePopup(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  CTerrain *ptrTerrain=GetTerrain();
  if( ptrTerrain==NULL) return;

  CCustomComboWnd *pCombo=new CCustomComboWnd;
  InsertItemMacro( pCombo, "Create izohipse texture", 22, 0);
  InsertItemMacro( pCombo, "Browse texture", 22, 1);
  InsertItemMacro( pCombo, "View layer texture", 3, 2);
  PointToScreenSpace(pt, pdp);
  pCombo->Initialize(NULL, ApplyLayerTextureCommand, pt.x, pt.y);
}

void InvokeLayerMaskPopup(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  CTerrain *ptrTerrain=GetTerrain();
  if( ptrTerrain==NULL) return;

  CCustomComboWnd *pCombo=new CCustomComboWnd;
  InsertItemMacro( pCombo, "Fill mask",     17, 0);
  InsertItemMacro( pCombo, "Clear mask",     9, 1);
  InsertItemMacro( pCombo, "Noise",         12, 4);
  InsertItemMacro( pCombo, "--------------",-1,-1);
  InsertItemMacro( pCombo, "Import mask",   22, 2);
  InsertItemMacro( pCombo, "Export mask",   22, 3);
  InsertItemMacro( pCombo, "--------------",-1,-1);
  InsertItemMacro( pCombo, "View mask",      3, 5);

  PointToScreenSpace(pt, pdp);
  pCombo->Initialize(NULL, ApplyLayerMaskCommand, pt.x, pt.y);
}

CTString GetStretchInfo(CTIButton *ptib, CPoint pt, CDrawPort *pdp, BOOL bLmb)
{
  CTString strInfo;
  INDEX iLayer=ptib->tib_iLayer;
  if( iLayer!=-1)
  {
    CTerrainLayer *ptlLayer=GetLayer(iLayer);
    CTerrain *ptrTerrain=GetTerrain();
    if(ptlLayer!=NULL && ptrTerrain!=NULL)
    {
      strInfo.PrintF("Texture stretch: %g", ptlLayer->tl_fStretchX);
      return strInfo;
    }
  }
  return strInfo;
}

CTString GetEditedData(CTIButton *ptib, CPoint pt, CDrawPort *pdp, BOOL bLmb)
{
  CTString strInfo;
  if(ptib->tib_pfData1!=NULL)  strInfo.PrintF("%f", ptib->tib_pfData1);
  return strInfo;
}

void OnDropIntoLayerTexture(CTIButton *ptib, CPoint pt, CDrawPort *pdp, CTFileName fnDropped)
{
  // if it is not texture, report error
  if( fnDropped.FileExt() != ".tex" )
  {
    AfxMessageBox( L"You can only drop textures here.");
    return;
  }
  
  INDEX iLayer=ptib->tib_iLayer;
  CTerrainLayer *ptlLayer=GetLayer(iLayer);
  CTerrain *ptrTerrain=GetTerrain();
  if(ptlLayer!=NULL && ptrTerrain!=NULL)
  {
    ASSERT(ptlLayer->tl_ptdTexture!=NULL);
    CTFileName fnOldTexture=CTFILENAME("Textures\\Editor\\Default.tex");
    if(ptlLayer->tl_ptdTexture!=NULL)
    {
      fnOldTexture=ptlLayer->tl_ptdTexture->GetName();
    }
    BOOL bSetOldTexture=TRUE;
    try
    {
      fnDropped.RemoveApplicationPath_t();
      ptlLayer->SetLayerTexture_t(fnDropped);
      bSetOldTexture=FALSE;
      theApp.m_ctTerrainPageCanvas.MarkChanged();
      ptrTerrain->RefreshTerrain();
    }
    catch (const char *strError)
    {
      (void) strError;
    }
    
    // if should restore old texture
    if(bSetOldTexture)
    {
      try
      {
        ptlLayer->SetLayerTexture_t(fnOldTexture);
      }
      catch (const char *strError)
      {
        (void) strError;
      }
    }
  }
}

void ToggleLayerVisibleFlag(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  INDEX iLayer=ptib->tib_iLayer;
  if( iLayer!=-1)
  {
    CTerrainLayer *ptlLayer=GetLayer(iLayer);
    if(ptlLayer!=NULL)
    {
      ptlLayer->tl_bVisible=!ptlLayer->tl_bVisible;
      CTerrain *ptrTerrain=GetTerrain();
      ptrTerrain->RefreshTerrain();
    }
  }
}

void ToggleFlag(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  INDEX iLayer=ptib->tib_iLayer;
  if( iLayer!=-1)
  {
    CTerrainLayer *ptlLayer=GetLayer(iLayer);
    if(ptlLayer!=NULL)
    {
      BOOL bOldValue=*(BOOL*)((UBYTE*)ptlLayer+(ULONG_PTR)ptib->tib_pfData1);
      bOldValue=!bOldValue;
      *(BOOL*)((UBYTE*)ptlLayer+(ULONG_PTR)ptib->tib_pfData1)=bOldValue;
    }
  }
}

BOOL DisableIfNoTerrainSelected(CTIButton *ptib)
{
  CTerrain *ptrTerrain=GetTerrain();
  return ptrTerrain!=NULL;
}

CTString GetNormalizedPercentageInfo(CTIButton *ptib, CPoint pt, CDrawPort *pdp, BOOL bLmb)
{
  CTString strInfo;
  INDEX iLayer=ptib->tib_iLayer;
  if( iLayer!=-1)
  {
    CTerrainLayer *ptlLayer=GetLayer(iLayer);
    if(ptlLayer!=NULL)
    {
      FLOAT *pfNormalized=(FLOAT *)((UBYTE*)ptlLayer+(ULONG_PTR)ptib->tib_pfData1);
      FLOAT fValue=*pfNormalized;
      strInfo.PrintF("%s: %d%%", ptib->tib_strToolTip, INDEX(floor(fValue*100.0f+0.5f)));
      return strInfo;
    }
  }
  return strInfo;
}

void ChangeLayerDistributionData(CTIButton *ptib, FLOAT fdx, FLOAT fdy, CDrawPort *pdp)
{
  if( ptib->tib_pfData1==NULL) return;
  INDEX iLayer=ptib->tib_iLayer;
  if( iLayer==-1) return;
  CTerrainLayer *ptlLayer=GetLayer(iLayer);
  if(ptlLayer==NULL) return;
  
  FLOAT *pfNormalized=(FLOAT *)((UBYTE*)ptlLayer+(ULONG_PTR)ptib->tib_pfData1);
  FLOAT fOldValue=*pfNormalized;
  FLOAT fAddX=ptib->tib_fDataDelta*fdx;
  FLOAT fNewValue=Clamp( fOldValue+fAddX, 0.0f, 1.0f);
  *pfNormalized=fNewValue;

  _iGenerateForLayer=iLayer;
  _chAutoGenerateDistribution.MarkChanged();
}

void ChangeData(CTIButton *ptib, FLOAT fdx, FLOAT fdy, CDrawPort *pdp)
{
  if( ptib->tib_pfData1!=NULL)
  {
    FLOAT fData=*ptib->tib_pfData1;
    FLOAT fAdd=ptib->tib_fDataDelta*fdx;
    if((fData+fAdd)>=ptib->tib_fDataMax)
    {
      if(ptib->tib_bWrapData)
      {
        fData=ptib->tib_fDataMin+((fData+fAdd)-ptib->tib_fDataMax);
      }
      else
      {
        fData=ptib->tib_fDataMax;
      }
    }
    else if((fData+fAdd)<=ptib->tib_fDataMin)
    {
      if(ptib->tib_bWrapData)
      {
        fData=ptib->tib_fDataMax+(fData+fAdd);
      }
      else
      {
        fData=ptib->tib_fDataMin;
      }
    }
    else
    {
      fData=fData+fAdd;
    }
    *ptib->tib_pfData1=fData;
  }
}

void ChangeTextureRotation(CTIButton *ptib, FLOAT fdx, FLOAT fdy, CDrawPort *pdp)
{
  INDEX iLayer=ptib->tib_iLayer;
  if( iLayer!=-1)
  {
    CTerrainLayer *ptlLayer=GetLayer(iLayer);
    if(ptlLayer!=NULL)
    {
      FLOAT fAdd=CHANGE_ANGLE_SENSITIVITY*fdx;
      ptlLayer->tl_fRotateX+=fAdd;
      ptlLayer->tl_fRotateY+=fAdd;
    }
  }
}

void ChangeTextureOffset(CTIButton *ptib, FLOAT fdx, FLOAT fdy, CDrawPort *pdp)
{
  INDEX iLayer=ptib->tib_iLayer;
  if( iLayer!=-1)
  {
    CTerrainLayer *ptlLayer=GetLayer(iLayer);
    if(ptlLayer!=NULL)
    {
      FLOAT fAddX=CHANGE_OFFSET_SENSITIVITY*fdx;
      FLOAT fAddY=CHANGE_OFFSET_SENSITIVITY*fdy;
      ptlLayer->tl_fOffsetX+=fAddX;
      ptlLayer->tl_fOffsetY+=fAddY;
    }
  }
}

void ChangeTextureStretch(CTIButton *ptib, FLOAT fdx, FLOAT fdy, CDrawPort *pdp)
{
  INDEX iLayer=ptib->tib_iLayer;
  if( iLayer!=-1)
  {
    CTerrainLayer *ptlLayer=GetLayer(iLayer);
    CTerrain *ptrTerrain=GetTerrain();
    if(ptlLayer!=NULL && ptrTerrain!=NULL)
    {
      if(fdx>=CHANGE_STRETCH_SENSITIVITY)
      {
        ptlLayer->tl_fStretchX/=2;
        ptlLayer->tl_fStretchY/=2;
        ptrTerrain->RefreshTerrain();
      }
      else if(fdx<=-CHANGE_STRETCH_SENSITIVITY)
      {
        ptlLayer->tl_fStretchX*=2;
        ptlLayer->tl_fStretchY*=2;
        ptrTerrain->RefreshTerrain();
      }
    }
  }
}

void OnSelectBrush(INDEX iSelectedItem)
{
  if( iSelectedItem>=0 && iSelectedItem<CT_BRUSH_MODES)
  {
    theApp.m_iTerrainBrushMode=iSelectedItem;
    theApp.m_ctTerrainPageCanvas.MarkChanged();
    theApp.GetDocument()->SetStatusLineModeInfoMessage();
  }
  else if(iSelectedItem==-1)
  {
    CTerrain *ptrTerrain=GetTerrain();
    if( ptrTerrain==NULL) return;

	  CDlgEditFloat dlg;
    dlg.m_fEditFloat=theApp.m_fPosterizeStep;
	  dlg.m_strVarName = "Posterize step (m)";
    dlg.m_strTitle = "Enter posterize step";
    if(dlg.DoModal()!=IDOK) return;
    theApp.m_fPosterizeStep=dlg.m_fEditFloat;
  }
  // auto update terrain distribution flag
  else if(iSelectedItem==50)
  {
    theApp.m_Preferences.ap_bAutoUpdateTerrainDistribution=!theApp.m_Preferences.ap_bAutoUpdateTerrainDistribution;
  }
  // settings
  else if(iSelectedItem==100)
  {
    CDlgTEOperationSettings dlg;
    dlg.DoModal();
  }
}

void InvokeBrushModeCombo(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  PointToScreenSpace(pt, pdp);
  CCustomComboWnd *pCombo=new CCustomComboWnd;
  INDEX iFlag=6;
  if(theApp.m_Preferences.ap_bAutoUpdateTerrainDistribution)
  {
    iFlag=7;
  }
  InsertItemMacro( pCombo, "Auto update layer distribution", iFlag, 50, COL_SETTINGS);
  InsertItemMacro( pCombo, "---------------------------------", -1, -1, COL_SEPARATOR);
  for(INDEX iMode=0; iMode<CT_BRUSH_MODES; iMode++)
  {
    INDEX iIcon;
    CTString strText;
    GetBrushModeInfo(iMode, iIcon, strText);
    InsertItemMacro( pCombo, strText, iIcon, iMode);
  }
  
  InsertItemMacro( pCombo, "---------------------------------", -1, -1, COL_SEPARATOR);
  InsertItemMacro( pCombo, "Operation settings (Ctrl+Shift+P)", 26, 100, COL_SETTINGS);

  pCombo->Initialize(NULL, OnSelectBrush, pt.x, pt.y);
}

void InvokePercentageCombo(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  PointToScreenSpace(pt, pdp);
  CCustomComboWnd *pCombo=new CCustomComboWnd;
  for( INDEX i=1; i<11; i++)
  {
    CTString strText;
    strText.PrintF("%d", i*10);
    pCombo->InsertItem( strText);
  }
  pCombo->Initialize(&theApp.m_fTerrainBrushPressureEnum, NULL, pt.x, pt.y);
}

void InvokeEditModeCombo(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  PointToScreenSpace(pt, pdp);
  CCustomComboWnd *pCombo=new CCustomComboWnd;
  InsertItemMacro( pCombo, "Altitude (U)", 20);
  InsertItemMacro( pCombo, "Texture (L)", 19);
  pCombo->Initialize(&theApp.m_iTerrainEditMode, NULL, pt.x, pt.y);
}

void NextEnum(CTIButton *ptib, CPoint pt)
{
  ChangeData(ptib, 1, 0, NULL);
}

FLOAT GetMaxSliderPos(CDrawPort *pdp)
{
  CTerrain *ptrTerrain=GetTerrain();
  if( ptrTerrain==NULL) return 0;
  FLOAT fMaxPos=ClampDn(ptrTerrain->tr_atlLayers.Count()*LAYER_HEIGHT-pdp->GetHeight(),PIX(0));
  return fMaxPos;
}

void OnSliderArrowUp(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  _fScrollLayers=ClampDn(_fScrollLayers-PIX(LAYER_HEIGHT),0.0f);
}

void OnSliderArrowDown(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  _fScrollLayers=ClampUp(_fScrollLayers+PIX(LAYER_HEIGHT),GetMaxSliderPos(pdp));
}

void RenderLayerColor(CTIButton *ptib, CDrawPort *pdp)
{
  INDEX iLayer=ptib->tib_iLayer;
  CTerrainLayer *ptlLayer=GetLayer(iLayer);
  if(ptlLayer==NULL) return;

  PIXaabbox2D box=GetButtonScreenBox(*ptib);
  pdp->Fill(box.Min()(1), box.Min()(2), box.Size()(1), box.Size()(2), ptlLayer->tl_colMultiply);
}

void RenderSlider(CTIButton *ptib, CDrawPort *pdp)
{
  FLOAT fMaxPos=GetMaxSliderPos(pdp);
  PIX pixSliderY=FLOAT(ptib->tib_fdy-SLIDER_HOLDER_HEIGHT/2)/fMaxPos*_fScrollLayers;
  pixSliderY=ClampDn<PIX>(pixSliderY-SLIDER_HOLDER_HEIGHT/2, 0);
  pdp->Fill(ptib->tib_fx, ptib->tib_fy+pixSliderY, ptib->tib_fdx-2, SLIDER_HOLDER_HEIGHT, C_mlGRAY|CT_OPAQUE);
}

void OnSliderClick(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  FLOAT fMaxPos=GetMaxSliderPos(pdp);
  PointToIconSpace(pt, ptib);
  pt.y=ClampUp( pt.y, INDEX(ptib->tib_fdy-SLIDER_HOLDER_HEIGHT/2));
  if(pt.y<SLIDER_HOLDER_HEIGHT/2) pt.y=0;
  FLOAT fRemapFactor=fMaxPos/(ptib->tib_fdy-SLIDER_HOLDER_HEIGHT);
  FLOAT fSetPos=pt.y*fRemapFactor;
  _fScrollLayers=ClampUp(fSetPos,fMaxPos);
}

void DragSlider(CTIButton *ptib, FLOAT fdx, FLOAT fdy, CDrawPort *pdp/*=NULL*/)
{
  FLOAT fMaxPos=GetMaxSliderPos(pdp);
  FLOAT fRemapFactor=FLOAT(fMaxPos)/(ptib->tib_fdy-SLIDER_HOLDER_HEIGHT);
  FLOAT fNewPos=_fScrollLayers+fdy*fRemapFactor;
  _fScrollLayers=Clamp( fNewPos, 0.0f, fMaxPos);
}

void RecalculateShadows(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  RecalculateShadows();
}

void DisplayHeightMap(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  PointToScreenSpace(pt, pdp);
  DisplayHeightMapWindow( pt);
}

void ApplyTerrainOptions(INDEX iSelectedItem)
{
  CTerrain *ptrTerrain=GetTerrain();
  if( ptrTerrain==NULL) return;

  switch(iSelectedItem)
  {
  // invoke properties
  case 0:
  {
    CDlgTerrainProperties dlg;
    dlg.DoModal();
    break;
  }
  // generate terrain
  case 3:
  {
    ApplyGenerateTerrain();
    break;
  }
  // smooth
  case 4:
  {
    ApplySmoothOntoTerrain();
    break;
  }
  // filter
  case 5:
  {
    ApplyFilterOntoTerrain();
    break;
  }
  // add noise
  case 6:
  {
    ApplyRndNoiseOntoTerrain();
    break;
  }
  // equalize
  case 7:
  {
    ApplyEqualizeOntoTerrain();
    break;
  }
  // operation settings
  case 8:
  {
    // option settings
    CDlgTEOperationSettings dlg;
    dlg.DoModal();
    break;
  }
  // posterize
  case 9:
  {
    ApplyPosterizeOntoTerrain();
    break;
  }
  // add continous noise
  case 11:
  {
    ApplyContinousNoiseOntoTerrain();
    break;
  }
  // limit down
  case 12:
  {
    ApplyMinimumOntoTerrain();
    break;
  }
  // limit up
  case 13:
  {
    ApplyMaximumOntoTerrain();
    break;
  }
  // flatten
  case 14:
  {
    ApplyFlattenOntoTerrain();
    break;
  }
  // recalcualte shadows
  case 15:
  {
    ptrTerrain->UpdateShadowMap();
  }
  // generate layer distribution
  case 16:
  {
    GenerateLayerDistribution(-1);
  }
  // optimize layers
  case 17:
  {
    OptimizeLayers();
  }
  // view heightmap
  case 50:
  {
    CPoint pt;
    GetCursorPos( &pt); 
    DisplayHeightMapWindow( pt);
  }
  }
}

void InvokeTerrainOptions(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  CTerrain *ptrTerrain=GetTerrain();
  if( ptrTerrain==NULL) return;

  CCustomComboWnd *pCombo=new CCustomComboWnd;
  INDEX iItem=0;
  InsertItemMacro( pCombo, "Terrain properties (Ctrl+Shift+T)",2,0);
  InsertItemMacro( pCombo, "------------------------------------", -1, -1, COL_SEPARATOR);
  InsertItemMacro( pCombo, "Generate terrain (Ctrl+Shift+G)",25, 3);
  InsertItemMacro( pCombo, "Equalize (Ctrl+Shift+E)",1,7);
  InsertItemMacro( pCombo, "------------------------------------", -1, -1, COL_SEPARATOR);
  InsertItemMacro( pCombo, "Recalculate shadows (Ctrl+Shift+R)",5, 15);
  InsertItemMacro( pCombo, "Generate layer distribution (Ctrl+G)",19, 16);
  InsertItemMacro( pCombo, "Optimize layers (Ctrl+Shift+Z)",25, 17);
  InsertItemMacro( pCombo, "------------------------------------", -1, -1, COL_SEPARATOR);
  InsertItemMacro( pCombo, "Smooth (Ctrl+Shift+O)",10,4);
  InsertItemMacro( pCombo, "Filter (Ctrl+Shift+F)",30,5);
  InsertItemMacro( pCombo, "Limit down (Ctrl+Shift+M)",27,12);
  InsertItemMacro( pCombo, "Limit up (Ctrl+Shift+X)",11,13);
  InsertItemMacro( pCombo, "Flatten (Ctrl+Shift+N)",18,14);
  InsertItemMacro( pCombo, "Posterize",28,9);
  InsertItemMacro( pCombo, "Random noise (Ctrl+Shift+K)",21,6);
  InsertItemMacro( pCombo, "Texture noise (Ctrl+Shift+Y)",31,11);
  InsertItemMacro( pCombo, "------------------------------------", -1, -1,COL_SEPARATOR);
  InsertItemMacro( pCombo, "View height map ", 26, 50);
  InsertItemMacro( pCombo, "Operation settings (Ctrl+Shift+P)", 26, 8, COL_SETTINGS);
  PointToScreenSpace(pt, pdp);                                      
  pCombo->Initialize(NULL, ApplyTerrainOptions, pt.x, pt.y);
}

void DisplayHeightMapWindow(CPoint pt)
{
  CTextureData tdHeightMap;
  CImageInfo ii;

  CTerrain *ptrTerrain=GetTerrain();
  if( ptrTerrain==NULL) return;

  PIX pixW=ptrTerrain->tr_pixHeightMapWidth;
  PIX pixH=ptrTerrain->tr_pixHeightMapHeight;
  if(ptrTerrain->tr_auwHeightMap==NULL) return;
  ii.ii_BitsPerPixel=32;
  ii.ii_Width=pixW-1;
  ii.ii_Height=pixH-1;
  ii.ii_Picture=(UBYTE*)AllocMemory((pixW-1)*(pixH-1)*sizeof(COLOR));
  COLOR *pcol=(COLOR *)ii.ii_Picture;

  for( INDEX iy=0; iy<pixH-1; iy++)
  {
    for( INDEX ix=0; ix<pixW-1; ix++)
    {
      UWORD *pdest=ptrTerrain->tr_auwHeightMap+iy*pixW+ix;
      UWORD uw=*pdest;
      FLOAT fPix=uw/256.0f;
      UBYTE ubR=UBYTE(fPix);
      COLOR col=RGBToColor(ubR,ubR,ubR)|CT_OPAQUE;
      col=RGBToColor(ubR,ubR,ubR)|CT_OPAQUE;
      *(pcol+iy*(pixW-1)+ix)=ByteSwap(col);
    }
  }

  try
  {
    tdHeightMap.Create_t( &ii, pixW-1, 16, TRUE);
    CTString strHeightMap="Temp\\ViewHeightMap.tex";
    tdHeightMap.Save_t( strHeightMap);

    CTextureData *ptd;
    ptd=_pTextureStock->Obtain_t( strHeightMap);
    ptd->Reload();
    CWndDisplayTexture *pDisplay=new CWndDisplayTexture;
    pDisplay->Initialize(pt.x, pt.y, ptd);
  }
  catch (const char *strError)
  {
    (void) strError;
    WarningMessage("Unable to display height map!");
  }
}

void OnSelectLoadSave(INDEX iSelectedItem)
{
  switch( iSelectedItem)
  {
  case 0:
  case 1:
  case 2:
  case 3:
  {
    ApplyImportExport(iSelectedItem);
  }
  case 4: // "Load surface"
  {
    CTFileName fnSurface=_EngineGUI.FileRequester(
      "Load surface", "Surfaces (*.sfc)\0*.sfc\0" FILTER_ALL FILTER_END,
      "Surface directory", "Surfaces\\");
    if( fnSurface=="") return;
    LoadSurface(fnSurface);
    break;
  }
  case 5: // "Save surface"
  {
    CTFileName fnSurface=_EngineGUI.FileRequester(
      "Save surface", "Surfaces (*.sfc)\0*.sfc\0" FILTER_ALL FILTER_END,
      "Surface directory", "Surfaces\\");
    if( fnSurface=="") return;
    SaveSurface(fnSurface);
    break;
  }
  }
}

void ApplyImportExport(INDEX iOperation)
{
  CTerrain *ptrTerrain=GetTerrain();
  if( ptrTerrain==NULL) return;

  CTFileName fnHeightmap;
  if(iOperation<2)
  {
    fnHeightmap=_EngineGUI.FileRequester(
      "Import heightmap", FILTER_TGA FILTER_PCX FILTER_ALL FILTER_END,
      "Terrain heightmap directory", "Textures\\");
    if( fnHeightmap=="") return;
  }
  else
  {
    fnHeightmap=_EngineGUI.FileRequester(
      "Export heightmap", FILTER_TGA FILTER_PCX FILTER_ALL FILTER_END,
      "Terrain heightmap directory", "Textures\\");
    if( fnHeightmap=="") return;
  }

  try
  {
    switch( iOperation)
    {
      // import 8 bit
      case 0:
      {
        ptrTerrain->ImportHeightMap_t(fnHeightmap, FALSE);
        theApp.m_ctTerrainPage.MarkChanged();
        break;
      }
      // import 16 bit
      case 1:
      {
        ptrTerrain->ImportHeightMap_t(fnHeightmap, TRUE);
        theApp.m_ctTerrainPage.MarkChanged();
        break;
      }
      // export 8 bit
      case 2:
      {
        ptrTerrain->ExportHeightMap_t(fnHeightmap, FALSE);
        break;
      }
      // export 16 bit
      case 3:
      {
        ptrTerrain->ExportHeightMap_t(fnHeightmap, TRUE);
        break;
      }
    }
  }
  catch (const char *strError)
  {
    AfxMessageBox( CString(strError));
  }
}

void GenerateLayerDistribution(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  GenerateLayerDistribution(-1);
}

void InvokeImportExportCombo(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  CTerrain *ptrTerrain=GetTerrain();
  if( ptrTerrain==NULL) return;

  CCustomComboWnd *pCombo=new CCustomComboWnd;
  InsertItemMacro( pCombo, "Import 8-bit",  22, 0);
  InsertItemMacro( pCombo, "Import 16-bit", 22, 1);
  InsertItemMacro( pCombo, "Export 8-bit",  22, 2);
  InsertItemMacro( pCombo, "Export 16-bit", 22, 3);
  InsertItemMacro( pCombo, "----------------", -1, -1, COL_SEPARATOR);
  InsertItemMacro( pCombo, "Load surface", 22, 4);
  InsertItemMacro( pCombo, "Save surface", 22, 5);
  PointToScreenSpace(pt, pdp);
  pCombo->Initialize(NULL, OnSelectLoadSave, pt.x, pt.y);
}

void InvokeTerrainTilePalette( PIX pixX, PIX pixY)
{
  CWndTerrainTilePalette *pDisplay=new CWndTerrainTilePalette;
  CTerrainLayer *ptlLayer=GetLayer();
  if(ptlLayer!=NULL && ptlLayer->tl_ptdTexture!=NULL)
  {
    pDisplay->Initialize(pixX, pixY, ptlLayer->tl_ptdTexture, TRUE);
  }
}

CBrushPaletteWnd *_pBrushPalette=NULL;
void InvokeTerrainBrushPalette( PIX pixX, PIX pixY)
{
  // calculate palette window's rectangle
  CRect rectWindow;
  rectWindow.left = pixX;
  rectWindow.bottom = pixY;
  rectWindow.right = rectWindow.left + BRUSH_PALETTE_WIDTH;
  rectWindow.top = rectWindow.bottom - BRUSH_PALETTE_HEIGHT;

  if( _pBrushPalette == NULL)
  {
    // instantiate new choose color palette window
    _pBrushPalette = new CBrushPaletteWnd;
    // create window
    CMainFrame* pMainFrame = STATIC_DOWNCAST(CMainFrame, AfxGetMainWnd());
    BOOL bResult = _pBrushPalette->CreateEx( WS_EX_TOOLWINDOW,
      NULL, L"Brush palette", WS_CHILD|WS_POPUP|WS_VISIBLE,
      rectWindow.left, rectWindow.top, rectWindow.Width(), rectWindow.Height(),
      pMainFrame->m_hWnd, NULL, NULL);
    _pBrushPalette->SetFocus();
    if( !bResult)
    {
      AfxMessageBox( L"Error: Failed to create brush palette");
      return;
    }
    // initialize canvas for active texture button
    _pGfx->CreateWindowCanvas( _pBrushPalette->m_hWnd, &_pBrushPalette->m_pViewPort,
                               &_pBrushPalette->m_pDrawPort);
  }
  else
  {
    _pBrushPalette->ShowWindow(SW_SHOW);
    _pBrushPalette->SetFocus();
  }
}

void InvokeBrushPalette(CTIButton *ptib, CPoint pt, CDrawPort *pdp)
{
  PointToScreenSpace(pt, pdp);
  InvokeTerrainBrushPalette( pt.x, pt.y);
}

BOOL CTerrainInterface::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext) 
{
	return CWnd::Create(lpszClassName, lpszWindowName, dwStyle, rect, pParentWnd, nID, pContext);
}

BOOL CTerrainInterface::IsClicked(CTIButton &tib, CPoint pt) const
{
  PIX pixOffsetX=0;
  PIX pixOffsetY=0;
  GetButtonOffset(tib, pixOffsetX, pixOffsetY);

  PIX x=tib.tib_fx+pixOffsetX;
  PIX y=tib.tib_fy+pixOffsetY;
  PIX dx=tib.tib_fdx;
  PIX dy=tib.tib_fdy;

  return( (pt.x>x) && (pt.x<x+dx) && (pt.y>y) && (pt.y<y+dy) );
}

BOOL _bDummyMouseMove=FALSE;
BOOL _bMouseTrapInProgress=FALSE;
void CTerrainInterface::OnLButtonDown(UINT nFlags, CPoint point) 
{
	CWnd::OnLButtonDown(nFlags, point);
  SetFocus();

  m_ptMouseDown=point;
  m_ptMouse=point;
  m_ptMouseDownScreen=point;
  ClientToScreen(&m_ptMouseDownScreen);

  FOREACHINDYNAMICCONTAINER( dcButtons, CTIButton, ittib)
  {
    CTIButton &tib=*ittib;
    
    if( IsClicked(tib, point))
    {
      if(tib.tib_pOnLeftClick!=NULL)
      {
        tib.tib_pOnLeftClick(&tib, m_ptMouse, m_pDrawPort);
        _bMouseTrapInProgress=FALSE;
        UnhideCursor();
        Invalidate(FALSE);
        if(!tib.tib_bContinueTesting) break;
      }
      if(tib.tib_pOnLeftClickMove!=NULL)
      {
        // display tool tip
        if( tib.tib_pGetClickMoveData!=NULL)
        {
          CTString strInfo=tib.tib_pGetClickMoveData(&tib, point, m_pDrawPort, TRUE);
          _strToolTip=strInfo;
          SetCapture();
          theApp.m_cttToolTips.ManualOn( m_ptMouseDownScreen.x, m_ptMouseDownScreen.y, &::GetToolTipText, this);
        }
        // if should trap the mouse
        if(tib.tib_bMouseTrapForMove)
        {
          m_ptMouseCenter.x=m_pDrawPort->GetWidth()/2;
          m_ptMouseCenter.y=m_pDrawPort->GetHeight()/2;
          m_ptMouseCenterScreen=m_ptMouseCenter;
          ClientToScreen(&m_ptMouseCenterScreen);
          _bDummyMouseMove=TRUE;
          SetCursorPos(m_ptMouseCenterScreen.x, m_ptMouseCenterScreen.y);
          HideCursor();
          _bMouseTrapInProgress=TRUE;
        }
        if(!tib.tib_bContinueTesting) break;
      }
    }
  }
}

void CTerrainInterface::OnLButtonDblClk(UINT nFlags, CPoint point) 
{
  OnLButtonDown(nFlags, point);
}

void CTerrainInterface::OnMouseMove(UINT nFlags, CPoint point)
{
  theApp.m_cttToolTips.MouseMoveNotify( m_hWnd, 500, &::GetToolTipText, this);

  BOOL bLMB = nFlags & MK_LBUTTON;
  BOOL bRMB = nFlags & MK_RBUTTON;
  FLOAT fdx=point.x-m_ptMouseCenter.x;
  FLOAT fdy=point.y-m_ptMouseCenter.y;

  FLOAT fdxNotLocked=point.x-m_ptMouse.x;
  FLOAT fdyNotLocked=point.y-m_ptMouse.y;

  fdx=fdxNotLocked;
  fdy=fdyNotLocked;

  m_ptMouse=point;

  if( _bDummyMouseMove)
  {
    _bDummyMouseMove=FALSE;
    return;
  }

  if( bLMB)
  {
    FOREACHINDYNAMICCONTAINER( dcButtons, CTIButton, ittib)
    {
      CTIButton &tib=*ittib;
    
      if( IsClicked(tib, m_ptMouseDown) && tib.tib_pOnLeftClickMove!=NULL)
      {
        if(tib.tib_bMouseTrapForMove)
        {
          tib.tib_pOnLeftClickMove(&tib, fdx, fdy, m_pDrawPort);
          _bDummyMouseMove=TRUE;
          SetCursorPos(m_ptMouseCenterScreen.x, m_ptMouseCenterScreen.y);
        }
        else
        {
          tib.tib_pOnLeftClickMove(&tib, fdxNotLocked, fdyNotLocked, m_pDrawPort);
        }
        Invalidate(FALSE);
        
        // update tool tip
        if( tib.tib_pGetClickMoveData!=NULL)
        {
          _strToolTip=tib.tib_pGetClickMoveData(&tib, point, m_pDrawPort, TRUE);
          theApp.m_cttToolTips.ManualUpdate();
        }

        if(!tib.tib_bContinueTesting) break;
      }
    }
  }
  else if( bRMB)
  {
    FOREACHINDYNAMICCONTAINER( dcButtons, CTIButton, ittib)
    {
      CTIButton &tib=*ittib;
    
      if( IsClicked(tib, m_ptMouseDown) && tib.tib_pOnRightClickMove!=NULL)
      {
        if(tib.tib_bMouseTrapForMove)
        {
          tib.tib_pOnRightClickMove(&tib, fdx, fdy, m_pDrawPort);
          _bDummyMouseMove=TRUE;
          SetCursorPos(m_ptMouseCenterScreen.x, m_ptMouseCenterScreen.y);
        }
        else
        {
          tib.tib_pOnRightClickMove(&tib, fdxNotLocked, fdyNotLocked, m_pDrawPort);
        }
        Invalidate(FALSE);
        
        // update tool tip
        if( tib.tib_pGetClickMoveData!=NULL)
        {
          _strToolTip=tib.tib_pGetClickMoveData(&tib, point, m_pDrawPort, FALSE);
          theApp.m_cttToolTips.ManualUpdate();
        }

        if(!tib.tib_bContinueTesting) break;
      }
    }
  }

  CWnd::OnMouseMove(nFlags, point);
}

void CTerrainInterface::OnLButtonUp(UINT nFlags, CPoint point) 
{
  theApp.m_cttToolTips.ManualOff();
  ReleaseCapture();
  _bDummyMouseMove=TRUE;
  if( _bMouseTrapInProgress)
  {
    SetCursorPos(m_ptMouseDownScreen.x, m_ptMouseDownScreen.y);
    _bMouseTrapInProgress=FALSE;
    UnhideCursor();
    m_ptMouseDown.x=-1;
    m_ptMouseDown.y=-1;
  }
  CWnd::OnLButtonUp(nFlags, point);
}

BOOL CTerrainInterface::PreTranslateMessage(MSG* pMsg) 
{
  return CWnd::PreTranslateMessage(pMsg);
}

void CTerrainInterface::OnRButtonDown(UINT nFlags, CPoint point) 
{
	CWnd::OnRButtonDown(nFlags, point);
  SetFocus();

  m_ptMouseDown=point;
  m_ptMouse=point;
  m_ptMouseDownScreen=point;
  ClientToScreen(&m_ptMouseDownScreen);

  FOREACHINDYNAMICCONTAINER( dcButtons, CTIButton, ittib)
  {
    CTIButton &tib=*ittib;
    
    if( IsClicked(tib, point))
    {
      if(tib.tib_pOnRightClick!=NULL)
      {
        tib.tib_pOnRightClick(&tib, m_ptMouse, m_pDrawPort);
        _bMouseTrapInProgress=FALSE;
        UnhideCursor();
        Invalidate(FALSE);
        if(!tib.tib_bContinueTesting) break;
      }
      if(tib.tib_pOnRightClickMove!=NULL)
      {
        // display tool tip
        if( tib.tib_pGetClickMoveData!=NULL)
        {
          CTString strInfo=tib.tib_pGetClickMoveData(&tib, point, m_pDrawPort, TRUE);
          _strToolTip=strInfo;
          theApp.m_cttToolTips.ManualOn( m_ptMouseDownScreen.x, m_ptMouseDownScreen.y, &::GetToolTipText, this);
          SetCapture();
        }
        // if should trap the mouse
        if(tib.tib_bMouseTrapForMove)
        {
          m_ptMouseCenter.x=m_pDrawPort->GetWidth()/2;
          m_ptMouseCenter.y=m_pDrawPort->GetHeight()/2;
          m_ptMouseCenterScreen=m_ptMouseCenter;
          ClientToScreen(&m_ptMouseCenterScreen);
          _bDummyMouseMove=TRUE;
          SetCursorPos(m_ptMouseCenterScreen.x, m_ptMouseCenterScreen.y);
          HideCursor();
          _bMouseTrapInProgress=TRUE;
        }
        if(!tib.tib_bContinueTesting) break;
      }
    }
  }
}

void CTerrainInterface::OnRButtonUp(UINT nFlags, CPoint point) 
{
  theApp.m_cttToolTips.ManualOff();
  ReleaseCapture();
  _bDummyMouseMove=TRUE;
  if( _bMouseTrapInProgress)
  {
    SetCursorPos(m_ptMouseDownScreen.x, m_ptMouseDownScreen.y);
    _bMouseTrapInProgress=FALSE;
    UnhideCursor();
    m_ptMouseDown.x=-1;
    m_ptMouseDown.y=-1;
  }
	CWnd::OnRButtonUp(nFlags, point);
}

void CTerrainInterface::OnIdle(void)
{
  CWorldEditorDoc *pDoc = theApp.GetDocument();
  
  if(!_chAutoGenerateDistribution.IsUpToDate( _udAutoGenerateDistribution))
  {
    GenerateLayerDistribution(_iGenerateForLayer);
    _udAutoGenerateDistribution.MarkUpdated();
  }

  CTerrain *ptrTerrain=GetTerrain();
  if(ptrTerrain!=NULL)
  {
    if(GetLayerIndex()>=ptrTerrain->tr_atlLayers.Count())
    {
      SelectLayer(0);
    }
  }
  BOOL bLMB = (GetKeyState( VK_LBUTTON)&0x8000) != 0;
  BOOL bRMB = (GetKeyState( VK_RBUTTON)&0x8000) != 0;
  if(!bLMB && !bRMB && _bMouseTrapInProgress)
  {
    _bMouseTrapInProgress=FALSE;
    UnhideCursor();
    theApp.m_cttToolTips.ManualOff();
  }
  
  // if should re-initialize interface
  if(pDoc!=NULL && !pDoc->m_chSelections.IsUpToDate( m_udTerrainPage) ||
     !theApp.m_ctTerrainPage.IsUpToDate( m_udTerrainPage) )
  {
    if(m_pDrawPort!=NULL)
    {
      InitializeInterface(m_pDrawPort);
      Invalidate(FALSE);
      m_udTerrainPage.MarkUpdated();
    }
  }
  
  // if should just redraw interface
  if(!theApp.m_ctTerrainPageCanvas.IsUpToDate( m_udTerrainPageCanvas) )
  {
    Invalidate(FALSE);
    m_udTerrainPageCanvas.MarkUpdated();
  }
}


void CTerrainInterface::OnDropFiles(HDROP hDropInfo) 
{
  INDEX iNoOfFiles = DragQueryFile( hDropInfo, 0xFFFFFFFF, NULL, 0);
  
  if( iNoOfFiles != 1)
  {
    AfxMessageBox( L"You can drop only one file at a time.");
    return;
  }

	// buffer for dropped file name
  wchar_t chrFile[ 256];
  // place dropped file name into buffer
  DragQueryFile( hDropInfo, 0, chrFile, 256);
  // create file name from buffer
  CTFileName fnDropped = CTString(CStringA(chrFile));

  CPoint ptMouse;
  GetCursorPos( &ptMouse); 
  ScreenToClient(&ptMouse);

  FOREACHINDYNAMICCONTAINER( dcButtons, CTIButton, ittib)
  {
    CTIButton &tib=*ittib;    
    if( IsClicked(tib, ptMouse) && tib.tib_pOnDropFiles!=NULL)
    {
      tib.tib_pOnDropFiles(&tib, ptMouse, m_pDrawPort, fnDropped);
    }
  }
	CWnd::OnDropFiles(hDropInfo);
}

int CTerrainInterface::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CWnd::OnCreate(lpCreateStruct) == -1)
		return -1;
  DragAcceptFiles();
  EnableToolTips( TRUE);
	return 0;
}

INT_PTR CTerrainInterface::OnToolHitTest( CPoint point, TOOLINFO* pTI ) const
{
  FOREACHINDYNAMICCONTAINER( dcButtons, CTIButton, ittib)
  {
    CTIButton &tib=*ittib;
    
    _strToolTip="";
    if( IsClicked(tib, point))
    {
      if( tib.tib_strToolTip!="" && tib.tib_strToolTip[0]!='_')
      {
        _strToolTip=tib.tib_strToolTip;
        return 1;
      }
    }
  }
  return 0;
}

void CTerrainInterface::InitializeInterface(CDrawPort *pdp)
{
	try
  {
    DECLARE_CTFILENAME( fnTerrainEditIcons, "Textures\\Editor\\TerrainEditingIcons.tex");
    _toIcons.SetData_t(fnTerrainEditIcons);
  }
  catch (const char *strError)
  {
    (void)strError;
    return;
  }
  
  FOREACHINDYNAMICCONTAINER( dcButtons, CTIButton, ittib)
  {
    delete &*ittib;
  }
  dcButtons.Clear();
  CTIButton *ptib;

  COLOR colBcg=RGBToColor(45,45,60)|CT_OPAQUE;
  ptib=AddButton(dcButtons,0,0,LAYER_START_X,pdp->GetHeight(),-1,-1, "_Terrain options area fill", C_dGRAY|CT_TRANSPARENT, colBcg);
  
  ptib=AddButton(dcButtons,1,1,45,45,-1,-1, "Current brush (\\)", C_lGRAY|CT_OPAQUE);
  ptib->SetData(0, CT_BRUSHES, CHANGE_INDEX_SENSITIVITY, FALSE, &theApp.m_fCurrentTerrainBrush, NULL);
  ptib->SetFunctions( RenderBrushShape, NULL, ChangeData, InvokeBrushPalette);

  ptib=AddButton(dcButtons,0,52,LAYER_START_X,8,-1,-1, "Brush pressure (keys 0-10)");
  ptib->SetData(0, 1024.0f, CHANGE_PREASSURE_SENSITIVITY, FALSE, &theApp.m_fTerrainBrushPressure, NULL);
  ptib->SetFunctions( RenderBrushPressure, NULL, ChangeData, InvokePercentageCombo, NULL, UpdatePressure);

  ptib=AddButton(dcButtons,6,64,16,16,-1,19, "Edit mode");
  ptib->SetData(0, CT_EDIT_MODES, CHANGE_INDEX_SENSITIVITY, FALSE, &theApp.m_iTerrainEditMode, NULL);
  UpdateEditModeIcon(ptib, NULL);
  ptib->SetFunctions( NULL, NULL, ChangeData, InvokeEditModeCombo, NULL, UpdateEditModeIcon);
  
  ptib=AddButton(dcButtons,28,64,16,16,-1,-1, "Brush mode");
  ptib->SetData(0, CT_BRUSH_MODES-0.5f, CHANGE_INDEX_SENSITIVITY, FALSE, &theApp.m_iTerrainBrushMode, NULL);
  UpdateBrushModeIcon(ptib, NULL);
  ptib->SetFunctions( NULL, NULL, ChangeData, InvokeBrushModeCombo, NULL, UpdateBrushModeIcon);

  /*
  ptib=AddButton(dcButtons,6,84,16,16,-1,5, "Recalculate shadows (Ctrl+R)");
  ptib->SetFunctions( NULL, RecalculateShadows);
  ptib->tib_pIsEnabled=DisableIfNoTerrainSelected;
  */

  ptib=AddButton(dcButtons,28,84,16,16,-1,25, "Terrain options");
  ptib->SetFunctions( NULL, NULL, NULL, InvokeTerrainOptions);
  ptib->tib_pIsEnabled=DisableIfNoTerrainSelected;

  /*
  ptib=AddButton(dcButtons,6,104,16,16,-1,6, "Generate layer distribution (Ctrl+G)");
  ptib->SetFunctions( NULL, GenerateLayerDistribution);
  ptib->tib_pIsEnabled=DisableIfNoTerrainSelected;
  */

  ptib=AddButton(dcButtons,6,84,16,16,-1,22, "Import/Export");
  ptib->SetFunctions( NULL, NULL, NULL, InvokeImportExportCombo);
  ptib->tib_pIsEnabled=DisableIfNoTerrainSelected;

  CTerrain *ptrTerrain=GetTerrain();
  if( ptrTerrain!=NULL)
  {
    for(INDEX iLayer=0; iLayer<ptrTerrain->tr_atlLayers.Count(); iLayer++)
    {
      CTerrainLayer &tlLayer=ptrTerrain->tr_atlLayers[iLayer];

      ptib=AddButton(dcButtons,0,0,GetLayerSize(pdp)(1)-SLIDER_WIDTH,
        GetLayerSize(pdp)(2),iLayer,-1, "_Layer options area bcg");
      ptib->SetFunctions( RenderLayerBcg, SetAsActiveLayer, NULL, SetAsActiveLayer);
      ptib->tib_bContinueTesting=TRUE;

      ptib=AddButton(dcButtons,
        LAYER_SPACING_V/2, LAYER_SPACING_V/2,
        LAYER_HEIGHT-LAYER_SPACING_V/2-1, LAYER_HEIGHT-LAYER_SPACING_V/2-1, iLayer,-1, "Layer texture",
        C_BLACK|CT_OPAQUE);
      ptib->SetFunctions( RenderLayerTexture, NULL, NULL, InvokeLayerTexturePopup);
      ptib->tib_pOnDropFiles=OnDropIntoLayerTexture;

      ptib=AddButton(dcButtons, LAYER_SPACING_V/2, LAYER_SPACING_V/2, 16, 16, iLayer, -1, "Layer visibility");
      UpdateLayerVisibleFlag(ptib, NULL);
      ptib->tib_pfData1=(FLOAT*)offsetof(CTerrainLayer, tl_bVisible);
      ptib->SetFunctions( NULL, ToggleLayerVisibleFlag, NULL, NULL, NULL, UpdateLayerVisibleFlag);

      ptib=AddButton(dcButtons,
        LAYER_HEIGHT, LAYER_SPACING_V/2,
        LAYER_HEIGHT-LAYER_SPACING_V/2-1, LAYER_HEIGHT-LAYER_SPACING_V/2-1, iLayer,-1, "Layer mask",
        C_BLACK|CT_OPAQUE);
      ptib->SetFunctions( RenderLayerMask, NULL, NULL, InvokeLayerMaskPopup);

      if(tlLayer.tl_ltType==LT_NORMAL)
      {
        ptib=AddButton(dcButtons, LAYER_HEIGHT*2,2,16,16,iLayer,0, "Rotate texture");
        ptib->SetFunctions( NULL, NULL, ChangeTextureRotation);
  
        ptib=AddButton(dcButtons, LAYER_HEIGHT*2,16,16,16,iLayer,2, "Stretch texture");
        ptib->SetFunctions( NULL, NULL, ChangeTextureStretch);
        ptib->tib_pGetClickMoveData=GetStretchInfo;
  
        ptib=AddButton(dcButtons, LAYER_HEIGHT*2,30,16,16,iLayer,1, "Offset texture");
        ptib->SetFunctions( NULL, NULL, ChangeTextureOffset);
  
  #define ICON_COVERAGE_X (LAYER_HEIGHT*2+16)
  #define ICON_FILTER_X (LAYER_HEIGHT*2+40)
  #define ICON_FADE_X   (ICON_FILTER_X+16)
  #define ICON_NOISE_X  (ICON_FADE_X+16)
  #define ICON_SLOPE_OFFSET 56
  #define ICON_LINE1_Y  6
  #define ICON_LINE2_Y  26

        ptib=AddButton(dcButtons, ICON_COVERAGE_X,ICON_LINE1_Y,16,16,iLayer,17, "Layer coverage");
        ptib->SetData(0, 1, CHANGE_NORMALIZED_SENSITIVITY, FALSE, (FLOAT*)offsetof(CTerrainLayer, tl_fCoverage));
        ptib->SetFunctions( NULL, NULL, ChangeLayerDistributionData);
        ptib->tib_pGetClickMoveData=GetNormalizedPercentageInfo;
        ptib=AddButton(dcButtons, ICON_COVERAGE_X,ICON_LINE2_Y,16,16,iLayer,21, "Layer coverage noise");
        ptib->SetData(0, 1, CHANGE_NORMALIZED_SENSITIVITY, FALSE, (FLOAT*)offsetof(CTerrainLayer, tl_fCoverageNoise));
        ptib->SetFunctions( NULL, NULL, ChangeLayerDistributionData);
        ptib->tib_pGetClickMoveData=GetNormalizedPercentageInfo;

        ptib=AddButton(dcButtons, ICON_FILTER_X,  ICON_LINE1_Y,16,16,iLayer,14, "Max altitude");
        ptib->SetData(0, 1, CHANGE_NORMALIZED_SENSITIVITY, FALSE, (FLOAT*)offsetof(CTerrainLayer, tl_fMaxAltitude));
        ptib->SetFunctions( NULL, NULL, ChangeLayerDistributionData);
        ptib->tib_pGetClickMoveData=GetNormalizedPercentageInfo;
        ptib=AddButton(dcButtons, ICON_FADE_X,  ICON_LINE1_Y,16,16,iLayer,29, "Max altitude fade");
        ptib->SetFunctions( NULL, NULL, ChangeLayerDistributionData);
        ptib->tib_pGetClickMoveData=GetNormalizedPercentageInfo;
        ptib->SetData(0, 1, CHANGE_NORMALIZED_SENSITIVITY, FALSE, (FLOAT*)offsetof(CTerrainLayer, tl_fMaxAltitudeFade));
        ptib=AddButton(dcButtons, ICON_NOISE_X, ICON_LINE1_Y,16,16,iLayer,21, "Max altitude noise");
        ptib->SetData(0, 1, CHANGE_NORMALIZED_SENSITIVITY, FALSE, (FLOAT*)offsetof(CTerrainLayer, tl_fMaxAltitudeNoise));
        ptib->SetFunctions( NULL, NULL, ChangeLayerDistributionData);
        ptib->tib_pGetClickMoveData=GetNormalizedPercentageInfo;
  
        ptib=AddButton(dcButtons, ICON_FILTER_X, ICON_LINE2_Y,16,16,iLayer,13, "Min altitude");
        ptib->SetData(0, 1, CHANGE_NORMALIZED_SENSITIVITY, FALSE, (FLOAT*)offsetof(CTerrainLayer, tl_fMinAltitude));
        ptib->SetFunctions( NULL, NULL, ChangeLayerDistributionData);
        ptib->tib_pGetClickMoveData=GetNormalizedPercentageInfo;
        ptib=AddButton(dcButtons, ICON_FADE_X, ICON_LINE2_Y,16,16,iLayer,29, "Min altitude fade");
        ptib->SetData(0, 1, CHANGE_NORMALIZED_SENSITIVITY, FALSE, (FLOAT*)offsetof(CTerrainLayer, tl_fMinAltitudeFade));
        ptib->SetFunctions( NULL, NULL, ChangeLayerDistributionData);
        ptib->tib_pGetClickMoveData=GetNormalizedPercentageInfo;
        ptib=AddButton(dcButtons, ICON_NOISE_X, ICON_LINE2_Y,16,16,iLayer,21, "Min altitude noise");
        ptib->SetData(0, 1, CHANGE_NORMALIZED_SENSITIVITY, FALSE, (FLOAT*)offsetof(CTerrainLayer, tl_fMinAltitudeNoise));
        ptib->SetFunctions( NULL, NULL, ChangeLayerDistributionData);
        ptib->tib_pGetClickMoveData=GetNormalizedPercentageInfo;
  
        ptib=AddButton(dcButtons, ICON_FILTER_X+ICON_SLOPE_OFFSET, ICON_LINE1_Y,16,16,iLayer,16, "Max slope");
        ptib->SetData(0, 1, CHANGE_NORMALIZED_SENSITIVITY, FALSE, (FLOAT*)offsetof(CTerrainLayer, tl_fMaxSlope));
        ptib->SetFunctions( NULL, NULL, ChangeLayerDistributionData);
        ptib->tib_pGetClickMoveData=GetNormalizedPercentageInfo;
        ptib=AddButton(dcButtons, ICON_FADE_X+ICON_SLOPE_OFFSET, ICON_LINE1_Y,16,16,iLayer,29, "Max slope fade");
        ptib->SetData(0, 1, CHANGE_NORMALIZED_SENSITIVITY, FALSE, (FLOAT*)offsetof(CTerrainLayer, tl_fMaxSlopeFade));
        ptib->SetFunctions( NULL, NULL, ChangeLayerDistributionData);
        ptib->tib_pGetClickMoveData=GetNormalizedPercentageInfo;
        ptib=AddButton(dcButtons, ICON_NOISE_X+ICON_SLOPE_OFFSET, ICON_LINE1_Y,16,16,iLayer,21, "Max slope noise");
        ptib->SetData(0, 1, CHANGE_NORMALIZED_SENSITIVITY, FALSE, (FLOAT*)offsetof(CTerrainLayer, tl_fMaxSlopeNoise));
        ptib->SetFunctions( NULL, NULL, ChangeLayerDistributionData);
        ptib->tib_pGetClickMoveData=GetNormalizedPercentageInfo;
  
        ptib=AddButton(dcButtons, ICON_FILTER_X+ICON_SLOPE_OFFSET, ICON_LINE2_Y,16,16,iLayer,15, "Min slope");
        ptib->SetData(0, 1, CHANGE_NORMALIZED_SENSITIVITY, FALSE, (FLOAT*)offsetof(CTerrainLayer, tl_fMinSlope));
        ptib->SetFunctions( NULL, NULL, ChangeLayerDistributionData);
        ptib->tib_pGetClickMoveData=GetNormalizedPercentageInfo;
        ptib=AddButton(dcButtons, ICON_FADE_X+ICON_SLOPE_OFFSET, ICON_LINE2_Y,16,16,iLayer,29, "Min slope fade");
        ptib->SetData(0, 1, CHANGE_NORMALIZED_SENSITIVITY, FALSE, (FLOAT*)offsetof(CTerrainLayer, tl_fMinSlopeFade));
        ptib->SetFunctions( NULL, NULL, ChangeLayerDistributionData);
        ptib->tib_pGetClickMoveData=GetNormalizedPercentageInfo;
        ptib=AddButton(dcButtons, ICON_NOISE_X+ICON_SLOPE_OFFSET, ICON_LINE2_Y,16,16,iLayer,21, "Min slope noise");
        ptib->SetData(0, 1, CHANGE_NORMALIZED_SENSITIVITY, FALSE, (FLOAT*)offsetof(CTerrainLayer, tl_fMinSlopeFade));
        ptib->SetFunctions( NULL, NULL, ChangeLayerDistributionData);
        ptib->tib_pGetClickMoveData=GetNormalizedPercentageInfo;

        ptib=AddButton(dcButtons, GetLayerSize(pdp)(1)-12-SLIDER_WIDTH-1-2,2,12,12,iLayer,-1, "Multiply color", C_BLACK|CT_OPAQUE);
        ptib->SetFunctions( RenderLayerColor, PickLayerColor, NULL, NumericAlpha);

        ptib=AddButton(dcButtons, GetLayerSize(pdp)(1)-16-SLIDER_WIDTH-1,GetLayerSize(pdp)(2)-20,16,16,iLayer,26, "Layer options (Ctrl+Shift+L)");
        ptib->SetFunctions( NULL, InvokeLayerOptions);  
      }
      else if(tlLayer.tl_ltType==LT_TILE)
      {
      }

      ptib=AddButton(dcButtons,0,0,GetLayerSize(pdp)(1)-SLIDER_WIDTH-1,GetLayerSize(pdp)(2),iLayer,-1, "_Layer options area border", C_GRAY|CT_OPAQUE);

      // _Layer click detector
      ptib=AddButton(dcButtons,0,0,GetLayerSize(pdp)(1)-SLIDER_WIDTH,
        GetLayerSize(pdp)(2),iLayer,-1, "_Layer click detector");
      ptib->SetFunctions( NULL, NULL, NULL, InvokeLayerPopup);
    }
  }

  //slider
  ptib=AddButton(dcButtons,pdp->GetWidth()-SLIDER_WIDTH, 0, SLIDER_WIDTH, SLIDER_WIDTH, -1, 23, "_Slider arrow up", C_GRAY|CT_OPAQUE, C_mdGRAY|CT_OPAQUE);
  ptib->SetFunctions(NULL,OnSliderArrowUp);
  ptib=AddButton(dcButtons,pdp->GetWidth()-SLIDER_WIDTH, pdp->GetHeight()-SLIDER_WIDTH, SLIDER_WIDTH, SLIDER_WIDTH, -1, 24, "_Slider arrow down", C_GRAY|CT_OPAQUE, C_mdGRAY|CT_OPAQUE);
  ptib->SetFunctions(NULL,OnSliderArrowDown);
  ptib=AddButton(dcButtons,pdp->GetWidth()-SLIDER_WIDTH, SLIDER_WIDTH, SLIDER_WIDTH, pdp->GetHeight()-SLIDER_WIDTH*2, -1,-1, "_Slider rect", C_dGRAY|CT_OPAQUE, colBcg);
  ptib->SetFunctions(RenderSlider,OnSliderClick, DragSlider);
  ptib->tib_bMouseTrapForMove=FALSE;
}
