/* 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/Math/Vector.h>
#include <Engine/Math/Plane.h>
#include <Engine/Math/Functions.h>
#include <Engine/Math/Geometry.inl>
#include <Engine/Math/Clipping.inl>
#include <Engine/Terrain/Terrain.h>
#include <Engine/Terrain/TerrainMisc.h>
#include <Engine/Entities/Entity.h>
#include <Engine/World/World.h>
#include <Engine/World/WorldRayCasting.h>
#include <Engine/Light/LightSource.h>
#include <Engine/Rendering/Render.h>
#include <Engine/Terrain/TerrainRayCasting.h>

/*
 * Terrain raycasting and colision 
 */

extern CTerrain *_ptrTerrain; // Current terrain
static FLOAT3D _vHitLocation = FLOAT3D(-100,-100,-100);

CStaticStackArray<GFXVertex4> _avExtVertices;
CStaticStackArray<INDEX>      _aiExtIndices;
CStaticStackArray<GFXColor>   _aiExtColors;
CStaticStackArray<INDEX>      _aiHitTiles;

static ULONG *_pulSharedTopMap = NULL; // Shared memory used for topmap regeneration
SLONG  _slSharedTopMapSize = 0; // Size of shared memory allocated for topmap regeneration
extern INDEX  _ctShadowMapUpdates;
#pragma message(">> Create class with destructor to clear shared topmap memory")

FLOATaabbox3D _bboxDrawOne;
FLOATaabbox3D _bboxDrawTwo;

#define NUMDIM	3
#define RIGHT	  0
#define LEFT	  1
#define MIDDLE	2

// Test AABBox agains ray
static BOOL HitBoundingBox(FLOAT3D &vOrigin, FLOAT3D &vDir, FLOAT3D &vHit, FLOATaabbox3D &bbox)
{
	BOOL bInside = TRUE;
	BOOL quadrant[NUMDIM];
	register int i;
	int whichPlane;

  double maxT[NUMDIM];
	double candidatePlane[NUMDIM];
  double minB[NUMDIM], maxB[NUMDIM];  /*box */
  double origin[NUMDIM], dir[NUMDIM]; /*ray */
  double coord[NUMDIM]; /* hit point */

  minB[0]   = bbox.minvect(1); minB[1] = bbox.minvect(2); minB[2] = bbox.minvect(3);
  maxB[0]   = bbox.maxvect(1); maxB[1] = bbox.maxvect(2); maxB[2] = bbox.maxvect(3);
  origin[0] = vOrigin(1); origin[1] = vOrigin(2); origin[2] = vOrigin(3);
  dir[0]    = vDir(1);    dir[1]    = vDir(2);    dir[2]    = vDir(3);

	/* Find candidate planes; this loop can be avoided if
   	rays cast all from the eye(assume perpsective view) */
  for (i=0; i<NUMDIM; i++) {
		if(origin[i] < minB[i]) {
			quadrant[i] = LEFT;
			candidatePlane[i] = minB[i];
			bInside = FALSE;
    } else if (origin[i] > maxB[i]) {
			quadrant[i] = RIGHT;
			candidatePlane[i] = maxB[i];
			bInside = FALSE;
    } else {
			quadrant[i] = MIDDLE;
		}
  }

	/* Ray origin inside bounding box */
	if(bInside)	{
    vHit = FLOAT3D(origin[0],origin[1],origin[2]);
		return (TRUE);
	}


	/* Calculate T distances to candidate planes */
	for (i = 0; i < NUMDIM; i++)
		if (quadrant[i] != MIDDLE && dir[i] !=0.)
			maxT[i] = (candidatePlane[i]-origin[i]) / dir[i];
		else
			maxT[i] = -1.;

	/* Get largest of the maxT's for final choice of intersection */
	whichPlane = 0;
	for (i = 1; i < NUMDIM; i++)
		if (maxT[whichPlane] < maxT[i])
			whichPlane = i;

	/* Check final candidate actually inside box */
	if (maxT[whichPlane] < 0.) return (FALSE);
  for (i = 0; i < NUMDIM; i++) {
		if (whichPlane != i) {
			coord[i] = origin[i] + maxT[whichPlane] *dir[i];
      if (coord[i] < minB[i] || coord[i] > maxB[i]) {
        return (FALSE);
      }
		} else {
			coord[i] = candidatePlane[i];
		}
  }
	return (TRUE);				/* ray hits box */
}


// Test AABBox agains ray
static BOOL RayHitsAABBox(FLOAT3D &vOrigin, FLOAT3D &vDir, FLOAT3D &vHit, FLOATaabbox3D &bbox)
{
  FLOAT minB[3];
  FLOAT maxB[3];
  FLOAT origin[3];
  FLOAT dir[3];
  FLOAT coord[3];
  

  minB[0]   = bbox.minvect(1); minB[1] = bbox.minvect(2); minB[2] = bbox.minvect(3);
  maxB[0]   = bbox.maxvect(1); maxB[1] = bbox.maxvect(2); maxB[2] = bbox.maxvect(3);
  origin[0] = vOrigin(1); origin[1] = vOrigin(2); origin[2] = vOrigin(3);
  dir[0]    = vDir(1);    dir[1]    = vDir(2);    dir[2]    = vDir(3);

	char inside = TRUE;
	char quadrant[3];
	register int i;
	int whichPlane;
	FLOAT maxT[3];
	FLOAT candidatePlane[3];

	/* Find candidate planes; this loop can be avoided if
   	rays cast all from the eye(assume perpsective view) */
	for (i=0; i<3; i++)
		if(origin[i] < minB[i]) {
			quadrant[i] = LEFT;
			candidatePlane[i] = minB[i];
			inside = FALSE;
		}else if (origin[i] > maxB[i]) {
			quadrant[i] = RIGHT;
			candidatePlane[i] = maxB[i];
			inside = FALSE;
		}else	{
			quadrant[i] = MIDDLE;
		}

	/* Ray origin inside bounding box */
	if(inside)	{
    vHit = FLOAT3D(origin[0],origin[1],origin[2]);
    return TRUE;
	}


	/* Calculate T distances to candidate planes */
  for (i = 0; i < 3; i++) {
    if (quadrant[i] != MIDDLE && dir[i] !=0.) {
			maxT[i] = (candidatePlane[i]-origin[i]) / dir[i];
    } else {
			maxT[i] = -1.;
    }
  }

	/* Get largest of the maxT's for final choice of intersection */
	whichPlane = 0;
	for (i = 1; i < 3; i++)
		if (maxT[whichPlane] < maxT[i])
			whichPlane = i;

	/* Check final candidate actually inside box */
    if (maxT[whichPlane] < 0.) {
      return FALSE;
    }
	for (i = 0; i < 3; i++)
		if (whichPlane != i) {
			coord[i] = origin[i] + maxT[whichPlane] *dir[i];
      if (coord[i] < minB[i] || coord[i] > maxB[i]) {
				return FALSE;
      }
		} else {
			coord[i] = candidatePlane[i];
		}

  // ray hits box
  vHit = FLOAT3D(coord[0],coord[1],coord[2]);
	return TRUE;
}

// Get exact hit location in tile
FLOAT GetExactHitLocation(INDEX iTileIndex, FLOAT3D &vOrigin, FLOAT3D &vTarget, FLOAT3D &vHitLocation)
{
  CTerrainTile &tt  = _ptrTerrain->tr_attTiles[iTileIndex];
  QuadTreeNode &qtn = _ptrTerrain->tr_aqtnQuadTreeNodes[iTileIndex];

  GFXVertex *pavVertices;
  INDEX     *paiIndices;
  INDEX      ctVertices;
  INDEX      ctIndices;

  ExtractPolygonsInBox(_ptrTerrain,qtn.qtn_aabbox,&pavVertices,&paiIndices,ctVertices,ctIndices);


  FLOAT fDummyDist = 100000;//(vTarget - vOrigin).Length() * 2; 
  FLOAT fDistance = fDummyDist;

  // for each triangle
  for(INDEX iTri=0;iTri<ctIndices;iTri+=3) {
    INDEX *pind = &paiIndices[iTri];
    GFXVertex &v0 = pavVertices[pind[0]];
    GFXVertex &v1 = pavVertices[pind[1]];
    GFXVertex &v2 = pavVertices[pind[2]];

    FLOAT3D vx0(v0.x,v0.y,v0.z);
    FLOAT3D vx1(v1.x,v1.y,v1.z);
    FLOAT3D vx2(v2.x,v2.y,v2.z);

    FLOATplane3D plTriPlane(vx0,vx1,vx2);
    FLOAT fDistance0 = plTriPlane.PointDistance(vOrigin);
	  FLOAT fDistance1 = plTriPlane.PointDistance(vTarget);

    // if the ray hits the polygon plane
	  if (fDistance0>=0 && fDistance0>=fDistance1) {
		  // calculate fraction of line before intersection
		  FLOAT fFraction = fDistance0/(fDistance0-fDistance1);
		  // calculate intersection coordinate
		  FLOAT3D vHitPoint = vOrigin+(vTarget-vOrigin)*fFraction;
		  // calculate intersection distance
		  FLOAT fHitDistance = (vHitPoint-vOrigin).Length();
		  // if the hit point can not be new closest candidate
		  if (fHitDistance>fDistance) {
			  // skip this triangle
			  continue;
		  }

      // find major axes of the polygon plane
			INDEX iMajorAxis1, iMajorAxis2;
			GetMajorAxesForPlane(plTriPlane, iMajorAxis1, iMajorAxis2);

      // create an intersector
			CIntersector isIntersector(vHitPoint(iMajorAxis1), vHitPoint(iMajorAxis2));

			// check intersections for all three edges of the polygon
			isIntersector.AddEdge(
					vx0(iMajorAxis1), vx0(iMajorAxis2),
					vx1(iMajorAxis1), vx1(iMajorAxis2));
			isIntersector.AddEdge(
					vx1(iMajorAxis1), vx1(iMajorAxis2),
					vx2(iMajorAxis1), vx2(iMajorAxis2));
			isIntersector.AddEdge(
					vx2(iMajorAxis1), vx2(iMajorAxis2),
					vx0(iMajorAxis1), vx0(iMajorAxis2));

      // if the polygon is intersected by the ray, and it is the closest intersection so far
			if (isIntersector.IsIntersecting() && (fHitDistance < fDistance)) {
				// remember hit coordinates
				fDistance = fHitDistance;
        vHitLocation = vHitPoint;
  		}
    }
  }
  if(fDistance!=fDummyDist) {
    _vHitLocation = vHitLocation;
    return fDistance;
  } else {
    return -1;
  }
}

FLOAT3D _vHitBegin;// TEMP
FLOAT3D _vHitEnd;  // TEMP
FLOAT3D _vDirection; // TEMP
FLOAT3D _vHitExact; // TEMP

#pragma message(">> Remove Rect from ExtractPolygonsInBox")
// Extract polygons in given box and returns clipped rectangle
Rect ExtractPolygonsInBox(CTerrain *ptrTerrain, const FLOATaabbox3D &bboxExtract, GFXVertex4 **pavVtx, 
                          INDEX **paiInd, INDEX &ctVtx,INDEX &ctInd,BOOL bFixSize/*=FALSE*/)
{
  ASSERT(ptrTerrain!=NULL);

  FLOATaabbox3D bbox = bboxExtract;
  
  bbox.minvect(1) /= ptrTerrain->tr_vStretch(1);
  bbox.minvect(3) /= ptrTerrain->tr_vStretch(3);
  bbox.maxvect(1) /= ptrTerrain->tr_vStretch(1);
  bbox.maxvect(3) /= ptrTerrain->tr_vStretch(3);
  
  _avExtVertices.PopAll();
  _aiExtIndices.PopAll();
  _aiExtColors.PopAll();

  Rect rc;
  if(!bFixSize) {
    // max vector of bbox in incremented for one, because first vertex is at 0,0,0 in world and in heightmap is at 1,1
    rc.rc_iLeft   = Clamp((INDEX)(bbox.minvect(1)-0),(INDEX)0,ptrTerrain->tr_pixHeightMapWidth);
    rc.rc_iTop    = Clamp((INDEX)(bbox.minvect(3)-0),(INDEX)0,ptrTerrain->tr_pixHeightMapHeight);
    rc.rc_iRight  = Clamp((INDEX)ceil(bbox.maxvect(1)+1),(INDEX)0,ptrTerrain->tr_pixHeightMapWidth);
    rc.rc_iBottom = Clamp((INDEX)ceil(bbox.maxvect(3)+1),(INDEX)0,ptrTerrain->tr_pixHeightMapHeight);
  } else {
    // max vector of bbox in incremented for one, because first vertex is at 0,0,0 in world and in heightmap is at 1,1
    rc.rc_iLeft   = Clamp((INDEX)(bbox.minvect(1)-0),(INDEX)0,ptrTerrain->tr_pixHeightMapWidth);
    rc.rc_iTop    = Clamp((INDEX)(bbox.minvect(3)-0),(INDEX)0,ptrTerrain->tr_pixHeightMapHeight);
    rc.rc_iRight  = Clamp((INDEX)(bbox.maxvect(1)+0),(INDEX)0,ptrTerrain->tr_pixHeightMapWidth);
    rc.rc_iBottom = Clamp((INDEX)(bbox.maxvect(3)+0),(INDEX)0,ptrTerrain->tr_pixHeightMapHeight);
  }

  INDEX iStartX = rc.rc_iLeft;
  INDEX iStartY = rc.rc_iTop;
  INDEX iWidth  = rc.Width();
  INDEX iHeight = rc.Height();

  INDEX iFirst = iStartX + iStartY * ptrTerrain->tr_pixHeightMapWidth;
  INDEX iPitchX = ptrTerrain->tr_pixHeightMapWidth  - iWidth;
  INDEX iPitchY = ptrTerrain->tr_pixHeightMapHeight - iHeight;

  // get first pixel in height map
  UWORD *puwHeight = &ptrTerrain->tr_auwHeightMap[iFirst];
  UBYTE *pubMask   = &ptrTerrain->tr_aubEdgeMap[iFirst];

  INDEX ctVertices = iWidth*iHeight;
  INDEX ctIndices = (iWidth-1)*(iHeight-1)*6;
  
//  ASSERT(ctVertices>0 && ctIndices>0);
  if(ctVertices==0 || ctIndices==0) {
    ctVtx = 0;
    ctInd = 0;
    return Rect(0,0,0,0);
  }

  // Allocate space for vertices and indices
  _avExtVertices.Push(ctVertices);
  _aiExtIndices.Push(ctIndices);

  GFXVertex4 *pavVertices = &_avExtVertices[0];
  INDEX *pauiIndices = &_aiExtIndices[0];

  // for each row
  INDEX iy, ix;
  for(iy=0;iy<iHeight;iy++) {
    // for each column
    for(ix=0;ix<iWidth;ix++) {
      // Add one vertex
      GFXVertex4 &vx = *pavVertices;
      vx.x = (FLOAT)(ix+iStartX)*ptrTerrain->tr_vStretch(1);
      vx.z = (FLOAT)(iy+iStartY)*ptrTerrain->tr_vStretch(3);
      vx.y = *puwHeight * ptrTerrain->tr_vStretch(2);
      vx.shade = *pubMask;

      puwHeight++;
      pubMask++;
      pavVertices++;
    }
    puwHeight+=iPitchX;
    pubMask+=iPitchX;
  }

  INDEX ivx=0;
  INDEX ind=0;
  INDEX iFacing=iFirst;

  GFXVertex *pavExtVtx = &_avExtVertices[0];
  INDEX ctVisTris = 0; // Visible tris

  // for each row
  for(iy=0;iy<iHeight-1;iy++) {
    // for each column
    for(ix=0;ix<iWidth-1;ix++) {
      // Add one quad ( if it is visible )
      if(iFacing&1) {
        // if all vertices in this triangle are visible
        if(pavExtVtx[ivx].shade + pavExtVtx[ivx+iWidth].shade + pavExtVtx[ivx+1].shade == 255*3) { 
          // Add one triangle
          pauiIndices[0] = ivx;
          pauiIndices[1] = ivx+iWidth;
          pauiIndices[2] = ivx+1;
          pauiIndices+=3;
          ctVisTris++;
        }
        // if all vertices in this triangle are visible
        if(pavExtVtx[ivx+1].shade + pavExtVtx[ivx+iWidth].shade + pavExtVtx[ivx+iWidth+1].shade == 255*3) {
          // Add one triangle
          pauiIndices[0] = ivx+1;
          pauiIndices[1] = ivx+iWidth;
          pauiIndices[2] = ivx+iWidth+1;
          pauiIndices+=3;
          ctVisTris++;
        }
      } else {
        // if all vertices in this triangle are visible
        if(pavExtVtx[ivx+iWidth].shade + pavExtVtx[ivx+iWidth+1].shade + pavExtVtx[ivx].shade == 255*3) { 
          // Add one triangle
          pauiIndices[0] = ivx+iWidth;
          pauiIndices[1] = ivx+iWidth+1;
          pauiIndices[2] = ivx;
          pauiIndices+=3;
          ctVisTris++;
        }
        // if all vertices in this triangle are visible
        if(pavExtVtx[ivx].shade + pavExtVtx[ivx+iWidth+1].shade + pavExtVtx[ivx+1].shade == 255*3) {
          // Add one triangle
          pauiIndices[0] = ivx;
          pauiIndices[1] = ivx+iWidth+1;
          pauiIndices[2] = ivx+1;
          pauiIndices+=3;
          ctVisTris++;
        }
      }
      iFacing++;
      ivx++;
    }
    if(iWidth&1) iFacing++;
    ivx++;
  }

  ctVtx = ctVertices;
  ctInd = ctVisTris*3;
  *pavVtx = &_avExtVertices[0];
  *paiInd = &_aiExtIndices[0];
  return rc;
}

void ExtractVerticesInRect(CTerrain *ptrTerrain, Rect &rc, GFXVertex4 **pavVtx, 
                          INDEX **paiInd, INDEX &ctVtx,INDEX &ctInd)
{
  _avExtVertices.PopAll();
  _aiExtIndices.PopAll();
  _aiExtColors.PopAll();

  INDEX iWidth  = rc.Width();
  INDEX iHeight = rc.Height();

  ctVtx = iWidth*iHeight;
  ctInd  = (iWidth-1)*(iHeight-1)*6;

  if(ctVtx==0 || ctInd==0) {
    return;
  }
  _avExtVertices.Push(ctVtx);
  _aiExtIndices.Push(ctInd);

  *pavVtx = &_avExtVertices[0];
  *paiInd = &_aiExtIndices[0];

  INDEX iStartX = rc.rc_iLeft;
  INDEX iStartY = rc.rc_iTop;

  INDEX iFirstHeight = rc.rc_iTop*ptrTerrain->tr_pixHeightMapWidth + rc.rc_iLeft;
  INDEX iStepY       = ptrTerrain->tr_pixHeightMapWidth - iWidth;
  UWORD *puwHeight   = &ptrTerrain->tr_auwHeightMap[iFirstHeight];

  GFXVertex *pavVertices = &_avExtVertices[0];
  INDEX iy, ix;
  for(iy=0;iy<iHeight;iy++) {
    for(ix=0;ix<iWidth;ix++) {
      pavVertices->x = (FLOAT)(ix+iStartX)*ptrTerrain->tr_vStretch(1);
      pavVertices->z = (FLOAT)(iy+iStartY)*ptrTerrain->tr_vStretch(3);
      pavVertices->y = *puwHeight * ptrTerrain->tr_vStretch(2);
      puwHeight++;
      pavVertices++;
    }
    puwHeight+=iStepY;
  }

  INDEX *pauiIndices = &_aiExtIndices[0];
  INDEX ivx=0;
  INDEX ind=0;
  INDEX iFacing=iFirstHeight;
  // for each row
  for(iy=0;iy<iHeight-1;iy++) {
    // for each column
    for(INDEX ix=0;ix<iWidth-1;ix++) {
      if(iFacing&1) {
        pauiIndices[0] = ivx;   pauiIndices[1] = ivx+iWidth; pauiIndices[2] = ivx+1;
        pauiIndices[3] = ivx+1; pauiIndices[4] = ivx+iWidth; pauiIndices[5] = ivx+iWidth+1;
      } else {
        pauiIndices[0] = ivx+iWidth; pauiIndices[1] = ivx+iWidth+1; pauiIndices[2] = ivx;
        pauiIndices[3] = ivx;        pauiIndices[4] = ivx+iWidth+1; pauiIndices[5] = ivx+1;
      }
      // Add one quad
      pauiIndices+=6;
      iFacing++;
      ivx++;
    }
    if(iWidth&1) iFacing++;
    ivx++;
  }
}

// Extract all tiles that intersect with given box
void FindTilesInBox(CTerrain *ptrTerrain, FLOATaabbox3D &bbox)
{
  ASSERT(ptrTerrain!=NULL);
  _aiHitTiles.PopAll();
  // for each terrain tile
  for(INDEX itt=0;itt<_ptrTerrain->tr_ctTiles;itt++) {
    QuadTreeNode &qtn = _ptrTerrain->tr_aqtnQuadTreeNodes[itt];
    // if it is coliding with given box
    if(qtn.qtn_aabbox.HasContactWith(bbox)) {
      // add it to array of coliding tiles
      INDEX &iHitTile = _aiHitTiles.Push();
      iHitTile = itt;
    }
  }
}

// Add these flags to all tiles that have been extracted
void AddFlagsToExtractedTiles(ULONG ulFlags)
{
  ASSERT(_ptrTerrain!=NULL);
  // for each tile that has contact with extraction box
  INDEX ctht = _aiHitTiles.Count();
  for(INDEX iht=0;iht<ctht;iht++) {
    // Add tile to regen queue
    INDEX iTileIndex = _aiHitTiles[iht];
    CTerrainTile &tt = _ptrTerrain->tr_attTiles[iTileIndex];
    tt.AddFlag(ulFlags);
    _ptrTerrain->AddTileToRegenQueue(iTileIndex);
  }
}

// Get value from layer at given point
UBYTE GetValueFromMask(CTerrain *ptrTerrain, INDEX iLayer, FLOAT3D vHitPoint)
{
  ASSERT(ptrTerrain!=NULL);
  ASSERT(ptrTerrain->tr_penEntity!=NULL);
  
  CEntity *penEntity = ptrTerrain->tr_penEntity;
  // convert hit point to terrain space and remove terrain stretch from terrain
  FLOAT3D vHit = (vHitPoint - penEntity->en_plPlacement.pl_PositionVector) * !penEntity->en_mRotation;
  vHit(1)=ceil(vHit(1)/ptrTerrain->tr_vStretch(1));
  vHit(3)=ceil(vHit(3)/ptrTerrain->tr_vStretch(3));

  CTerrainLayer &tl = ptrTerrain->GetLayer(iLayer);
  INDEX iVtx = (INDEX) (vHit(1) + tl.tl_iMaskWidth*vHit(3));
  if(iVtx<0 || iVtx>=tl.tl_iMaskWidth*tl.tl_iMaskHeight) {
    ASSERTALWAYS("Invalid hit point");
    return 0;
  }
  UBYTE ubValue = tl.tl_aubColors[iVtx];
  return ubValue;
}

// Allocate memory of one top map
void CreateTexture(CTextureData &tdTopMap, PIX pixWidth, PIX pixHeight,ULONG ulFlags)
{
  // clear current top map
  if(tdTopMap.td_pulFrames!=NULL) {
    FreeMemory( tdTopMap.td_pulFrames);
    tdTopMap.td_pulFrames = NULL;
  }

  // Create new top map
  tdTopMap.td_mexWidth  = pixWidth;
  tdTopMap.td_mexHeight = pixHeight;
  tdTopMap.td_ulFlags   = ulFlags;
  // Allocate memory for top map
  INDEX ctMipMaps = GetNoOfMipmaps(pixWidth,pixHeight);
  SLONG slSize = GetMipmapOffset(ctMipMaps,pixWidth,pixHeight)*BYTES_PER_TEXEL;
  tdTopMap.td_pulFrames = (ULONG*)AllocMemory(slSize);
  tdTopMap.td_slFrameSize = slSize;
  tdTopMap.td_ctFrames = 1;
  tdTopMap.td_iFirstMipLevel = 0;
  tdTopMap.td_ctFineMipLevels = GetNoOfMipmaps(pixWidth,pixHeight);
  // Prepare dithering type
  tdTopMap.td_ulInternalFormat = TS.ts_tfRGBA8;
}

// Create one topmap
void CreateTopMap(CTextureData &tdTopMap, PIX pixWidth , PIX pixHeight)
{
  ASSERT(tdTopMap.td_pulFrames==NULL);
  // Prepare new top map
  INDEX ctMipMaps = GetNoOfMipmaps(pixWidth,pixHeight);
  SLONG slSize = GetMipmapOffset(ctMipMaps,pixWidth,pixHeight)*BYTES_PER_TEXEL;

  tdTopMap.td_mexWidth  = pixWidth;
  tdTopMap.td_mexHeight = pixHeight;
  tdTopMap.td_ulFlags   = TEX_ALPHACHANNEL|TEX_STATIC; // Pretend this texture is static
  tdTopMap.td_pulFrames = NULL; // This will be shared memory
  tdTopMap.td_slFrameSize = slSize;
  tdTopMap.td_ctFrames = 1;
  tdTopMap.td_iFirstMipLevel = 0;
  tdTopMap.td_ctFineMipLevels = GetNoOfMipmaps(pixWidth,pixHeight);
  tdTopMap.td_ulInternalFormat = TS.ts_tfRGB5A1;
}

// Set topmap frames pointer to shared memory
void PrepareSharedTopMapMemory(CTextureData *ptdTopMap, INDEX iTileIndex)
{
  SLONG slSize = ptdTopMap->td_slFrameSize;
  // if this is global top map
  if(iTileIndex==(-1)) {
    // if shared memory is larger then global top map
    if(slSize<=_slSharedTopMapSize && _pulSharedTopMap!=NULL) {
      // assign pointer of global top map to shared memory
      ptdTopMap->td_pulFrames = _pulSharedTopMap;
      return;
    // else
    } else {
      // Allocate new memory for global top map
      ptdTopMap->td_pulFrames = (ULONG*)AllocMemory(slSize);
    }
  // else this is normal top map
  } else {
    // if required memory is larger than currently allocated one
    if(slSize>_slSharedTopMapSize) {
      // if shared memory exists
      if(_pulSharedTopMap!=NULL) {
        // free current shared memory
        FreeMemory(_pulSharedTopMap);
        _pulSharedTopMap = NULL;
      }
      // allocate new shared memory for top maps
      _pulSharedTopMap = (ULONG*)AllocMemory(slSize);
      // remember new memory size
      _slSharedTopMapSize = slSize;
    }
    // assign pointer of top map to shared memory
    ptdTopMap->td_pulFrames = _pulSharedTopMap;
  }
}

void FreeSharedTopMapMemory(CTextureData *ptdTopMap, INDEX iTileIndex)
{
  // if this is global top map
  if(iTileIndex==(-1)) {
    // if global top map isn't using shared memory
    if(ptdTopMap->td_pulFrames!=_pulSharedTopMap) {
      // free memory global top map is using
      FreeMemory(ptdTopMap->td_pulFrames);
    }
  }
  // Just clear pointer to memory
  ptdTopMap->td_pulFrames = NULL;
}

static FLOAT3D CalculateNormalFromPoint(FLOAT fPosX, FLOAT fPosZ, FLOAT3D *pvStrPos=NULL)
{
  FLOAT3D vNormal;
  INDEX iPosX = (INDEX)fPosX;
  INDEX iPosZ = (INDEX)fPosZ;
  FLOAT fLerpX = fPosX - iPosX;
  FLOAT fLerpZ = fPosZ - iPosZ;

  FLOAT3D avVtx[4];
  INDEX iHMapWidth = _ptrTerrain->tr_pixHeightMapWidth;
  FLOAT3D vStretch = _ptrTerrain->tr_vStretch;

  avVtx[0](1) = (FLOAT)(iPosX  ) * vStretch(1);
  avVtx[1](1) = (FLOAT)(iPosX+1) * vStretch(1);
  avVtx[2](1) = (FLOAT)(iPosX  ) * vStretch(1);
  avVtx[3](1) = (FLOAT)(iPosX+1) * vStretch(1);

  avVtx[0](3) = (FLOAT)(iPosZ  ) * vStretch(3);
  avVtx[1](3) = (FLOAT)(iPosZ  ) * vStretch(3);
  avVtx[2](3) = (FLOAT)(iPosZ+1) * vStretch(3);
  avVtx[3](3) = (FLOAT)(iPosZ+1) * vStretch(3);

  avVtx[0](2) = (FLOAT)_ptrTerrain->tr_auwHeightMap[ (iPosX  ) + (iPosZ  )*iHMapWidth ] * vStretch(2);
  avVtx[1](2) = (FLOAT)_ptrTerrain->tr_auwHeightMap[ (iPosX+1) + (iPosZ  )*iHMapWidth ] * vStretch(2);
  avVtx[2](2) = (FLOAT)_ptrTerrain->tr_auwHeightMap[ (iPosX  ) + (iPosZ+1)*iHMapWidth ] * vStretch(2);
  avVtx[3](2) = (FLOAT)_ptrTerrain->tr_auwHeightMap[ (iPosX+1) + (iPosZ+1)*iHMapWidth ] * vStretch(2);

  FLOAT fHDeltaX = Lerp(avVtx[1](2)-avVtx[0](2), avVtx[3](2)-avVtx[2](2), fLerpZ);
  FLOAT fHDeltaZ = Lerp(avVtx[0](2)-avVtx[2](2), avVtx[1](2)-avVtx[3](2), fLerpX);
  FLOAT fDeltaX  = avVtx[1](1) - avVtx[0](1);
  FLOAT fDeltaZ  = avVtx[0](3) - avVtx[2](3);

  vNormal(2) = sqrt(1 / (((fHDeltaX*fHDeltaX)/(fDeltaX*fDeltaX)) + ((fHDeltaZ*fHDeltaZ)/(fDeltaZ*fDeltaZ)) + 1));
  vNormal(1) = sqrt(vNormal(2)*vNormal(2) * ((fHDeltaX*fHDeltaX) / (fDeltaX*fDeltaX)));
  vNormal(3) = sqrt(vNormal(2)*vNormal(2) * ((fHDeltaZ*fHDeltaZ) / (fDeltaZ*fDeltaZ)));
  if (fHDeltaX>0) {
    vNormal(1) = -vNormal(1);
  }
  if (fHDeltaZ<0) {
    vNormal(3) = -vNormal(3);
  }
  ASSERT(Abs(vNormal.Length()-1)<0.01);

  if(pvStrPos!=NULL) {
    FLOAT fResX1 = Lerp(avVtx[0](2),avVtx[1](2),fLerpX);
    FLOAT fResX2 = Lerp(avVtx[2](2),avVtx[3](2),fLerpX);
    FLOAT fPosY  = Lerp(fResX1,fResX2,fLerpZ);

    (*pvStrPos)(1) = fPosX * vStretch(1);
    (*pvStrPos)(2) = fPosY; // * vStretch(2);
    (*pvStrPos)(3) = fPosZ * vStretch(3);
  }

  return vNormal;
}

static void CalcPointLight(CPlacement3D &plLight, CLightSource *plsLight, Rect &rcUpdate)
{
  FLOAT fSHDiffX = (FLOAT)_ptrTerrain->tr_pixHeightMapWidth  / _ptrTerrain->GetShadowMapWidth();
  FLOAT fSHDiffZ = (FLOAT)_ptrTerrain->tr_pixHeightMapHeight / _ptrTerrain->GetShadowMapHeight();

  PIX pixLeft   = rcUpdate.rc_iLeft;
  PIX pixRight  = rcUpdate.rc_iRight;
  PIX pixTop    = rcUpdate.rc_iTop;
  PIX pixBottom = rcUpdate.rc_iBottom;
  PIX pixWidth  = pixRight - pixLeft;
  PIX pixStepX  = _ptrTerrain->GetShadowMapWidth()  - pixWidth;

  // Get color pointer in shadow map
  PIX       pixFirst   = pixLeft + pixTop*_ptrTerrain->GetShadowMapWidth();
  GFXColor *pacolData  = (GFXColor*)&_ptrTerrain->tr_tdShadowMap.td_pulFrames[pixFirst];

  // for each row in shadow map
  for(PIX pixY=pixTop;pixY<pixBottom;pixY++) {
    // for each in column
    for(PIX pixX=pixLeft;pixX<pixRight;pixX++) {
      FLOAT fPosX = (FLOAT)(pixX*fSHDiffX);
      FLOAT fPosZ = (FLOAT)(pixY*fSHDiffZ);

      FLOAT3D vPosStr;
      FLOAT3D vNormal = CalculateNormalFromPoint(fPosX,fPosZ,&vPosStr);
      
      // Calculate normal from light position
      FLOAT3D vDistance = vPosStr - plLight.pl_PositionVector;
      FLOAT   fDistance = vDistance.Length();
      FLOAT3D vLightNormal = -vDistance.Normalize();
      GFXColor colLight   = plsLight->GetLightColor();

      // Calculate light intensity
      FLOAT fIntensity = 1.0f;
      FLOAT fFallOff   = plsLight->ls_rFallOff;
      FLOAT fHotSpot   = plsLight->ls_rHotSpot;
      if(fDistance>fFallOff) {
        fIntensity = 0;
      } else if(fDistance>fHotSpot) {
        fIntensity = CalculateRatio(fDistance, fHotSpot, fFallOff, 0.0f, 1.0f);
      }
      ULONG ulIntensity = NormFloatToByte(fIntensity);
      ulIntensity = (ulIntensity<<CT_RSHIFT)|(ulIntensity<<CT_GSHIFT)|(ulIntensity<<CT_BSHIFT);
      colLight = MulColors(ByteSwap(colLight.ul.abgr), ulIntensity);


      FLOAT fDot = vNormal%vLightNormal;
      fDot = Clamp(fDot,0.0f,1.0f);
      SLONG slDot = NormFloatToByte(fDot);

      pacolData->ub.r = ClampUp(pacolData->ub.r + ((colLight.ub.r*slDot)>>8),255L);
      pacolData->ub.g = ClampUp(pacolData->ub.g + ((colLight.ub.g*slDot)>>8),255L);
      pacolData->ub.b = ClampUp(pacolData->ub.b + ((colLight.ub.b*slDot)>>8),255L);
      pacolData->ub.a = 255;
      pacolData++;
    }
    pacolData+=pixStepX;
  }
}

static void CalcDirectionalLight(CPlacement3D &plLight, CLightSource *plsLight, Rect &rcUpdate)
{
  FLOAT fSHDiffX = (FLOAT)_ptrTerrain->tr_pixHeightMapWidth  / _ptrTerrain->GetShadowMapWidth();
  FLOAT fSHDiffZ = (FLOAT)_ptrTerrain->tr_pixHeightMapHeight / _ptrTerrain->GetShadowMapHeight();

  PIX pixLeft   = rcUpdate.rc_iLeft;
  PIX pixRight  = rcUpdate.rc_iRight;
  PIX pixTop    = rcUpdate.rc_iTop;
  PIX pixBottom = rcUpdate.rc_iBottom;
  PIX pixWidth  = pixRight - pixLeft;
  PIX pixStepX  = _ptrTerrain->GetShadowMapWidth()  - pixWidth;

  // Get color pointer in shadow map
  PIX       pixFirst   = pixLeft + pixTop*_ptrTerrain->GetShadowMapWidth();
  GFXColor *pacolData  = (GFXColor*)&_ptrTerrain->tr_tdShadowMap.td_pulFrames[pixFirst];

  FLOAT3D vLightNormal;
  GFXColor colLight   = plsLight->GetLightColor();
  GFXColor colAmbient = plsLight->GetLightAmbient();

  UBYTE ubColShift = 8;
  SLONG slar = colAmbient.ub.r;
  SLONG slag = colAmbient.ub.g;
  SLONG slab = colAmbient.ub.b;

  extern INDEX mdl_bAllowOverbright;
  BOOL bOverBrightning = mdl_bAllowOverbright && _pGfx->gl_ctTextureUnits>1;

  // is overbrightning enabled
  if(bOverBrightning) {
    slar = ClampUp(slar,127L);
    slag = ClampUp(slag,127L);
    slab = ClampUp(slab,127L);
    ubColShift = 8;
  } else {
    slar*=2;
    slag*=2;
    slab*=2;
    ubColShift = 7;
  }

  // Calculate light normal
  AnglesToDirectionVector(plLight.pl_OrientationAngle,vLightNormal);
  vLightNormal *= !_ptrTerrain->tr_penEntity->en_mRotation;
  vLightNormal = -vLightNormal.Normalize();

  // for each row in shadow map
  for(PIX pixY=pixTop;pixY<pixBottom;pixY++) {
    // for each in column
    for(PIX pixX=pixLeft;pixX<pixRight;pixX++) {
      FLOAT fPosX = (FLOAT)(pixX*fSHDiffX);
      FLOAT fPosZ = (FLOAT)(pixY*fSHDiffZ);
      FLOAT3D vNormal = CalculateNormalFromPoint(fPosX,fPosZ);

      FLOAT fDot = vNormal%vLightNormal;
      fDot = Clamp(fDot,0.0f,1.0f);
      SLONG slDot = NormFloatToByte(fDot);

      pacolData->ub.r = ClampUp(pacolData->ub.r + slar + ((colLight.ub.r*slDot)>>ubColShift),255L);
      pacolData->ub.g = ClampUp(pacolData->ub.g + slag + ((colLight.ub.g*slDot)>>ubColShift),255L);
      pacolData->ub.b = ClampUp(pacolData->ub.b + slab + ((colLight.ub.b*slDot)>>ubColShift),255L);
      pacolData->ub.a = 255;
      pacolData++;
    }
    pacolData+=pixStepX;
  }
}

static void ClearPartOfShadowMap(CTerrain *ptrTerrain, Rect &rcUpdate)
{
  PIX pixLeft   = rcUpdate.rc_iLeft;
  PIX pixRight  = rcUpdate.rc_iRight;
  PIX pixTop    = rcUpdate.rc_iTop;
  PIX pixBottom = rcUpdate.rc_iBottom;
  PIX pixWidth  = pixRight - pixLeft;
  PIX pixStepX  = _ptrTerrain->GetShadowMapWidth() - pixWidth;

  // Get color pointer in shadow map
  PIX pixFirst = rcUpdate.rc_iLeft + rcUpdate.rc_iTop * ptrTerrain->GetShadowMapWidth();
  GFXColor *pacolData  = (GFXColor*)&_ptrTerrain->tr_tdShadowMap.td_pulFrames[pixFirst];
  // for each row in shadow map
  for(PIX pixY=pixTop;pixY<pixBottom;pixY++) {
    // for each in column
    for(PIX pixX=pixLeft;pixX<pixRight;pixX++) {
      *pacolData = 0x00000000;
      pacolData++;
    }
    pacolData+=pixStepX;
  }
}

static Rect GetUpdateRectFromBox(CTerrain *ptrTerrain, FLOATaabbox3D &boxUpdate)
{
  Rect rcUpdate;
  // Prepare update rect
  FLOAT fSHDiffX = (FLOAT)ptrTerrain->tr_pixHeightMapWidth  / ptrTerrain->GetShadowMapWidth();
  FLOAT fSHDiffZ = (FLOAT)ptrTerrain->tr_pixHeightMapHeight / ptrTerrain->GetShadowMapHeight();
  rcUpdate.rc_iLeft   = (INDEX)floor((boxUpdate.minvect(1)/ptrTerrain->tr_vStretch(1)) / fSHDiffX);
  rcUpdate.rc_iRight  = (INDEX)ceil ((boxUpdate.maxvect(1)/ptrTerrain->tr_vStretch(1)) / fSHDiffX);
  rcUpdate.rc_iTop    = (INDEX)floor((boxUpdate.minvect(3)/ptrTerrain->tr_vStretch(3)) / fSHDiffZ);
  rcUpdate.rc_iBottom = (INDEX)ceil ((boxUpdate.maxvect(3)/ptrTerrain->tr_vStretch(3)) / fSHDiffZ);
  return rcUpdate;
}

static FLOATaabbox3D AbsoluteToRelative(const CTerrain *ptrTerrain, const FLOATaabbox3D &bbox)
{
  ASSERT(ptrTerrain!=NULL);
  ASSERT(ptrTerrain->tr_penEntity!=NULL);
  FLOATaabbox3D bboxRelative;
  CEntity *pen = ptrTerrain->tr_penEntity;
  
  #define TRANSPT(x) (x-pen->en_plPlacement.pl_PositionVector) * !pen->en_mRotation
  bboxRelative  = TRANSPT(FLOAT3D(bbox.minvect(1),bbox.minvect(2),bbox.minvect(3)));
  bboxRelative |= TRANSPT(FLOAT3D(bbox.minvect(1),bbox.minvect(2),bbox.maxvect(3)));
  bboxRelative |= TRANSPT(FLOAT3D(bbox.maxvect(1),bbox.minvect(2),bbox.minvect(3)));
  bboxRelative |= TRANSPT(FLOAT3D(bbox.maxvect(1),bbox.minvect(2),bbox.maxvect(3)));
  bboxRelative |= TRANSPT(FLOAT3D(bbox.minvect(1),bbox.maxvect(2),bbox.minvect(3)));
  bboxRelative |= TRANSPT(FLOAT3D(bbox.minvect(1),bbox.maxvect(2),bbox.maxvect(3)));
  bboxRelative |= TRANSPT(FLOAT3D(bbox.maxvect(1),bbox.maxvect(2),bbox.minvect(3)));
  bboxRelative |= TRANSPT(FLOAT3D(bbox.maxvect(1),bbox.maxvect(2),bbox.maxvect(3)));
  return bboxRelative;
}

static ULONG ulTemp = 0xFFFFFFFF;

void UpdateTerrainShadowMap(CTerrain *ptrTerrain, FLOATaabbox3D *pboxUpdate/*=NULL*/, BOOL bAbsoluteSpace/*=FALSE*/)
{
  // if this is not world editor app 
  extern BOOL _bWorldEditorApp;
  if(!_bWorldEditorApp) {

    ASSERTALWAYS("Terrain shadow map can only be updated from world editor!");
    return;
  }

  ASSERT(ptrTerrain!=NULL);
  ASSERT(ptrTerrain->tr_penEntity!=NULL);
  ASSERT(ptrTerrain->tr_penEntity->en_pwoWorld!=NULL);
  
  FLOATaabbox3D boxUpdate;
  FLOATaabbox3D boxAllTerrain;
  CEntity *penEntity = ptrTerrain->tr_penEntity;

  ptrTerrain->GetAllTerrainBBox(boxAllTerrain);
  // if request to update whole terrain is given
  if(pboxUpdate==NULL) {
    // take all terrain bbox as update box
    boxUpdate = boxAllTerrain;
  } else {
    // use given bbox as update box
    boxUpdate = *pboxUpdate;
    if(bAbsoluteSpace) {
      boxUpdate = AbsoluteToRelative(ptrTerrain, boxUpdate);
    }
    
    // do not update terrain if update box isn't in terrain box
    if(!boxUpdate.HasContactWith(boxAllTerrain)) {
      return;
    }

    boxUpdate.minvect(1) = Clamp(boxUpdate.minvect(1),boxAllTerrain.minvect(1),boxAllTerrain.maxvect(1));
    boxUpdate.minvect(3) = Clamp(boxUpdate.minvect(3),boxAllTerrain.minvect(3),boxAllTerrain.maxvect(3));
    boxUpdate.maxvect(1) = Clamp(boxUpdate.maxvect(1),boxAllTerrain.minvect(1),boxAllTerrain.maxvect(1));
    boxUpdate.maxvect(3) = Clamp(boxUpdate.maxvect(3),boxAllTerrain.minvect(3),boxAllTerrain.maxvect(3));
    boxUpdate.minvect(2) = boxAllTerrain.minvect(2);
    boxUpdate.maxvect(2) = boxAllTerrain.maxvect(2);
  }

  _ptrTerrain = ptrTerrain;
  // Get pointer to world that holds this terrain
  CWorld *pwldWorld = penEntity->en_pwoWorld;
  
  PIX pixWidth  = ptrTerrain->GetShadowMapWidth();
  PIX pixHeight = ptrTerrain->GetShadowMapHeight();

  CTextureData &tdShadowMap = ptrTerrain->tr_tdShadowMap;
  ASSERT(tdShadowMap.td_pulFrames!=NULL);

  Rect rcUpdate = GetUpdateRectFromBox(ptrTerrain, boxUpdate);
  // Clear part of shadow map that will be updated
  ClearPartOfShadowMap(ptrTerrain,rcUpdate);

  // for each entity in the world
  FOREACHINDYNAMICCONTAINER(pwldWorld->wo_cenEntities, CEntity, iten) {
    // if it is light entity and it influences the given range
    CLightSource *pls = iten->GetLightSource();
    CPlacement3D plLight = iten->en_plPlacement;
    
    // Translate light placement to terrain space
    plLight.pl_PositionVector = 
     (plLight.pl_PositionVector - penEntity->en_plPlacement.pl_PositionVector) * !penEntity->en_mRotation;

    if (pls!=NULL) {
      // Get light bounding box
      FLOATaabbox3D boxLight(plLight.pl_PositionVector, pls->ls_rFallOff);
      // if light is directional
      if(pls->ls_ulFlags &LSF_DIRECTIONAL) {
        // Calculate lightning
        CalcDirectionalLight(plLight,pls,rcUpdate);
      // if it is point light
      } else {
        _bboxDrawOne = boxLight;
        _bboxDrawTwo = boxUpdate;
        // if point light box have contact with update box
        if(boxLight.HasContactWith(boxUpdate)) {
          _ctShadowMapUpdates++;

          // if light box is inside update box
          if(boxLight.minvect(1)>=boxUpdate.minvect(1) && boxLight.minvect(3)>boxUpdate.minvect(3) && 
            boxLight.maxvect(1)<=boxUpdate.maxvect(1) && boxLight.maxvect(3)<=boxUpdate.maxvect(3)) {
            // Recalculate only light box
            Rect rcLightUpdate = GetUpdateRectFromBox(ptrTerrain,boxLight);
            CalcPointLight(plLight,pls,rcLightUpdate);
          // else 
          } else {
            // Recalculate update box
            CalcPointLight(plLight,pls,rcUpdate);
          }
        }
      }
    }
  }

  // Create shadow map mipmaps 
  INDEX ctMipMaps = GetNoOfMipmaps(tdShadowMap.td_mexWidth,tdShadowMap.td_mexHeight);
  MakeMipmaps(ctMipMaps, tdShadowMap.td_pulFrames, tdShadowMap.td_mexWidth, tdShadowMap.td_mexHeight);


  // Update shading map from one mip of shadow map
  INDEX iMipOffset = GetMipmapOffset(ptrTerrain->tr_iShadingMapSizeAspect,ptrTerrain->GetShadowMapWidth(),ptrTerrain->GetShadowMapHeight());
  UWORD *puwShade = &ptrTerrain->tr_auwShadingMap[0];
  ULONG *ppixShadowMip = &ptrTerrain->tr_tdShadowMap.td_pulFrames[iMipOffset];

  INDEX ctpixs = ptrTerrain->GetShadingMapWidth()*ptrTerrain->GetShadingMapHeight();
  for(PIX ipix=0;ipix<ctpixs;ipix++) {
    ULONG ulPixel = ByteSwap(*ppixShadowMip);
    // ULONG ulPixel = ulTemp;
    *puwShade = (((ulPixel>>27)&0x001F)<<10) | 
                (((ulPixel>>19)&0x001F)<< 5) | 
                (((ulPixel>>11)&0x001F)<< 0);
    puwShade++;
    ppixShadowMip++;
  }

  // discard cached model info
  ptrTerrain->DiscardShadingInfos();

  ptrTerrain->tr_tdShadowMap.SetAsCurrent(0,TRUE);
}


// Calculate 2d relative point in terrain from absolute 3d point in world
Point Calculate2dHitPoint(CTerrain *ptrTerrain, FLOAT3D &vHitPoint)
{
  ASSERT(ptrTerrain!=NULL);
  ASSERT(ptrTerrain->tr_penEntity!=NULL);

  // Get entity that holds this terrain
  CEntity *penEntity = ptrTerrain->tr_penEntity;
  // Get relative hit point
  FLOAT3D vRelHitPoint = (vHitPoint - penEntity->en_plPlacement.pl_PositionVector) * !penEntity->en_mRotation;
  
  // Unstretch hit point and convert it to 2d
  Point pt;
  pt.pt_iX = (INDEX) (ceil(vRelHitPoint(1) / ptrTerrain->tr_vStretch(1) - 0.5f));
  pt.pt_iY = (INDEX) (ceil(vRelHitPoint(3) / ptrTerrain->tr_vStretch(3) - 0.5f));
  
  return pt;
}

// Calculate tex coords on shading map from absolute 3d point in world
FLOAT2D CalculateShadingTexCoords(CTerrain *ptrTerrain, FLOAT3D &vPoint)
{
  ASSERT(ptrTerrain!=NULL);
  ASSERT(ptrTerrain->tr_penEntity!=NULL);
  // Get entity that holds this terrain
  CEntity *penEntity = ptrTerrain->tr_penEntity;
  // Get relative hit point
  FLOAT3D vRelPoint = (vPoint - penEntity->en_plPlacement.pl_PositionVector) * !penEntity->en_mRotation;

  // Unstretch hit point and convert it to 2d point in shading map
  FLOAT fX = vRelPoint(1) / ptrTerrain->tr_vStretch(1);
  FLOAT fY = vRelPoint(3) / ptrTerrain->tr_vStretch(3);
  FLOAT fU = fX / ((FLOAT)(ptrTerrain->tr_pixHeightMapWidth)  / ptrTerrain->GetShadingMapWidth());
  FLOAT fV = fY / ((FLOAT)(ptrTerrain->tr_pixHeightMapHeight) / ptrTerrain->GetShadingMapHeight());
  
  ASSERT(fU>0.0f && fU<ptrTerrain->GetShadingMapWidth());
  ASSERT(fV>0.0f && fV<ptrTerrain->GetShadingMapHeight());
  return FLOAT2D(fU,fV);
}
