Serious-Engine/Sources/Engine/Terrain/TerrainMisc.cpp

1079 lines
37 KiB
C++
Executable File

/* 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_T> _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
#if 0 // DG: unused.
// 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;
}
#endif // 0 (unused)
// 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_T *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_T *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_T **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
#ifdef __arm__
#if defined(PLATFORM_PANDORA) || defined(PLATFORM_PYRA)
#define Isinf(a) (((*(unsigned int*)&a)&0x7fffffff)==0x7f800000)
#else
#define Isinf isinff
#endif
rc.rc_iLeft = (Isinf(bbox.minvect(1)))?(INDEX)0:Clamp((INDEX)(bbox.minvect(1)-0),(INDEX)0,ptrTerrain->tr_pixHeightMapWidth);
rc.rc_iTop = (Isinf(bbox.minvect(3)))?(INDEX)0:Clamp((INDEX)(bbox.minvect(3)-0),(INDEX)0,ptrTerrain->tr_pixHeightMapHeight);
rc.rc_iRight = (Isinf(bbox.maxvect(1)))?(INDEX)0:Clamp((INDEX)ceil(bbox.maxvect(1)+1),(INDEX)0,ptrTerrain->tr_pixHeightMapWidth);
rc.rc_iBottom = (Isinf(bbox.maxvect(3)))?(INDEX)0:Clamp((INDEX)ceil(bbox.maxvect(3)+1),(INDEX)0,ptrTerrain->tr_pixHeightMapHeight);
#else
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);
#endif
} 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
#ifdef __arm__
rc.rc_iLeft = (Isinf(bbox.minvect(1)))?(INDEX)0:Clamp((INDEX)(bbox.minvect(1)-0),(INDEX)0,ptrTerrain->tr_pixHeightMapWidth);
rc.rc_iTop = (Isinf(bbox.minvect(3)))?(INDEX)0:Clamp((INDEX)(bbox.minvect(3)-0),(INDEX)0,ptrTerrain->tr_pixHeightMapHeight);
rc.rc_iRight = (Isinf(bbox.maxvect(1)))?(INDEX)0:Clamp((INDEX)(bbox.maxvect(1)+0),(INDEX)0,ptrTerrain->tr_pixHeightMapWidth);
rc.rc_iBottom = (Isinf(bbox.maxvect(3)))?(INDEX)0:Clamp((INDEX)(bbox.maxvect(3)+0),(INDEX)0,ptrTerrain->tr_pixHeightMapHeight);
#else
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);
#endif
}
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_T *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_T **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_T *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),255);
pacolData->ub.g = ClampUp(pacolData->ub.g + ((colLight.ub.g*slDot)>>8),255);
pacolData->ub.b = ClampUp(pacolData->ub.b + ((colLight.ub.b*slDot)>>8),255);
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,127);
slag = ClampUp(slag,127);
slab = ClampUp(slab,127);
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),255);
pacolData->ub.g = ClampUp(pacolData->ub.g + slag + ((colLight.ub.g*slDot)>>ubColShift),255);
pacolData->ub.b = ClampUp(pacolData->ub.b + slab + ((colLight.ub.b*slDot)>>ubColShift),255);
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);
}