Serious-Engine/Sources/Engine/Terrain/TerrainMisc.cpp
Daniel Gibson 72edf1c720 Commented out unused functions and variables
many unused functions and variables are now commented out

You'll still get tons of warnings, which should mostly fall in one of
the following categories:
1. Unnecessary variables or values generated from .es scripts
2. Pointers assigned to from functions with side-effects: DO NOT REMOVE!
   Like CEntity *penNew = CreateEntity_t(...); - even if penNew isn't
   used, CreateEntity() must be called there!
2016-05-09 18:51:03 +02:00

1074 lines
37 KiB
C++

/* 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
#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 *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
#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)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 *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),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);
}