tge/engine/terrain/terrRender.cc
2017-04-17 06:17:10 -06:00

2619 lines
88 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "terrain/terrData.h"
#include "math/mMath.h"
#include "dgl/dgl.h"
#include "console/console.h"
#include "console/consoleTypes.h"
#include "dgl/gBitmap.h"
#include "terrain/terrRender.h"
#include "dgl/materialList.h"
#include "sceneGraph/sceneState.h"
#include "terrain/waterBlock.h"
#include "terrain/blender.h"
#include "core/frameAllocator.h"
#include "sceneGraph/sceneGraph.h"
#include "sceneGraph/sgUtil.h"
#include "platform/profiler.h"
inline F32 custom_dot(Point4F &a, Point3F &b)
{
return (a.x * b.x) + (a.y * b.y) + (a.z * b.z) + a.w;
}
struct LightTriangle {
ColorF color;
Point2F texco1;
Point3F point1;
Point2F texco2;
Point3F point2;
Point2F texco3;
Point3F point3;
LightTriangle* next;
U32 flags; // 0 if inactive
AllocatedTexture *chunkTexture;
};
static LightTriangle* sgCurrLightTris = NULL;
GBitmap* TerrainRender::mBlendBitmap = NULL;
S32 TerrainRender::mTextureMinSquareSize;
bool TerrainRender::mEnableTerrainDetails = true;
// CW - stuff with bump maps
// So, this is how it works...
// There are 3 fields:
// 1) bumpScale: This is the size of the bumps. It determines how much the bump texture is stretched
// 2) bumpOffset: How much the two textures are offset from each other. This should be VERY close to
// 0. An appropriate value is usually around 0.004 and 0.01. Play around with it and see what
// works best for your implementation.
// 3) zeroBumpScale: This is a bitshift value for how far the bumps are drawn. The LESS the value
// the FARTHER they will exist. If you go above 7-8, the bumps tend to go a little wacky.
// 8 should be the max. 6-7 are good values.
bool TerrainRender::mEnableTerrainEmbossBumps = true;
// This is only for win32
#ifdef TORQUE_OS_WIN32
bool TerrainRender::mRenderGL = false;
#endif
// CW - end bump map stuff
bool TerrainRender::mEnableTerrainDynLights = true;
U32 TerrainRender::mNumClipPlanes = 4;
MatrixF TerrainRender::mCameraToObject;
AllocatedTexture TerrainRender::mTextureFrameListHead;
AllocatedTexture TerrainRender::mTextureFrameListTail;
AllocatedTexture TerrainRender::mTextureFreeListHead;
AllocatedTexture TerrainRender::mTextureFreeListTail;
AllocatedTexture TerrainRender::mTextureFreeBigListHead;
AllocatedTexture TerrainRender::mTextureFreeBigListTail;
AllocatedTexture *TerrainRender::mTextureGrid[AllocatedTextureCount];
AllocatedTexture **TerrainRender::mTextureGridPtr[5];
AllocatedTexture *TerrainRender::mCurrentTexture = NULL;
U32 TerrainRender::mTextureSlopSize = 512;
static bool sgTextureFreeListPrimed = false;
static U32 sgFreeListPrimeCount = 32;
Vector<TextureHandle> TerrainRender::mTextureFreeList(__FILE__, __LINE__);
SceneState* TerrainRender::mSceneState;
TerrainBlock* TerrainRender::mCurrentBlock;
S32 TerrainRender::mSquareSize;
F32 TerrainRender::mScreenSize;
U32 TerrainRender::mFrameIndex;
Point2F TerrainRender::mBlockPos;
Point2I TerrainRender::mBlockOffset;
Point2I TerrainRender::mTerrainOffset;
PlaneF TerrainRender::mClipPlane[MaxClipPlanes];
Point3F TerrainRender::mCamPos;
TextureHandle* TerrainRender::mGrainyTexture = NULL;
U32 TerrainRender::mDynamicLightCount;
F32 TerrainRender::mPixelError;
TerrLightInfo TerrainRender::mTerrainLights[MaxTerrainLights];
bool TerrainRender::mRenderingCommander = false;
F32 TerrainRender::mScreenError;
F32 TerrainRender::mMinSquareSize;
F32 TerrainRender::mFarDistance;
S32 TerrainRender::mDynamicTextureCount;
S32 TerrainRender::mStaticTextureCount;
const U32 maxTerrPoints = 100;
ColorF TerrainRender::mFogColor;
bool TerrainRender::mRenderOutline;
U32 TerrainRender::mMaterialCount;
namespace {
Point4F sgTexGenS;
Point4F sgTexGenT;
Point4F sgLMGenS;
Point4F sgLMGenT;
Point4F bumpTexGenS;
Point4F bumpTexGenT;
Point2F bumpTextureOffset;
} // namespace {}
static S32 getPower(S32 x)
{
// Returns 2^n (the highest bit).
S32 i = 0;
if (x)
do
i++;
while (x >>= 1);
return i;
}
void TerrainRender::init()
{
S32 i;
mTextureMinSquareSize = 0;
mFrameIndex = 0;
mScreenError = 4;
mScreenSize = 45;
mMinSquareSize = 4;
mFarDistance = 500;
mRenderOutline = false;
mTextureFrameListHead.next = &mTextureFrameListTail;
mTextureFrameListHead.previous = NULL;
mTextureFrameListTail.next = NULL;
mTextureFrameListTail.previous = &mTextureFrameListHead;
mTextureFreeListHead.next = &mTextureFreeListTail;
mTextureFreeListHead.previous = NULL;
mTextureFreeListTail.next = NULL;
mTextureFreeListTail.previous = &mTextureFreeListHead;
mTextureFreeBigListHead.next = &mTextureFreeBigListTail;
mTextureFreeBigListHead.previous = NULL;
mTextureFreeBigListTail.next = NULL;
mTextureFreeBigListTail.previous = &mTextureFreeBigListHead;
for(i = 0; i < AllocatedTextureCount; i++)
mTextureGrid[i] = 0;
mTextureGridPtr[0] = mTextureGrid;
mTextureGridPtr[1] = mTextureGrid + 4096;
mTextureGridPtr[2] = mTextureGrid + 4096 + 1024;
mTextureGridPtr[3] = mTextureGrid + 4096 + 1024 + 256;
mTextureGridPtr[4] = mTextureGrid + 4096 + 1024 + 256 + 64;
/* for(i = 0; i < 256; i++)
{
mSquareSeqAdd[i] = 0;
for(S32 val = 0; val < 9; val++)
{
if(i & (1 << val))
mSquareSeqAdd[i] += (1 << val) * (1 << val);
}
} */
mBlendBitmap = new GBitmap(TerrainTextureSize, TerrainTextureSize, true, GBitmap::RGB5551);
Con::addVariable("T2::dynamicTextureCount", TypeS32, &mDynamicTextureCount);
Con::addVariable("T2::staticTextureCount", TypeS32, &mStaticTextureCount);
Con::addVariable("screenSize", TypeF32, &mScreenSize);
Con::addVariable("farDistance", TypeF32, &mFarDistance);
Con::addVariable("pref::Terrain::texDetail", TypeS32, &mTextureMinSquareSize);
Con::addVariable("pref::Terrain::enableDetails", TypeBool, &mEnableTerrainDetails);
// CW - stuff with bump maps
Con::addVariable("pref::Terrain::enableEmbossBumps", TypeBool, &mEnableTerrainEmbossBumps);
// CW - end bump map stuff
Con::addVariable("pref::Terrain::dynamicLights", TypeBool, &mEnableTerrainDynLights);
Con::addVariable("pref::Terrain::screenError", TypeF32, &mScreenError);
Con::addVariable("pref::Terrain::textureCacheSize", TypeS32, &mTextureSlopSize);
}
void TerrainRender::shutdown()
{
delete mBlendBitmap;
mBlendBitmap = NULL;
flushCache();
}
void TerrainRender::buildClippingPlanes(bool flipClipPlanes)
{
F64 frustumParam[6];
dglGetFrustum(&frustumParam[0], &frustumParam[1],
&frustumParam[2], &frustumParam[3],
&frustumParam[4], &frustumParam[5]);
Point3F osCamPoint(0, 0, 0);
mCameraToObject.mulP(osCamPoint);
sgComputeOSFrustumPlanes(frustumParam,
mCameraToObject,
osCamPoint,
mClipPlane[4],
mClipPlane[0],
mClipPlane[1],
mClipPlane[2],
mClipPlane[3]);
// no need
mNumClipPlanes = 4;
// near plane is needed as well...
//PlaneF p(0, 1, 0, -frustumParam[4]);
//mTransformPlane(mCameraToObject, Point3F(1,1,1), p, &mClipPlane[0]);
if (flipClipPlanes) {
mClipPlane[0].neg();
mClipPlane[1].neg();
mClipPlane[2].neg();
mClipPlane[3].neg();
mClipPlane[4].neg();
mClipPlane[5].neg();
}
}
S32 TerrainRender::TestSquareVisibility(Point3F &min, Point3F &max, S32 mask, F32 expand)
{
S32 retMask = 0;
Point3F minPoint, maxPoint;
for(S32 i = 0; i < mNumClipPlanes; i++)
{
if(mask & (1 << i))
{
if(mClipPlane[i].x > 0.0f)
{
maxPoint.x = max.x;
minPoint.x = min.x;
}
else
{
maxPoint.x = min.x;
minPoint.x = max.x;
}
if(mClipPlane[i].y > 0.0f)
{
maxPoint.y = max.y;
minPoint.y = min.y;
}
else
{
maxPoint.y = min.y;
minPoint.y = max.y;
}
if(mClipPlane[i].z > 0.0f)
{
maxPoint.z = max.z;
minPoint.z = min.z;
}
else
{
maxPoint.z = min.z;
minPoint.z = max.z;
}
F32 maxDot = mDot(maxPoint, mClipPlane[i]);
F32 minDot = mDot(minPoint, mClipPlane[i]);
F32 planeD = mClipPlane[i].d;
if(maxDot <= -(planeD + expand))
return -1;
if(minDot <= -planeD)
retMask |= (1 << i);
}
}
return retMask;
}
ChunkCornerPoint *TerrainRender::allocInitialPoint(Point3F pos)
{
ChunkCornerPoint *ret = (ChunkCornerPoint *) FrameAllocator::alloc(sizeof(ChunkCornerPoint));
ret->x = pos.x;
ret->y = pos.y;
ret->z = pos.z;
ret->distance = (*ret - mCamPos).len();
gClientSceneGraph->getFogCoordPair(ret->distance, ret->z, ret->fogRed, ret->fogGreen);
ret->xfIndex = 0;
return ret;
}
ChunkCornerPoint *TerrainRender::allocPoint(Point2I pos)
{
ChunkCornerPoint *ret = (ChunkCornerPoint *) FrameAllocator::alloc(sizeof(ChunkCornerPoint));
ret->x = pos.x * mSquareSize + mBlockPos.x;
ret->y = pos.y * mSquareSize + mBlockPos.y;
ret->z = fixedToFloat(mCurrentBlock->getHeight(pos.x, pos.y));
ret->distance = (*ret - mCamPos).len();
gClientSceneGraph->getFogCoordPair(ret->distance, ret->z, ret->fogRed, ret->fogGreen);
ret->xfIndex = 0;
return ret;
}
void TerrainRender::allocRenderEdges(U32 edgeCount, EdgeParent **dest, bool renderEdge)
{
if(renderEdge)
{
for(U32 i = 0; i < edgeCount; i++)
{
ChunkEdge *edge = (ChunkEdge *) FrameAllocator::alloc(sizeof(ChunkEdge));
edge->c1 = NULL;
edge->c2 = NULL;
edge->xfIndex = 0;
dest[i] = edge;
}
}
else
{
for(U32 i = 0; i < edgeCount; i++)
{
ChunkScanEdge *edge = (ChunkScanEdge *) FrameAllocator::alloc(sizeof(ChunkScanEdge));
edge->mp = NULL;
dest[i] = edge;
}
}
}
void TerrainRender::subdivideChunkEdge(ChunkScanEdge *e, Point2I pos, bool chunkEdge)
{
if(!e->mp)
{
allocRenderEdges(2, &e->e1, chunkEdge);
e->mp = allocPoint(pos);
e->e1->p1 = e->p1;
e->e1->p2 = e->mp;
e->e2->p1 = e->mp;
e->e2->p2 = e->p2;
}
}
F32 TerrainRender::getSquareDistance(const Point3F& minPoint, const Point3F& maxPoint, F32* zDiff)
{
Point3F vec( 0.0f, 0.0f, 0.0f );
if(mCamPos.z < minPoint.z)
vec.z = minPoint.z - mCamPos.z;
else if(mCamPos.z > maxPoint.z)
vec.z = maxPoint.z - mCamPos.z;
if(mCamPos.x < minPoint.x)
vec.x = minPoint.x - mCamPos.x;
else if(mCamPos.x > maxPoint.x)
vec.x = mCamPos.x - maxPoint.x;
if(mCamPos.y < minPoint.y)
vec.y = minPoint.y - mCamPos.y;
else if(mCamPos.y > maxPoint.y)
vec.y = mCamPos.y - maxPoint.y;
*zDiff = vec.z;
return vec.len();
}
void TerrainRender::emitTerrChunk(SquareStackNode *n, F32 squareDistance, U32 lightMask, bool farClip, bool drawDetails, bool drawBumps)
{
//if(n->pos.x || n->pos.y)
// return;
GridChunk *gc = mCurrentBlock->findChunk(n->pos);
EmitChunk *chunk = (EmitChunk *) FrameAllocator::alloc(sizeof(EmitChunk));
chunk->x = n->pos.x + mBlockOffset.x + mTerrainOffset.x;
chunk->y = n->pos.y + mBlockOffset.y + mTerrainOffset.y;
chunk->gridX = n->pos.x;
chunk->gridY = n->pos.y;
chunk->lightMask = lightMask;
chunk->next = mCurrentTexture->list;
chunk->chunkTexture = mCurrentTexture;
mCurrentTexture->list = chunk;
if(mRenderingCommander)
return;
chunk->edge[0] = (ChunkEdge *) n->top;
chunk->edge[1] = (ChunkEdge *) n->right;
chunk->edge[2] = (ChunkEdge *) n->bottom;
chunk->edge[3] = (ChunkEdge *) n->left;
chunk->edge[0]->c2 = chunk;
chunk->edge[1]->c1 = chunk;
chunk->edge[2]->c1 = chunk;
chunk->edge[3]->c2 = chunk;
// holes only in the primary terrain block
if (gc->emptyFlags && mBlockPos.x == 0.0f && mBlockPos.y == 0.0f)
chunk->emptyFlags = gc->emptyFlags;
else
chunk->emptyFlags = 0;
S32 subDivLevel;
F32 growFactor = 0.0f;
F32 minSubdivideDistance = 1000000.0f;
chunk->clip = farClip;
chunk->renderDetails = drawDetails;
chunk->renderBumps = drawBumps;
if(squareDistance < 1.0f)
{
subDivLevel = -1;
}
else
{
const F32 invPixelError = 1.0f / mPixelError;
for(subDivLevel = 2; subDivLevel >= 0; subDivLevel--)
{
F32 subdivideDistance = fixedToFloat(gc->heightDeviance[subDivLevel]) * invPixelError;
if(subdivideDistance > minSubdivideDistance)
subdivideDistance = minSubdivideDistance;
if(squareDistance >= subdivideDistance)
break;
F32 clampDistance = subdivideDistance * 0.75f;
if(squareDistance > clampDistance)
{
growFactor = (squareDistance - clampDistance) / (0.25f * subdivideDistance);
subDivLevel--;
break;
}
minSubdivideDistance = clampDistance;
}
}
chunk->subDivLevel = subDivLevel;
chunk->growFactor = growFactor;
}
void TerrainRender::processCurrentBlock(SceneState*, EdgeParent *topEdge, EdgeParent *rightEdge, EdgeParent *bottomEdge, EdgeParent *leftEdge)
{
SquareStackNode stack[TerrainBlock::BlockShift*4];
Point3F minPoint, maxPoint;
stack[0].level = TerrainBlock::BlockShift;
stack[0].clipFlags = ((1 << mNumClipPlanes) - 1) | FarSphereMask; // test all the planes
stack[0].pos.set(0,0);
stack[0].top = topEdge;
stack[0].right = rightEdge;
stack[0].bottom = bottomEdge;
stack[0].left = leftEdge;
stack[0].lightMask = (1 << mDynamicLightCount) - 1; // test all the lights
stack[0].texAllocated = false;
Vector<SceneState::FogBand> *posFog = mSceneState->getPosFogBands();
Vector<SceneState::FogBand> *negFog = mSceneState->getNegFogBands();
bool clipAbove = posFog->size() > 0 && (*posFog)[0].isFog == false;
bool clipBelow = negFog->size() > 0 && (*negFog)[0].isFog == false;
bool clipOn = posFog->size() > 0 && (*posFog)[0].isFog == true;
if(posFog->size() != 0 || negFog->size() != 0)
stack[0].clipFlags |= FogPlaneBoxMask;
S32 curStackSize = 1;
F32 worldToScreenScale = dglProjectRadius(1,1);
F32 zeroDetailDistance = (mSquareSize * worldToScreenScale) / (1 << 6) - (mSquareSize >> 1);
F32 zeroBumpDistance = (mSquareSize * worldToScreenScale) / (1 << mCurrentBlock->mZeroBumpScale) - (mSquareSize >> 1);
while(curStackSize)
{
SquareStackNode *n = stack + curStackSize - 1;
// see if it's visible
GridSquare *sq = mCurrentBlock->findSquare(n->level, n->pos);
minPoint.set(mSquareSize * n->pos.x + mBlockPos.x,
mSquareSize * n->pos.y + mBlockPos.y,
fixedToFloat(sq->minHeight));
maxPoint.set(minPoint.x + (mSquareSize << n->level),
minPoint.y + (mSquareSize << n->level),
fixedToFloat(sq->maxHeight));
// holes only in the primary terrain block
if ((sq->flags & GridSquare::Empty) && mBlockPos.x == 0.0f && mBlockPos.y == 0.0f)
{
curStackSize--;
continue;
}
F32 zDiff;
F32 squareDistance = getSquareDistance(minPoint, maxPoint, &zDiff);
S32 nextClipFlags = 0;
if(n->clipFlags)
{
if(n->clipFlags & FogPlaneBoxMask)
{
F32 camZ = mCamPos.z;
bool boxBelow = camZ > maxPoint.z;
bool boxAbove = camZ < minPoint.z;
bool boxOn = !(boxAbove || boxBelow);
if( clipOn ||
(clipAbove && boxAbove && (maxPoint.z - camZ > (*posFog)[0].cap)) ||
(clipBelow && boxBelow && (camZ - minPoint.z > (*negFog)[0].cap)) ||
(boxOn && (( clipAbove && maxPoint.z - camZ > (*posFog)[0].cap ) ||
( clipBelow && camZ - minPoint.z > (*negFog)[0].cap ))))
{
// Using the fxSunLight can cause the "sky" to extend down below the camera.
// To avoid the sun showing through, the terrain must always be rendered.
// If the fxSunLight is not being used, the following optimization can be
// uncommented.
#if 0
if(boxBelow && !mSceneState->isBoxFogVisible(squareDistance, maxPoint.z, minPoint.z))
{
// Totally fogged terrain tiles can be thrown out as long as they are
// below the camera. If they are ubove, the sky will show through the
// fog.
curStackSize--;
continue;
}
#endif
nextClipFlags |= FogPlaneBoxMask;
}
}
if(n->clipFlags & FarSphereMask)
{
if(squareDistance >= mFarDistance)
{
curStackSize--;
continue;
}
S32 squareSz = mSquareSize << n->level;
if(squareDistance + maxPoint.z - minPoint.z + squareSz + squareSz > mFarDistance)
nextClipFlags |= FarSphereMask;
}
// zDelta for screen error height deviance.
F32 zDelta = squareDistance * mPixelError;
minPoint.z -= zDelta;
maxPoint.z += zDelta;
nextClipFlags |= TestSquareVisibility(minPoint, maxPoint, n->clipFlags, mSquareSize);
if(nextClipFlags == -1)
{
//if(!n->texAllocated)
// textureRecurse(n);
// trivially rejected, so pop it off the stack
curStackSize--;
continue;
}
}
if(!n->texAllocated)
{
S32 squareSz = mSquareSize << n->level;
// first check the level - if its 3 or less, we have to just make a bitmap:
// level 3 == 8x8 square - 8x8 * 16x16 == 128x128
if(n->level > 6)
goto notexalloc;
S32 mipLevel = TerrainTextureMipLevel;
if(!mRenderingCommander)
{
if(n->level > mTextureMinSquareSize + 2)
{
// get the mip level of the square and see if we're in range
if(squareDistance > 0.001f)
{
S32 size = S32(dglProjectRadius(squareDistance + (squareSz >> 1), squareSz));
mipLevel = getPower((S32)(size * 0.75));
if(mipLevel > TerrainTextureMipLevel) // too big for this square
goto notexalloc;
}
else
goto notexalloc;
}
}
allocTerrTexture(n->pos, n->level, mipLevel, true, squareDistance);
n->texAllocated = true;
if(mRenderingCommander) // level == 6
{
emitTerrChunk(n, 0.0f, 0, false, false, false);
curStackSize--;
continue;
}
}
notexalloc:
if(n->lightMask)
n->lightMask = TestSquareLights(sq, n->level, n->pos, n->lightMask);
if(n->level == 2)
{
AssertFatal(n->texAllocated, "Invalid texture index.");
bool drawDetails = false;
if (mEnableTerrainDetails && squareDistance < zeroDetailDistance)
drawDetails = true;
//END
bool drawBumps = false;
// CW - stuff with bump maps
if (mEnableTerrainEmbossBumps && squareDistance < zeroBumpDistance)
drawBumps = true;
// CW - end bump map stuff
emitTerrChunk(n, squareDistance, n->lightMask, nextClipFlags & FarSphereMask, drawDetails, drawBumps);
curStackSize--;
continue;
}
bool allocChunkEdges = (n->level == 3);
Point2I pos = n->pos;
ChunkScanEdge *top = (ChunkScanEdge *) n->top;
ChunkScanEdge *right = (ChunkScanEdge *) n->right;
ChunkScanEdge *bottom = (ChunkScanEdge *) n->bottom;
ChunkScanEdge *left = (ChunkScanEdge *) n->left;
// subdivide this square and throw it on the stack
S32 squareOneSize = 1 << n->level;
S32 squareHalfSize = squareOneSize >> 1;
ChunkCornerPoint *midPoint = allocPoint(Point2I(pos.x + squareHalfSize, pos.y + squareHalfSize));
S32 nextLevel = n->level - 1;
subdivideChunkEdge(top, Point2I(pos.x + squareHalfSize, pos.y + squareOneSize), allocChunkEdges);
subdivideChunkEdge(right, Point2I(pos.x + squareOneSize, pos.y + squareHalfSize), allocChunkEdges);
subdivideChunkEdge(bottom, Point2I(pos.x + squareHalfSize, pos.y), allocChunkEdges);
subdivideChunkEdge(left, Point2I(pos.x, pos.y + squareHalfSize), allocChunkEdges);
// cross edges go top, right, bottom, left
EdgeParent *crossEdges[4];
allocRenderEdges(4, crossEdges, allocChunkEdges);
crossEdges[0]->p1 = top->mp;
crossEdges[0]->p2 = midPoint;
crossEdges[1]->p1 = midPoint;
crossEdges[1]->p2 = right->mp;
crossEdges[2]->p1 = midPoint;
crossEdges[2]->p2 = bottom->mp;
crossEdges[3]->p1 = left->mp;
crossEdges[3]->p2 = midPoint;
n->level = nextLevel;
n->clipFlags = nextClipFlags;
for(S32 i = 1; i < 4; i++)
{
n[i].level = nextLevel;
n[i].clipFlags = nextClipFlags;
n[i].lightMask = n->lightMask;
n[i].texAllocated = n->texAllocated;
}
// push in reverse order of processing.
n[3].pos = pos;
n[3].top = crossEdges[3];
n[3].right = crossEdges[2];
n[3].bottom = bottom->e1;
n[3].left = left->e2;
n[2].pos.set(pos.x + squareHalfSize, pos.y);
n[2].top = crossEdges[1];
n[2].right = right->e2;
n[2].bottom = bottom->e2;
n[2].left = crossEdges[2];
n[1].pos.set(pos.x, pos.y + squareHalfSize);
n[1].top = top->e1;
n[1].right = crossEdges[0];
n[1].bottom = crossEdges[3];
n[1].left = left->e1;
n[0].pos.set(pos.x + squareHalfSize, pos.y + squareHalfSize);
n[0].top = top->e2;
n[0].right = right->e1;
n[0].bottom = crossEdges[1];
n[0].left = crossEdges[0];
curStackSize += 3;
}
}
//---------------------------------------------------------------
//---------------------------------------------------------------
// Root block render function
//---------------------------------------------------------------
//---------------------------------------------------------------
void TerrainRender::fixEdge(ChunkEdge *edge, S32 x, S32 y, S32 dx, S32 dy)
{
S32 minLevel, maxLevel;
F32 growFactor;
if(edge->c1)
{
minLevel = edge->c1->subDivLevel;
maxLevel = edge->c1->subDivLevel;
growFactor = edge->c1->growFactor;
if(edge->c2)
{
if(edge->c2->subDivLevel < minLevel)
minLevel = edge->c2->subDivLevel;
else if(edge->c2->subDivLevel > maxLevel)
{
maxLevel = edge->c2->subDivLevel;
growFactor = edge->c2->growFactor;
}
else if(edge->c2->growFactor > growFactor)
growFactor = edge->c2->growFactor;
}
}
else
{
minLevel = maxLevel = edge->c2->subDivLevel;
growFactor = edge->c2->growFactor;
}
if(minLevel == 2)
{
edge->pointCount = 0;
return;
}
// get the mid heights
EdgePoint *pmid = &edge->pt[1];
ChunkCornerPoint *p1 = edge->p1;
ChunkCornerPoint *p2 = edge->p2;
pmid->x = (p1->x + p2->x) * 0.5f;
pmid->y = (p1->y + p2->y) * 0.5f;
if(maxLevel == 2)
{
// pure interp
pmid->z = (p1->z + p2->z) * 0.5f;
pmid->distance = (*pmid - mCamPos).len();
pmid->fogRed = (p1->fogRed + p2->fogRed) * 0.5f;
pmid->fogGreen = (p1->fogGreen + p2->fogGreen) * 0.5f;
if(minLevel >= 0)
{
edge->pointCount = 1;
return;
}
}
else
{
pmid->z = fixedToFloat(mCurrentBlock->getHeight(x + dx + dx, y + dy + dy));
if(maxLevel == 1) // interp the z and haze
pmid->z = pmid->z + growFactor * (((p1->z + p2->z) * 0.5f) - pmid->z);
pmid->distance = (*pmid - mCamPos).len();
gClientSceneGraph->getFogCoordPair(pmid->distance, pmid->z, pmid->fogRed, pmid->fogGreen);
if(maxLevel == 1) // interp the z and haze
{
pmid->fogRed = pmid->fogRed + growFactor * (((p1->fogRed + p2->fogRed) * 0.5f) - pmid->fogRed);
pmid->fogGreen = pmid->fogGreen + growFactor * (((p1->fogGreen + p2->fogGreen) * 0.5f) - pmid->fogGreen);
}
if(minLevel >= 0)
{
edge->pointCount = 1;
return;
}
}
// last case - minLevel == -1, midPoint calc'd
edge->pointCount = 3;
EdgePoint *pm1 = &edge->pt[0];
EdgePoint *pm2 = &edge->pt[2];
pm1->x = (p1->x + pmid->x) * 0.5f;
pm1->y = (p1->y + pmid->y) * 0.5f;
pm2->x = (p2->x + pmid->x) * 0.5f;
pm2->y = (p2->y + pmid->y) * 0.5f;
if(maxLevel != -1)
{
// clamp it:
pm1->z = (p1->z + pmid->z) * 0.5f;
pm1->distance = (*pm1 - mCamPos).len();
pm1->fogRed = (p1->fogRed + pmid->fogRed) * 0.5f;
pm1->fogGreen = (p1->fogGreen + pmid->fogGreen) * 0.5f;
pm2->z = (p2->z + pmid->z) * 0.5f;
pm2->distance = (*pm2 - mCamPos).len();
pm2->fogRed = (p2->fogRed + pmid->fogRed) * 0.5f;
pm2->fogGreen = (p2->fogGreen + pmid->fogGreen) * 0.5f;
return;
}
// compute the real deals:
pm1->z = fixedToFloat(mCurrentBlock->getHeight(x + dx, y + dy));
pm2->z = fixedToFloat(mCurrentBlock->getHeight(x + dx + dx + dx, y + dy + dy + dy));
if(growFactor)
{
pm1->z = pm1->z + growFactor * (((p1->z + pmid->z) * 0.5f) - pm1->z);
pm2->z = pm2->z + growFactor * (((p2->z + pmid->z) * 0.5f) - pm2->z);
}
pm1->distance = (*pm1 - mCamPos).len();
gClientSceneGraph->getFogCoordPair(pm1->distance, pm1->z, pm1->fogRed, pm1->fogGreen);
pm2->distance = (*pm2 - mCamPos).len();
gClientSceneGraph->getFogCoordPair(pm2->distance, pm2->z, pm2->fogRed, pm2->fogGreen);
if(growFactor)
{
pm1->fogRed = pm1->fogRed + growFactor * (((p1->fogRed + pmid->fogRed) * 0.5f) - pm1->fogRed);
pm1->fogGreen = pm1->fogGreen + growFactor * (((p1->fogGreen + pmid->fogGreen) * 0.5f) - pm1->fogGreen);
pm2->fogRed = pm2->fogRed + growFactor * (((p2->fogRed + pmid->fogRed) * 0.5f) - pm2->fogRed);
pm2->fogGreen = pm2->fogGreen + growFactor * (((p2->fogGreen + pmid->fogGreen) * 0.5f) - pm2->fogGreen);
}
}
EdgePoint *mXFVertices = NULL;
U16 *mXFIndexBuffer;
U16 *mXFIndexPtr;
U32 mXFIndexCount;
U32 mXFPointCount;
U32 mXFIndex;
inline U32 clipPoint(EdgePoint *p1, EdgePoint *p2, F32 dist)
{
F32 frac = (dist - p1->distance) / (p2->distance - p1->distance);
F32 onefrac = 1.0f - frac;
U32 clipIndex = mXFPointCount++;
EdgePoint *ip = mXFVertices + clipIndex;
ip->x = (p2->x * frac) + (p1->x * onefrac);
ip->y = (p2->y * frac) + (p1->y * onefrac);
ip->z = (p2->z * frac) + (p1->z * onefrac);
ip->fogRed = (p2->fogRed * frac) + (p1->fogRed * onefrac);
ip->fogGreen = (p2->fogGreen * frac) + (p1->fogGreen * onefrac);
ip->distance = dist;
return clipIndex;
}
void TerrainRender::clip(U32 indexStart)
{
static U16 dest[16 * 3 * 2];
U32 vertexCount = mXFIndexBuffer[indexStart + 1];
U32 centerPoint = mXFIndexBuffer[indexStart + 2];
U16 *source = mXFIndexBuffer + indexStart + 3;
EdgePoint *center = mXFVertices + centerPoint;
U32 destIndex = 0;
if(mXFVertices[centerPoint].distance > mFarDistance)
{
// loop through all the tris and clip em:
// there are vertexCount - 1 triangles.
EdgePoint *p1 = mXFVertices + source[0];
bool p1out = p1->distance >= mFarDistance;
U32 p1idx = source[0];
U32 clip1;
if(!p1out)
clip1 = clipPoint(p1, center, mFarDistance);
for(U32 i = 0; i < vertexCount - 2; i++)
{
U32 p2idx = source[i+1];
EdgePoint *p2 = mXFVertices + p2idx;
bool p2out = p2->distance >= mFarDistance;
if(!p2out)
{
U32 clip2 = clipPoint(p2, center, mFarDistance);
if(p1out)
{
// p2 is the only "in" point:
dest[destIndex++] = p2idx;
dest[destIndex++] = clip2;
dest[destIndex++] = clipPoint(p1, p2, mFarDistance);
}
else
{
dest[destIndex++] = clip2;
dest[destIndex++] = clip1;
dest[destIndex++] = p1idx;
dest[destIndex++] = p2idx;
dest[destIndex++] = clip2;
dest[destIndex++] = p1idx;
}
clip1 = clip2;
}
else if(!p1out)
{
dest[destIndex++] = p1idx;
dest[destIndex++] = clipPoint(p1, p2, mFarDistance);
dest[destIndex++] = clip1;
}
p1idx = p2idx;
p1out = p2out;
p1 = p2;
}
if(destIndex)
{
// copy this in..
mXFIndexBuffer[indexStart] = GL_TRIANGLES;
mXFIndexBuffer[indexStart + 1] = destIndex;
for(U32 i = 0; i < destIndex; i++)
mXFIndexBuffer[indexStart + i + 2] = dest[i];
mXFIndexCount = destIndex + indexStart + 2;
}
else
mXFIndexCount = indexStart;
}
else
{
EdgePoint *prev = mXFVertices + source[0];
bool prevIn = prev->distance <= mFarDistance;
U32 i;
for(i = 1; i < vertexCount - 1; i++)
{
EdgePoint *pt = mXFVertices + source[i];
bool curIn = pt->distance <= mFarDistance;
if((curIn && !prevIn) || (!curIn && prevIn))
dest[destIndex++] = clipPoint(pt, prev, mFarDistance);
if(curIn)
dest[destIndex++] = source[i];
else
dest[destIndex++] = clipPoint(pt, center, mFarDistance);
prev = pt;
prevIn = curIn;
}
for(i = 0; i < destIndex; i++)
mXFIndexBuffer[indexStart + i + 3] = dest[i];
mXFIndexBuffer[indexStart + destIndex + 3] = dest[0];
mXFIndexBuffer[indexStart + 1] = destIndex + 2;
mXFIndexCount = indexStart + destIndex + 4;
}
}
inline U32 TerrainRender::constructPoint(S32 x, S32 y)
{
U32 ret = mXFPointCount++;
EdgePoint *pt = mXFVertices + ret;
pt->x = x * mSquareSize;
pt->y = y * mSquareSize;
pt->z = fixedToFloat(mCurrentBlock->getHeight(x, y));
pt->distance = (*pt - mCamPos).len();
gClientSceneGraph->getFogCoordPair(pt->distance, pt->z, pt->fogRed, pt->fogGreen);
return ret;
}
inline U32 TerrainRender::interpPoint(U32 p1, U32 p2, S32 x, S32 y, F32 growFactor)
{
U32 ret = mXFPointCount++;
EdgePoint *pt = mXFVertices + ret;
pt->x = x * mSquareSize;
pt->y = y * mSquareSize;
pt->z = fixedToFloat(mCurrentBlock->getHeight(x, y));
pt->z = pt->z + growFactor * (((mXFVertices[p1].z + mXFVertices[p2].z) * 0.5f) - pt->z);
pt->distance = (*pt - mCamPos).len();
gClientSceneGraph->getFogCoordPair(pt->distance, pt->z, pt->fogRed, pt->fogGreen);
return ret;
}
inline void TerrainRender::addEdge(ChunkEdge *edge)
{
if(edge->pointCount == 1)
{
edge->pointIndex = mXFPointCount;
mXFVertices[mXFPointCount++] = * ((EdgePoint *) &edge->pt[1]);
}
else if(edge->pointCount == 3)
{
edge->pointIndex = mXFPointCount;
mXFVertices[mXFPointCount++] = *((EdgePoint *) &edge->pt[0]);
mXFVertices[mXFPointCount++] = *((EdgePoint *) &edge->pt[1]);
mXFVertices[mXFPointCount++] = *((EdgePoint *) &edge->pt[2]);
}
edge->xfIndex = mXFIndex;
}
inline void emitTri(U32 i1, U32 i2, U32 i3)
{
mXFIndexBuffer[mXFIndexCount] = i1;
mXFIndexBuffer[mXFIndexCount + 1] = i2;
mXFIndexBuffer[mXFIndexCount + 2] = i3;
mXFIndexCount += 3;
}
inline U32 emitCornerPoint(ChunkCornerPoint *p)
{
if(p->xfIndex != mXFIndex)
{
p->pointIndex = mXFPointCount;
p->xfIndex = mXFIndex;
mXFVertices[mXFPointCount++] = *((EdgePoint *) p);
}
return p->pointIndex;
}
void buildLightTri(LightTriangle* pTri, TerrLightInfo* pInfo)
{
// Get the plane normal
Point3F normal;
mCross((pTri->point1 - pTri->point2), (pTri->point3 - pTri->point2), &normal);
if (normal.lenSquared() < 1e-7f)
{
pTri->flags = 0;
return;
}
PlaneF plane(pTri->point2, normal); // Assumes that mPlane.h normalizes incoming point
Point3F centerPoint;
F32 d = plane.distToPlane(pInfo->pos);
centerPoint = pInfo->pos - plane * d;
d = mFabs(d);
if (d >= pInfo->radius) {
pTri->flags = 0;
return;
}
F32 mr = mSqrt(pInfo->radiusSquared - d*d);
Point3F normalS;
Point3F normalT;
mCross(plane, Point3F(0.0f, 1.0f, 0.0f), &normalS);
mCross(plane, normalS, &normalT);
PlaneF splane(centerPoint, normalS); // Assumes that mPlane.h normalizes incoming point
PlaneF tplane(centerPoint, normalT); // Assumes that mPlane.h normalizes incoming point
pTri->color.red = pInfo->r;
pTri->color.green = pInfo->g;
pTri->color.blue = pInfo->b;
pTri->color.alpha = (pInfo->radius - d) / pInfo->radius;
pTri->texco1.set(((splane.distToPlane(pTri->point1) / mr) + 1.0f) * 0.5f,
((tplane.distToPlane(pTri->point1) / mr) + 1.0f) * 0.5f);
pTri->texco2.set(((splane.distToPlane(pTri->point2) / mr) + 1.0f) * 0.5f,
((tplane.distToPlane(pTri->point2) / mr) + 1.0f) * 0.5f);
pTri->texco3.set(((splane.distToPlane(pTri->point3) / mr) + 1.0f) * 0.5f,
((tplane.distToPlane(pTri->point3) / mr) + 1.0f) * 0.5f);
pTri->flags = 1;
}
void TerrainRender::renderChunkCommander(EmitChunk *chunk)
{
U32 ll = mXFPointCount;
for(U32 y = 0; y <= 64; y += 4)
for(U32 x = (y & 4) ? 4 : 0; x <= 64; x += 8)
constructPoint(chunk->x + x,chunk->y + y);
for(U32 y = 0; y < 8; y++)
{
for(U32 x = 0; x < 8; x++)
{
U16 *ib = mXFIndexBuffer + mXFIndexCount;
ib[0] = GL_TRIANGLE_FAN;
ib[1] = 6;
ib[2] = ll + 9;
ib[3] = ll;
ib[4] = ll + 17;
ib[5] = ll + 18;
ib[6] = ll + 1;
ib[7] = ll;
mXFIndexCount += 8;
ll++;
}
ll += 9;
}
}
void TerrainRender::renderChunkOutline(EmitChunk *chunk)
{
U32 startXFIndex = mXFIndexCount;
ChunkEdge *e0 = chunk->edge[0];
ChunkEdge *e1 = chunk->edge[1];
ChunkEdge *e2 = chunk->edge[2];
ChunkEdge *e3 = chunk->edge[3];
if(e0->xfIndex != mXFIndex)
{
if(!e0->xfIndex)
fixEdge(e0, chunk->x, chunk->y + 4, 1, 0);
addEdge(e0);
}
if(e1->xfIndex != mXFIndex)
{
if(!e1->xfIndex)
fixEdge(e1, chunk->x + 4, chunk->y + 4, 0, -1);
addEdge(e1);
}
if(e2->xfIndex != mXFIndex)
{
if(!e2->xfIndex)
fixEdge(e2, chunk->x, chunk->y, 1, 0);
addEdge(e2);
}
if(e3->xfIndex != mXFIndex)
{
if(!e3->xfIndex)
fixEdge(e3, chunk->x, chunk->y + 4, 0, -1);
addEdge(e3);
}
U32 p0 = emitCornerPoint(e0->p1);
U32 p1 = emitCornerPoint(e0->p2);
U32 p2 = emitCornerPoint(e2->p2);
U32 p3 = emitCornerPoint(e2->p1);
// build the interior points:
U32 ip0 = constructPoint(chunk->x + 2, chunk->y + 2);
F32 growFactor = chunk->growFactor;
if(chunk->subDivLevel >= 1)
{
// just emit the fan for the whole square:
S32 i;
mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLE_FAN;
U32 indexStart = mXFIndexCount++;
mXFIndexBuffer[mXFIndexCount++] = ip0;
mXFIndexBuffer[mXFIndexCount++] = p0;
for(i = 0; i < e0->pointCount; i++)
mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex + i;
mXFIndexBuffer[mXFIndexCount++] = p1;
for(i = 0; i < e1->pointCount; i++)
mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex + i;
mXFIndexBuffer[mXFIndexCount++] = p2;
for(i = e2->pointCount - 1; i >= 0; i--)
mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex + i;
mXFIndexBuffer[mXFIndexCount++] = p3;
for(i = e3->pointCount - 1; i >= 0; i--)
mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex + i;
mXFIndexBuffer[mXFIndexCount++] = p0;
mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1;
if(chunk->clip)
clip(indexStart - 1);
}
else
{
if(chunk->subDivLevel == 0)
{
U32 ip1 = interpPoint(p0, ip0, chunk->x + 1, chunk->y + 3, growFactor);
U32 ip2 = interpPoint(p1, ip0, chunk->x + 3, chunk->y + 3, growFactor);
U32 ip3 = interpPoint(p2, ip0, chunk->x + 3, chunk->y + 1, growFactor);
U32 ip4 = interpPoint(p3, ip0, chunk->x + 1, chunk->y + 1, growFactor);
// emit the 4 fans:
U32 indexStart;
if((chunk->emptyFlags & CornerEmpty_0_1) != CornerEmpty_0_1)
{
mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLE_FAN;
indexStart = mXFIndexCount++;
mXFIndexBuffer[mXFIndexCount++] = ip1;
mXFIndexBuffer[mXFIndexCount++] = p0;
if(e0->pointCount == 1)
mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex;
else // has to be 3:
{
mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex;
mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex + 1;
}
mXFIndexBuffer[mXFIndexCount++] = ip0;
if(e3->pointCount == 1)
mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex;
else
{
mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex + 1;
mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex;
}
mXFIndexBuffer[mXFIndexCount++] = p0;
mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1;
if(chunk->clip)
clip(indexStart - 1);
}
if((chunk->emptyFlags & CornerEmpty_1_1) != CornerEmpty_1_1)
{
mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLE_FAN;
indexStart = mXFIndexCount++;
mXFIndexBuffer[mXFIndexCount++] = ip2;
mXFIndexBuffer[mXFIndexCount++] = p1;
if(e1->pointCount == 1)
mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex;
else
{
mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex;
mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex + 1;
}
mXFIndexBuffer[mXFIndexCount++] = ip0;
if(e0->pointCount == 1)
mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex;
else
{
mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex + 1;
mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex + 2;
}
mXFIndexBuffer[mXFIndexCount++] = p1;
mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1;
if(chunk->clip)
clip(indexStart - 1);
}
if((chunk->emptyFlags & CornerEmpty_1_0) != CornerEmpty_1_0)
{
mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLE_FAN;
indexStart = mXFIndexCount++;
mXFIndexBuffer[mXFIndexCount++] = ip3;
mXFIndexBuffer[mXFIndexCount++] = p2;
if(e2->pointCount == 1)
mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex;
else
{
mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex + 2;
mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex + 1;
}
mXFIndexBuffer[mXFIndexCount++] = ip0;
if(e1->pointCount == 1)
mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex;
else
{
mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex + 1;
mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex + 2;
}
mXFIndexBuffer[mXFIndexCount++] = p2;
mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1;
if(chunk->clip)
clip(indexStart - 1);
}
if((chunk->emptyFlags & CornerEmpty_0_0) != CornerEmpty_0_0)
{
mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLE_FAN;
indexStart = mXFIndexCount++;
mXFIndexBuffer[mXFIndexCount++] = ip4;
mXFIndexBuffer[mXFIndexCount++] = p3;
if(e3->pointCount == 1)
mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex;
else
{
mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex + 2;
mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex + 1;
}
mXFIndexBuffer[mXFIndexCount++] = ip0;
if(e2->pointCount == 1)
mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex;
else
{
mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex + 1;
mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex;
}
mXFIndexBuffer[mXFIndexCount++] = p3;
mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1;
if(chunk->clip)
clip(indexStart - 1);
}
}
else
{
// subDiv == -1
U32 ip1 = constructPoint(chunk->x + 1, chunk->y + 3);
U32 ip2 = constructPoint(chunk->x + 3, chunk->y + 3);
U32 ip3 = constructPoint(chunk->x + 3, chunk->y + 1);
U32 ip4 = constructPoint(chunk->x + 1, chunk->y + 1);
U32 ip5 = interpPoint(e0->pointIndex + 1, ip0, chunk->x + 2, chunk->y + 3, growFactor);
U32 ip6 = interpPoint(e1->pointIndex + 1, ip0, chunk->x + 3, chunk->y + 2, growFactor);
U32 ip7 = interpPoint(e2->pointIndex + 1, ip0, chunk->x + 2, chunk->y + 1, growFactor);
U32 ip8 = interpPoint(e3->pointIndex + 1, ip0, chunk->x + 1, chunk->y + 2, growFactor);
// now do the squares:
U16 *ib;
if(chunk->emptyFlags & CornerEmpty_0_1)
{
if((chunk->emptyFlags & CornerEmpty_0_1) != CornerEmpty_0_1)
{
mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLES;
U32 indexStart = mXFIndexCount++;
if(!(chunk->emptyFlags & SquareEmpty_0_3))
{
emitTri(ip1, e3->pointIndex, p0);
emitTri(ip1, p0, e0->pointIndex);
}
if(!(chunk->emptyFlags & SquareEmpty_1_3))
{
emitTri(ip1, e0->pointIndex, e0->pointIndex + 1);
emitTri(ip1, e0->pointIndex + 1, ip5);
}
if(!(chunk->emptyFlags & SquareEmpty_1_2))
{
emitTri(ip1, ip5, ip0);
emitTri(ip1, ip0, ip8);
}
if(!(chunk->emptyFlags & SquareEmpty_0_2))
{
emitTri(ip1, ip8, e3->pointIndex + 1);
emitTri(ip1, e3->pointIndex + 1, e3->pointIndex);
}
mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1;
}
}
else
{
ib = mXFIndexBuffer + mXFIndexCount + 1;
ib[-1] = GL_TRIANGLE_FAN;
ib[0] = 10;
ib[1] = ip1;
ib[2] = p0;
ib[3] = e0->pointIndex;
ib[4] = e0->pointIndex + 1;
ib[5] = ip5;
ib[6] = ip0;
ib[7] = ip8;
ib[8] = e3->pointIndex + 1;
ib[9] = e3->pointIndex;
ib[10] = ib[2];
mXFIndexCount += 12;
if(chunk->clip)
clip(mXFIndexCount - 12);
}
if(chunk->emptyFlags & CornerEmpty_1_1)
{
if((chunk->emptyFlags & CornerEmpty_1_1) != CornerEmpty_1_1)
{
mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLES;
U32 indexStart = mXFIndexCount++;
if(!(chunk->emptyFlags & SquareEmpty_3_3))
{
emitTri(ip2, e0->pointIndex + 2, p1);
emitTri(ip2, p1, e1->pointIndex);
}
if(!(chunk->emptyFlags & SquareEmpty_3_2))
{
emitTri(ip2, e1->pointIndex, e1->pointIndex + 1);
emitTri(ip2, e1->pointIndex + 1, ip6);
}
if(!(chunk->emptyFlags & SquareEmpty_2_2))
{
emitTri(ip2, ip6, ip0);
emitTri(ip2, ip0, ip5);
}
if(!(chunk->emptyFlags & SquareEmpty_2_3))
{
emitTri(ip2, ip5, e0->pointIndex + 1);
emitTri(ip2, e0->pointIndex + 1, e0->pointIndex + 2);
}
mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1;
}
}
else
{
ib = mXFIndexBuffer + mXFIndexCount + 1;
ib[-1] = GL_TRIANGLE_FAN;
ib[0] = 10;
ib[1] = ip2;
ib[2] = p1;
ib[3] = e1->pointIndex;
ib[4] = e1->pointIndex + 1;
ib[5] = ip6;
ib[6] = ip0;
ib[7] = ip5;
ib[8] = e0->pointIndex + 1;
ib[9] = e0->pointIndex + 2;
ib[10] = ib[2];
mXFIndexCount += 12;
if(chunk->clip)
clip(mXFIndexCount - 12);
}
if(chunk->emptyFlags & CornerEmpty_1_0)
{
if((chunk->emptyFlags & CornerEmpty_1_0) != CornerEmpty_1_0)
{
mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLES;
U32 indexStart = mXFIndexCount++;
if(!(chunk->emptyFlags & SquareEmpty_3_0))
{
emitTri(ip3, e1->pointIndex + 2, p2);
emitTri(ip3, p2, e2->pointIndex + 2);
}
if(!(chunk->emptyFlags & SquareEmpty_2_0))
{
emitTri(ip3, e2->pointIndex + 2, e2->pointIndex + 1);
emitTri(ip3, e2->pointIndex + 1, ip7);
}
if(!(chunk->emptyFlags & SquareEmpty_2_1))
{
emitTri(ip3, ip7, ip0);
emitTri(ip3, ip0, ip6);
}
if(!(chunk->emptyFlags & SquareEmpty_3_1))
{
emitTri(ip3, ip6, e1->pointIndex + 1);
emitTri(ip3, e1->pointIndex + 1, e1->pointIndex + 2);
}
mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1;
}
}
else
{
ib = mXFIndexBuffer + mXFIndexCount + 1;
ib[-1] = GL_TRIANGLE_FAN;
ib[0] = 10;
ib[1] = ip3;
ib[2] = p2;
ib[3] = e2->pointIndex + 2;
ib[4] = e2->pointIndex + 1;
ib[5] = ip7;
ib[6] = ip0;
ib[7] = ip6;
ib[8] = e1->pointIndex + 1;
ib[9] = e1->pointIndex + 2;
ib[10] = ib[2];
mXFIndexCount += 12;
if(chunk->clip)
clip(mXFIndexCount - 12);
}
if(chunk->emptyFlags & CornerEmpty_0_0)
{
if((chunk->emptyFlags & CornerEmpty_0_0) != CornerEmpty_0_0)
{
mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLES;
U32 indexStart = mXFIndexCount++;
if(!(chunk->emptyFlags & SquareEmpty_0_0))
{
emitTri(ip4, e2->pointIndex, p3);
emitTri(ip4, p3, e3->pointIndex + 2);
}
if(!(chunk->emptyFlags & SquareEmpty_0_1))
{
emitTri(ip4, e3->pointIndex + 2, e3->pointIndex + 1);
emitTri(ip4, e3->pointIndex + 1, ip8);
}
if(!(chunk->emptyFlags & SquareEmpty_1_1))
{
emitTri(ip4, ip8, ip0);
emitTri(ip4, ip0, ip7);
}
if(!(chunk->emptyFlags & SquareEmpty_1_0))
{
emitTri(ip4, ip7, e2->pointIndex + 1);
emitTri(ip4, e2->pointIndex + 1, e2->pointIndex);
}
mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1;
}
}
else
{
ib = mXFIndexBuffer + mXFIndexCount + 1;
ib[-1] = GL_TRIANGLE_FAN;
ib[0] = 10;
ib[1] = ip4;
ib[2] = p3;
ib[3] = e3->pointIndex + 2;
ib[4] = e3->pointIndex + 1;
ib[5] = ip8;
ib[6] = ip0;
ib[7] = ip7;
ib[8] = e2->pointIndex + 1;
ib[9] = e2->pointIndex;
ib[10] = ib[2];
mXFIndexCount += 12;
if(chunk->clip)
clip(mXFIndexCount - 12);
}
}
}
// Do dynamic lighting here...
if (mEnableTerrainDynLights && chunk->lightMask != 0) {
for (U32 i = 0; i < 32; i++) {
if ((chunk->lightMask & (1 << i)) == 0)
continue;
for (U32 start = startXFIndex; start < mXFIndexCount; start++) {
if (mXFIndexBuffer[start] == GL_TRIANGLE_FAN) {
start++;
U32 count = mXFIndexBuffer[start];
U32 triCount = count - 2;
LightTriangle* lightTris = (LightTriangle*)FrameAllocator::alloc(sizeof(LightTriangle) * triCount);
U32 j;
for (j = 0; j < (triCount-1); j++)
lightTris[j].next = &lightTris[j+1];
lightTris[triCount-1].next = sgCurrLightTris;
sgCurrLightTris = lightTris;
// Copy out tri data here...
for (j = 0; j < triCount; j++) {
lightTris[j].point1 = mXFVertices[mXFIndexBuffer[start + 1 + 0]];
lightTris[j].point2 = mXFVertices[mXFIndexBuffer[start + 1 + j + 1]];
lightTris[j].point3 = mXFVertices[mXFIndexBuffer[start + 1 + j + 2]];
lightTris[j].chunkTexture = chunk->chunkTexture;
buildLightTri(&lightTris[j], &mTerrainLights[i]);
}
start += count;
} else {
AssertFatal(mXFIndexBuffer[start] == GL_TRIANGLES, "Error, bad start code!");
start++;
U32 count = mXFIndexBuffer[start];
AssertFatal((count / 3) * 3 == count, "Error, vertex count not divisible by 3!");
U32 triCount = count/3;
LightTriangle* lightTris = (LightTriangle*)FrameAllocator::alloc(sizeof(LightTriangle) * triCount);
U32 j;
for (j = 0; j < (triCount-1); j++)
lightTris[j].next = &lightTris[j+1];
lightTris[triCount-1].next = sgCurrLightTris;
sgCurrLightTris = lightTris;
// Copy out tri data here...
for (j = 0; j < triCount; j++) {
lightTris[j].point1 = mXFVertices[mXFIndexBuffer[start + 1 + (j*3) + 0]];
lightTris[j].point2 = mXFVertices[mXFIndexBuffer[start + 1 + (j*3) + 1]];
lightTris[j].point3 = mXFVertices[mXFIndexBuffer[start + 1 + (j*3) + 2]];
lightTris[j].chunkTexture = chunk->chunkTexture;
buildLightTri(&lightTris[j], &mTerrainLights[i]);
}
start += count;
}
}
}
}
}
/*void TerrainRender::drawTriFan(U32 vCount, U32 *indexBuffer)
{
glBegin(GL_LINES);
U32 cur = vCount - 1;
for(U32 i = 1; i < vCount; i++)
{
glArrayElement(indexBuffer[0]);
glArrayElement(indexBuffer[cur]);
glArrayElement(indexBuffer[cur]);
glArrayElement(indexBuffer[i]);
cur = i;
}
glEnd();
}*/
void TerrainRender::renderXFCache()
{
U32 count = 0;
while (count < mXFIndexCount)
{
U32 mode = mXFIndexBuffer[count];
U32 vertexCount = mXFIndexBuffer[count + 1];
glDrawElements(mode, vertexCount, GL_UNSIGNED_SHORT, mXFIndexBuffer + count + 2);
count += vertexCount + 2;
}
}
void doTexGens(Point4F &s, Point4F &t)
{
EdgePoint *pt = mXFVertices;
EdgePoint *last = pt + mXFPointCount;
for(;pt < last; pt++)
{
pt->haze = pt->x * s.x + pt->y * s.y + pt->z * s.z + s.w;
pt->distance = pt->x * t.x + pt->y * t.y + pt->z * t.z + t.w;
}
}
#ifdef TORQUE_OS_WIN32
void TerrainRender::renderD3DBumps(Point2F bumpTextureOffset)
{
PROFILE_START(TerrainRenderBumpsD3D);
glColor3f(1.0f,1.0f,1.0f);
//First pass - normal bump map, no blending, no offset
doTexGens(bumpTexGenS, bumpTexGenT);
glBindTexture(GL_TEXTURE_2D, mCurrentBlock->mBumpTextureHandle.getGLName());
renderXFCache();
//Second pass - inverted bump map, additive blending, offset
Point4F bumpTexGenOS = bumpTexGenS;
Point4F bumpTexGenOT = bumpTexGenT;
bumpTexGenOS.w += bumpTextureOffset.x;
bumpTexGenOT.w += bumpTextureOffset.y;
doTexGens(bumpTexGenOS, bumpTexGenOT);
glBindTexture(GL_TEXTURE_2D, mCurrentBlock->mInvertedBumpTextureHandle.getGLName());
glBlendFunc(GL_ONE, GL_ONE);
glEnable(GL_BLEND);
renderXFCache();
//Setup blending for the terrain pass
glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR);
PROFILE_END();
}
#endif
void TerrainRender::renderGLBumps(Point2F bumpTextureOffset, U32 hazeName)
{
PROFILE_START(TerrainRenderBumpsGL);
//have to set color to white or bumps turn up black on the terrain
glColor3f(1.0f,1.0f,1.0f);
doTexGens(bumpTexGenS, bumpTexGenT);
//Set up TMU #0
//Normal bump texture
glBindTexture(GL_TEXTURE_2D, mCurrentBlock->mBumpTextureHandle.getGLName());
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE);
//Set up TMU #1
//Inverted bump texture
//Offset the texture matrix
glActiveTextureARB(GL_TEXTURE1_ARB);
glClientActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, mCurrentBlock->mInvertedBumpTextureHandle.getGLName());
glTexCoordPointer(2, GL_FLOAT, sizeof(EdgePoint), &mXFVertices->haze);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_ADD);
glMatrixMode(GL_TEXTURE);
glPushMatrix();
glTranslatef(bumpTextureOffset.x, bumpTextureOffset.y, 0.0f);
//Render!
renderXFCache();
//Restore TMU #1
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glBindTexture(GL_TEXTURE_2D, hazeName);
glTexCoordPointer(2, GL_FLOAT, sizeof(EdgePoint), &mXFVertices->fogRed);
//Restore TMU #0
glActiveTextureARB(GL_TEXTURE0_ARB);
glClientActiveTextureARB(GL_TEXTURE0_ARB);
//Set up for the bump mapping application
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glEnable(GL_BLEND);
//Do the rest in the function
PROFILE_END();
}
void TerrainRender::renderBlock(TerrainBlock *block, SceneState *state)
{
PROFILE_START(TerrainRender);
// verify lighting type is the same...
static bool lastblended = false;
bool blended = LightManager::sgAllowBlendedTerrainDynamicLighting();
if(lastblended != blended)
{
block->triggerLightmapReload();
block->flushCache();
lastblended = blended;
}
PROFILE_START(TerrainRenderSetup);
dglSetRenderPrimType(1);
U32 storedWaterMark = FrameAllocator::getWaterMark();
if (sgTextureFreeListPrimed == false) {
sgTextureFreeListPrimed = true;
AssertFatal(mTextureFreeList.size() == 0, "Error, unprimed free list should always be size 0");
mTextureFreeList.setSize(sgFreeListPrimeCount);
for (U32 i = 0; i < sgFreeListPrimeCount; i++) {
constructInPlace(&mTextureFreeList[i]);
mTextureFreeList[i] = TextureHandle((const char*)NULL, mBlendBitmap, TerrainTexture, true);
}
}
mFrameIndex++;
mSceneState = state;
mFarDistance = state->getVisibleDistance();
dglGetModelview(&mCameraToObject);
mCameraToObject.inverse();
mCameraToObject.getColumn(3, &mCamPos);
mFogColor = state->getFogColor();
TextureHandle hazeTexture = gClientSceneGraph->getFogTexture();
mCurrentBlock = block;
Point4F detTexGenS(1.0f, 0.0f, 0.0f, 0.0f);
Point4F detTexGenT(0.0f, 1.0f, 0.0f, 0.0f);
if (mEnableTerrainDetails && mCurrentBlock->mDetailTextureHandle.getGLName() != 0) {
detTexGenS.x *= 62.0f / mCurrentBlock->mDetailTextureHandle.getWidth();
detTexGenT.y *= 62.0f / mCurrentBlock->mDetailTextureHandle.getHeight();
detTexGenS.w = -(S32) (mCamPos.x*detTexGenS.x);
detTexGenT.w = -(S32) (mCamPos.y*detTexGenT.y);
}
// CW - stuff with bump maps
//calculate the texture offset
if (mEnableTerrainEmbossBumps && mCurrentBlock->mBumpTextureHandle.getGLName() != 0)
{
PROFILE_START(TerrainRenderBumpBuild);
//There should be a better way to do this, really...
#ifdef TORQUE_OS_WIN32
if (dStrcmp(Con::getVariable( "$pref::Video::displayDevice" ), "OpenGL") == 0)
mRenderGL = true;
else
mRenderGL = false;
#endif
//note: don't multiply light by inverse modelview matrix
//because it is already in object space (or world or anything, really)
//The first light in the light manager is always the sun.
//This is also how players are lit, so if this is broken, that is broken as well.
Point3F sTangent,tTangent;
LightInfo *sun = gClientSceneGraph->getLightManager()->sgGetSpecialLight(LightManager::sgSunLightType);
VectorF sunVector = -sun->mDirection;
//find s and t tangents
F32 pHeight[4];
for (int i = 0; i < 4; i++)
{
int x = i == 0 || i == 3 ? 0 : 255;
int y = i == 1 || i == 2 ? 0 : 255;
block->getHeight(Point2F(x,y), &pHeight[i]);
}
S32 pSquareSize = block->getSquareSize();
sTangent.x = pSquareSize;
sTangent.y = pHeight[3] - pHeight[0];
sTangent.z = 0;
tTangent.x = 0;
tTangent.y = pHeight[1] - pHeight[0];
tTangent.z = pSquareSize;
sTangent.normalize();
tTangent.normalize();
// The above is needed for both emboss and dot3 bump mapping
// techniques. However, the below 2 lines are emboss-specific.
// So, dot3 bump mapping would not be too difficult because
// I have already computed most of the necessary info.
bumpTextureOffset.x = mDot(sTangent, sunVector) * mCurrentBlock->mBumpOffset;
bumpTextureOffset.y = mDot(tTangent, sunVector) * mCurrentBlock->mBumpOffset;
PROFILE_END();
}
// CW - end bump map stuff
mSquareSize = block->getSquareSize();
// mNewGenTextureCount = 0;
// compute pixelError
if(mScreenError >= 0.001f)
mPixelError = 1.0f / dglProjectRadius(mScreenError, 1);
else
mPixelError = 0.000001f;
buildClippingPlanes(state->mFlipCull);
buildLightArray();
F32 worldToScreenScale = dglProjectRadius(1,1);
F32 zeroDetailDistance = (mSquareSize * worldToScreenScale) / (1 << 6) - (mSquareSize >> 1);
F32 zeroBumpDistance = (mSquareSize * worldToScreenScale) / (1 << mCurrentBlock->mZeroBumpScale) - (mSquareSize >> 1);
F32 blockSize = mSquareSize * TerrainBlock::BlockSquareWidth;
S32 xStart;
S32 xEnd;
S32 yStart;
S32 yEnd;
if(mCurrentBlock->mTile)
{
xStart = (S32)mFloor( (mCamPos.x - mFarDistance) / blockSize );
xEnd = (S32)mCeil ( (mCamPos.x + mFarDistance) / blockSize );
yStart = (S32)mFloor( (mCamPos.y - mFarDistance) / blockSize );
yEnd = (S32)mCeil ( (mCamPos.y + mFarDistance) / blockSize );
}
else
{
xStart = 0;
xEnd = 1;
yStart = 0;
yEnd = 1;
}
S32 xExt = (S32)(xEnd - xStart);
S32 yExt = (S32)(yEnd - yStart);
PROFILE_END();
PROFILE_START(TerrainRenderRecurse);
F32 height = fixedToFloat(block->getHeight(0,0));
EdgeParent **bottomEdges = (EdgeParent **) FrameAllocator::alloc(sizeof(ChunkScanEdge *) * xExt);
TerrainRender::allocRenderEdges(xExt, bottomEdges, false);
ChunkCornerPoint *prevCorner = TerrainRender::allocInitialPoint(Point3F(xStart * blockSize, yStart * blockSize, height));
mTerrainOffset.set(xStart * TerrainBlock::BlockSquareWidth, yStart * TerrainBlock::BlockSquareWidth);
for(S32 x = 0; x < xExt; x++)
{
bottomEdges[x]->p1 = prevCorner;
prevCorner = TerrainRender::allocInitialPoint(Point3F((xStart + x ) * blockSize, yStart * blockSize, height));
bottomEdges[x]->p2 = prevCorner;
}
for(S32 y = 0; y < yExt; y++)
{
// allocate the left edge:
EdgeParent *left;
TerrainRender::allocRenderEdges(1, &left, false);
left->p1 = TerrainRender::allocInitialPoint(Point3F(xStart * blockSize, (yStart + y + 1) * blockSize, height));
left->p2 = bottomEdges[0]->p1;
for(S32 x = 0; x < xExt; x++)
{
EdgeParent *right;
TerrainRender::allocRenderEdges(1, &right, false);
right->p1 = TerrainRender::allocInitialPoint(Point3F((xStart + x + 1) * blockSize, (yStart + y + 1) * blockSize, height));
right->p2 = bottomEdges[x]->p2;
EdgeParent *top;
TerrainRender::allocRenderEdges(1, &top, false);
top->p1 = left->p1;
top->p2 = right->p1;
mBlockOffset.set(x << TerrainBlock::BlockShift,
y << TerrainBlock::BlockShift);
mBlockPos.set((xStart + x) * blockSize,
(yStart + y) * blockSize);
TerrainRender::processCurrentBlock(state, top, right, bottomEdges[x], left);
left = right;
bottomEdges[x] = top;
}
}
U32 slop = 0;
AllocatedTexture *fwalk = mTextureFreeListHead.next;
while(fwalk != &mTextureFreeListTail && slop < mTextureSlopSize)
{
fwalk = fwalk->next;
slop++;
}
while(fwalk != &mTextureFreeListTail)
{
AllocatedTexture *next = fwalk->next;
fwalk->unlink();
S32 x = (fwalk->x & TerrainBlock::BlockMask) >> fwalk->level;
S32 y = (fwalk->y & TerrainBlock::BlockMask) >> fwalk->level;
mTextureGridPtr[fwalk->level - 2][x + (y << (8 - fwalk->level))] = NULL;
freeTerrTexture(fwalk);
fwalk = next;
}
PROFILE_END();
PROFILE_START(TerrainRenderEmit);
bool lockArrays = dglDoesSupportCompiledVertexArray() && dglDoesSupportARBMultitexture();
bool vertexBuffer = dglDoesSupportVertexBuffer() && (block->mVertexBuffer != -1);
bool blendedlighting = LightManager::sgAllowBlendedTerrainDynamicLighting();
glFrontFace(GL_CW);
glEnable(GL_CULL_FACE);
glEnableClientState(GL_VERTEX_ARRAY);
if(!vertexBuffer)
mXFVertices = (EdgePoint *) FrameAllocator::alloc(sizeof(EdgePoint) * VertexBufferSize);
//CW - D3D goes crazy if we stick our bump colors in the EdgePoint structure, so make a new one
ColorI *bumpColors = (ColorI*)FrameAllocator::alloc(sizeof(ColorI) * VertexBufferSize);
glVertexPointer(3, GL_FLOAT, sizeof(EdgePoint), mXFVertices);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ZERO);
// see if we should setup hazing on the second TMU
// if not, we'll have to 2-pass the terrain...
// and that ain't gonna be fast no matter how ya slice it.
// Hazing
if(blendedlighting)
{
// set this up for bump and dynamic...
glClientActiveTextureARB(GL_TEXTURE1_ARB);
glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_2D);
// set this up for fog...
// disable so not on in all passes...
// base pass must turn fog on...
glClientActiveTextureARB(GL_TEXTURE2_ARB);
glActiveTextureARB(GL_TEXTURE2_ARB);
glDisable(GL_TEXTURE_2D);
}
else
{
glClientActiveTextureARB(GL_TEXTURE1_ARB);
glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_2D);
}
glBindTexture(GL_TEXTURE_2D, hazeTexture.getGLName()); //handle
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, sizeof(EdgePoint), &mXFVertices->fogRed);
// Base textures
glClientActiveTextureARB(GL_TEXTURE0_ARB);
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, sizeof(EdgePoint), &mXFVertices->haze);
glDisable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
mXFIndex = 0;
sgCurrLightTris = NULL;
AllocatedTexture *walk;
mXFIndexBuffer = (U16 *) FrameAllocator::alloc(sizeof(U16) * 64 * 64 * 4);
Vector<AllocatedTexture *> deleteList;
while((walk = mTextureFrameListHead.next) != &mTextureFrameListTail)
{
walk->unlink();
if(walk->level != 6)
walk->linkAfter(&mTextureFreeListHead);
else
walk->linkAfter(&mTextureFreeBigListHead);
mStaticTextureCount++;
if(!walk->handle)
buildBlendMap(walk);
AllocatedTexture *step = walk;
while(step)
{
// loop through the list and draw all the squares:
F32 invLevel = 1.0f / F32(mSquareSize << step->level);
sgTexGenS.set(invLevel, 0.0f, 0.0f, -(step->x >> step->level));
sgTexGenT.set(0.0f, invLevel, 0.0f, -(step->y >> step->level));
// Bump map texture coordinate "calculation" (hack) for ATi cards -CW
// Why don't Point4F's have a multiply operator?
const F32 scale = 32.0f / mCurrentBlock->mBumpScale;
bumpTexGenS.x = sgTexGenS.x * scale;
bumpTexGenS.y = sgTexGenS.y * scale;
bumpTexGenS.z = sgTexGenS.z * scale;
bumpTexGenS.w = sgTexGenS.w * scale;
bumpTexGenT.x = sgTexGenT.x * scale;
bumpTexGenT.y = sgTexGenT.y * scale;
bumpTexGenT.z = sgTexGenT.z * scale;
bumpTexGenT.w = sgTexGenT.w * scale;
// End -CW
while(step->list)
{
mXFPointCount = 0;
mXFIndex++;
mXFIndexPtr = mXFIndexBuffer;
mXFIndexCount = 0;
PROFILE_START(TerrainRenderLock);
if(vertexBuffer)
mXFVertices = (EdgePoint *) glLockVertexBufferEXT(block->mVertexBuffer, VertexBufferSize);
PROFILE_END();
PROFILE_START(TerrainRenderBuild);
bool renderDetails = false;
// CW - stuff with bump maps
bool renderBumps = false;
// CW - end bump map stuff
EmitChunk *sq;
for(sq = step->list; sq && mXFPointCount < maxTerrPoints; sq = sq->next)
{
if(mRenderingCommander)
{
renderChunkCommander(sq);
}
else
{
renderDetails |= sq->renderDetails && mEnableTerrainDetails;
// CW - stuff with bump maps
renderBumps |= sq->renderBumps && mEnableTerrainEmbossBumps;
// CW - end bump map stuff
renderChunkOutline(sq);
}
AssertFatal(mXFPointCount <= VertexBufferSize, "Invalid point count.");
AssertFatal(mXFIndexCount <= 64 * 64 * 4, "Index count sucks.");
}
step->list = sq;
if(mXFIndexCount && renderDetails && mCurrentBlock->mDetailTextureHandle.getGLName() != 0)
{
for(U32 a = 0; a < mXFPointCount; a++)
{
EdgePoint& rPoint = mXFVertices[a];
F32 distFactor = (zeroDetailDistance - rPoint.distance) / zeroDetailDistance;
if(distFactor < 0.0f)
distFactor = 0.0f;
else if(distFactor > 1.0f)
distFactor = 1.0f;
F32 factor = state->getHazeAndFog(rPoint.distance, rPoint.z);
U8 c = U8((((1.0f - factor) * distFactor) * 255.0f) + 0.5f);
rPoint.detailColor.red = c;
rPoint.detailColor.green = c;
rPoint.detailColor.blue = c;
rPoint.detailColor.alpha = c;
}
}
// for bumps, it shouldn't fade out purely linearly
// instead, only start linear fade out at 3/4 out
if(mXFIndexCount && renderBumps && mCurrentBlock->mBumpTextureHandle.getGLName() != 0)
{
for(U32 a = 0; a < mXFPointCount; a++)
{
EdgePoint& rPoint = mXFVertices[a];
F32 distFactor = rPoint.distance / zeroBumpDistance;
if(distFactor < 0.0f)
distFactor = 0.0f;
else if(distFactor > 1.0f)
distFactor = 1.0f;
U8 c = 0;
if(distFactor > 0.75f)
c = U8( (distFactor - 0.75f) * 4.0f * 255.0f + 0.5f );
else
c = 0;
bumpColors[a].red = 127;
bumpColors[a].green = 127;
bumpColors[a].blue = 127;
bumpColors[a].alpha = c;
}
}
PROFILE_END();
PROFILE_START(TerrainRenderUnlock);
if(vertexBuffer)
glUnlockVertexBufferEXT(block->mVertexBuffer);
PROFILE_END();
// Render the bump maps! -CW
glDisable(GL_BLEND);
if(renderBumps && mCurrentBlock->mBumpTextureHandle.getGLName() != 0 && mCurrentBlock->mInvertedBumpTextureHandle.getGLName() != 0 && mEnableTerrainEmbossBumps)
{
#ifdef TORQUE_OS_WIN32
if(mRenderGL)
renderGLBumps(bumpTextureOffset, hazeTexture.getGLName());
else
// D3D sucks...
renderD3DBumps(bumpTextureOffset);
#else
renderGLBumps(bumpTextureOffset, hazeTexture.getGLName());
#endif
//add a little fade-out
glEnable(GL_BLEND);
glDisable(GL_TEXTURE_2D);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ColorI), bumpColors);
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_2D);
renderXFCache();
glEnable(GL_TEXTURE_2D);
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glDisableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR);
glColor3f(1.0f,1.0f,1.0f);
}
// CW - end bump map render
if(mXFIndexCount)
{
if(blendedlighting)
{
glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, mCurrentBlock->lightMapTexture.getGLName());
LightManager::sgSetupExposureRendering();
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
F32 offset = 1.0f / F32(mCurrentBlock->getSquareSize() * TerrainBlock::BlockSize);
glTexGenfv(GL_S, GL_OBJECT_PLANE, Point4F(offset, 0.0f, 0.0f, 0.0f));
glTexGenfv(GL_T, GL_OBJECT_PLANE, Point4F(0.0f, offset, 0.0f, 0.0f));
glActiveTextureARB(GL_TEXTURE2_ARB);
glEnable(GL_TEXTURE_2D);
glActiveTextureARB(GL_TEXTURE0_ARB);
}
PROFILE_START(TerrainRenderBind);
glBindTexture(GL_TEXTURE_2D, walk->handle.getGLName());
doTexGens(sgTexGenS, sgTexGenT);
PROFILE_END();
PROFILE_START(TerrainRenderSetVertexBuffer);
if(vertexBuffer)
glSetVertexBufferEXT(block->mVertexBuffer);
if(lockArrays)
glLockArraysEXT(0, mXFPointCount);
// lock the array
PROFILE_END();
PROFILE_START(TerrainRenderXFRender);
renderXFCache();
PROFILE_END();
if(blendedlighting)
{
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_GEN_S);
glDisable(GL_TEXTURE_GEN_T);
LightManager::sgResetExposureRendering();
glActiveTextureARB(GL_TEXTURE2_ARB);
glDisable(GL_TEXTURE_2D);
glActiveTextureARB(GL_TEXTURE0_ARB);
}
if(renderDetails && mCurrentBlock->mDetailTextureHandle.getGLName() != 0)
{
PROFILE_START(TerrainRenderDetails);
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(EdgePoint), &mXFVertices->detailColor);
glBindTexture(GL_TEXTURE_2D, mCurrentBlock->mDetailTextureHandle.getGLName());
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
if(vertexBuffer)
mXFVertices = (EdgePoint *) glLockVertexBufferEXT(block->mVertexBuffer, VertexBufferSize);
doTexGens(detTexGenS, detTexGenT);
if(vertexBuffer)
glUnlockVertexBufferEXT(block->mVertexBuffer);
glEnable(GL_BLEND);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE);
const F32 gray[4] = {0.5f, 0.5f, 0.5f, 0.5f};
glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, gray);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_PRIMARY_COLOR);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA);
glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR);
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_2D);
renderXFCache();
glEnable(GL_TEXTURE_2D);
glActiveTextureARB(GL_TEXTURE0_ARB);
glDisable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glDisableClientState(GL_COLOR_ARRAY);
PROFILE_END();
}
if(lockArrays)
glUnlockArraysEXT();
}
}
AllocatedTexture *nextStep = step->nextLink;
step->list = 0;
step->nextLink = 0;
if(step != walk)
deleteList.push_back(step);
step = nextStep;
}
}
glActiveTextureARB(GL_TEXTURE1_ARB);
glClientActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glActiveTextureARB(GL_TEXTURE0_ARB);
glClientActiveTextureARB(GL_TEXTURE0_ARB);
glDisable(GL_TEXTURE_2D);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
if(mEnableTerrainDynLights && sgCurrLightTris) {
glEnable(GL_TEXTURE_2D);
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glBindTexture(GL_TEXTURE_2D, mCurrentBlock->mDynLightTexture.getGLName());
if(blendedlighting)
{
glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_2D);
LightManager::sgSetupExposureRendering();
// terrain texture alpha is garbage...
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
}
else
{
LightManager::sgSetupExposureRendering();
}
LightTriangle* walkLT = sgCurrLightTris;
while(walkLT)
{
if(walkLT->flags)
{
if(blendedlighting)
{
F32 invLevel = 1.0f / F32(mSquareSize << walkLT->chunkTexture->level);
sgTexGenS.set(invLevel, 0.0f, 0.0f, -(walkLT->chunkTexture->x >> walkLT->chunkTexture->level));
sgTexGenT.set(0.0f, invLevel, 0.0f, -(walkLT->chunkTexture->y >> walkLT->chunkTexture->level));
glBindTexture(GL_TEXTURE_2D, walkLT->chunkTexture->handle.getGLName());
}
glBegin(GL_TRIANGLES);
glColor4fv(walkLT->color);
glMultiTexCoord2fARB(GL_TEXTURE0_ARB, walkLT->texco1.x, walkLT->texco1.y);
if(blendedlighting)
{
glMultiTexCoord2fARB(GL_TEXTURE1_ARB,
custom_dot(sgTexGenS, walkLT->point1),
custom_dot(sgTexGenT, walkLT->point1));
}
glVertex3fv(walkLT->point1);
glMultiTexCoord2fARB(GL_TEXTURE0_ARB, walkLT->texco2.x, walkLT->texco2.y);
if(blendedlighting)
{
glMultiTexCoord2fARB(GL_TEXTURE1_ARB,
custom_dot(sgTexGenS, walkLT->point2),
custom_dot(sgTexGenT, walkLT->point2));
}
glVertex3fv(walkLT->point2);
glMultiTexCoord2fARB(GL_TEXTURE0_ARB, walkLT->texco3.x, walkLT->texco3.y);
if(blendedlighting)
{
glMultiTexCoord2fARB(GL_TEXTURE1_ARB,
custom_dot(sgTexGenS, walkLT->point3),
custom_dot(sgTexGenT, walkLT->point3));
}
glVertex3fv(walkLT->point3);
glEnd();
}
walkLT = walkLT->next;
}
if(blendedlighting)
{
LightManager::sgResetExposureRendering();
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glDisable(GL_TEXTURE_2D);
glActiveTextureARB(GL_TEXTURE0_ARB);
}
else
{
LightManager::sgResetExposureRendering();
}
glDisable(GL_TEXTURE_2D);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ZERO);
}
for(U32 i=0; i<deleteList.size(); i++)
delete deleteList[i];
deleteList.clear();
glDisable(GL_CULL_FACE);
FrameAllocator::setWaterMark(storedWaterMark);
dglSetRenderPrimType(0);
PROFILE_END();
PROFILE_END();
}
//---------------------------------------------------------------
//---------------------------------------------------------------
// Texture allocation / free list management
//---------------------------------------------------------------
void TerrainRender::flushCache()
{
for(S32 i = 0; i < AllocatedTextureCount; i++)
mTextureGrid[i] = 0;
for(S32 i = 0; i < mTextureFreeList.size(); i++)
mTextureFreeList[i] = NULL;
mTextureFreeList.clear();
AllocatedTexture *tex;
while( (tex = mTextureFreeListHead.next) != &mTextureFreeListTail)
{
tex->unlink();
delete tex;
}
while( (tex = mTextureFreeBigListHead.next) != &mTextureFreeBigListTail)
{
tex->unlink();
delete tex;
}
}
void TerrainRender::flushCacheRect(RectI bRect)
{
bRect.inset(-1,-1);
for(S32 i = 0; i < AllocatedTextureCount; i++)
{
AllocatedTexture *tex = mTextureGrid[i];
if(!tex)
continue;
RectI texRect(tex->x, tex->y, 1 << tex->level, 1 << tex->level);
if(texRect.intersect(bRect))
{
mTextureGrid[i] = NULL;
tex->unlink();
freeTerrTexture(tex);
}
}
}
//---------------------------------------------------------------
void TerrainRender::freeTerrTexture(AllocatedTexture *texture)
{
if(texture->handle)
{
mTextureFreeList.increment();
constructInPlace(&mTextureFreeList.last());
mTextureFreeList.last() = texture->handle;
}
delete texture;
}
//---------------------------------------------------------------
void TerrainRender::allocTerrTexture(Point2I pos, U32 level, U32 mipLevel, bool vis, F32 distance)
{
vis;
S32 blockX = mBlockOffset.x + mTerrainOffset.x;
S32 blockY = mBlockOffset.y + mTerrainOffset.y;
S32 x = pos.x + blockX;
S32 y = pos.y + blockY;
S32 px = (pos.x & TerrainBlock::BlockMask) >> level;
S32 py = (pos.y & TerrainBlock::BlockMask) >> level;
AssertFatal(level >= 2 && level <= 6, "Invalid level");
AllocatedTexture *cur = mTextureGridPtr[level - 2][px + (py << (8 - level))];
if(!cur)
{
cur = new AllocatedTexture;
cur->list = NULL;
cur->mipLevel = mipLevel;
cur->x = x;
cur->y = y;
cur->level = level;
cur->nextLink = NULL;
}
else
{
AssertFatal(cur->level == level, "Invalid block for this level.");
if(cur->list && (cur->x != x || cur->y != y))
{
// see if the texture is already in the list...
AllocatedTexture *walk = cur->nextLink;
while(walk && walk->x != x && walk->y != y)
walk = walk->nextLink;
if(walk)
{
mCurrentTexture = walk;
return;
}
AllocatedTexture *tail = new AllocatedTexture;
tail->list = NULL;
tail->x = x;
tail->y = y;
tail->level = level;
tail->nextLink = cur->nextLink;
tail->distance = distance;
cur->nextLink = tail;
mCurrentTexture = tail;
return;
}
else
{
cur->x = x;
cur->y = y;
cur->unlink();
}
}
cur->linkAfter(&mTextureFrameListHead);
cur->distance = distance;
mCurrentTexture = cur;
mTextureGridPtr[level - 2][px + (py << (8 - level))] = cur;
}
static inline void fixcolors(GBitmap* bmp)
{
#if defined(TORQUE_OS_MAC) && defined(TORQUE_LITTLE_ENDIAN)
U32 _bpp = bmp->bytesPerPixel;
for(int miplevel = 0; miplevel < bmp->getNumMipLevels(); miplevel++)
{
U16* pixels = (U16*)bmp->getWritableBits(miplevel);
U32 numpixels = bmp->getWidth(miplevel) * bmp->getHeight(miplevel);
for( int i = 0; i < numpixels; i++ )
{
register const U16 c = *(pixels + i);
// from:
// *sourceFormat = GL_RGBA;
// *byteFormat = GL_UNSIGNED_SHORT_5_5_5_1;
// to:
// *sourceFormat = GL_BGRA_EXT;
// *byteFormat = GL_UNSIGNED_SHORT_1_5_5_5_REV;
//static U16 __color = 0xF800;
// rrrrr ggggg bbbbb a
// a bbbbb ggggg rrrrr
register U16 red = ( c & 0xf800 ) >> 11;
register U16 green = ( c & 0x07C0 ) >> 6;
register U16 blue = ( c & 0x003e ) >> 1;
register U16 alpha = ( c & 0x0001 );
*(pixels + i) = alpha << 15 | red << 10 | green << 5 | blue;
}
}
#endif
}
void TerrainRender::buildBlendMap(AllocatedTexture *tex)
{
PROFILE_START(TerrainRenderBuildBlendMap);
GBitmap *bmp = mBlendBitmap;
S32 x = tex->x;
S32 y = tex->y;
S32 level = tex->level;
AssertFatal(mCurrentBlock->lightMap->getFormat() == GBitmap::RGB5551, "Error, lightmap must be 5551");
AssertFatal(bmp->getFormat() == GBitmap::RGB5551, "Error, destination must be 565");
AssertFatal(bmp->getWidth() == TerrainTextureSize && bmp->getHeight() == TerrainTextureSize, avar("Error, bitmaps must be %d high and wide for the terrain", TerrainTextureSize));
AssertFatal(mCurrentBlock->lightMap->getWidth() == 512 && mCurrentBlock->lightMap->getHeight() == 512,
"Fast blender requires a 512 lightmap!");
U16* mips[TerrainTextureMipLevel + 1];
for (U32 i = 0; i < bmp->getNumMipLevels(); i++)
mips[i] = (U16*)bmp->getWritableBits(i);
GBitmap *lightmap = NULL;
if(LightManager::sgAllowBlendedTerrainDynamicLighting())
{
if(!mCurrentBlock->whiteMap)
{
#define SG_GRAY 0x7BDF// 01111 01111 01111 1
#define SG_REDMASK 0xF800// 11111 00000 00000 0
#define SG_BLUEMASK 0x3E // 00000 00000 11111 0
#define SG_GREENMASK 0x7C0 // 00000 11111 00000 0
#define SG_ALPHAMASK 0x1 // 00000 00000 00000 1
mCurrentBlock->whiteMap = new GBitmap(
TerrainBlock::LightmapSize, TerrainBlock::LightmapSize,
false, GBitmap::RGB5551);
U16 color = SG_GRAY;
U32 count = mCurrentBlock->whiteMap->byteSize / 2;
U16 *bits = (U16 *)mCurrentBlock->whiteMap->getWritableBits();
for(U32 i=0; i<count; i++)
bits[i] = color;
GBitmap *lm = new GBitmap(
TerrainBlock::LightmapSize, TerrainBlock::LightmapSize,
false, GBitmap::RGB5551);
bits = (U16 *)mCurrentBlock->lightMap->getWritableBits();
U16 *lmbits = (U16 *)lm->getWritableBits();
for(U32 i=0; i<count; i++)
{
U16 color = bits[i];
U16 red = (color & SG_REDMASK) >> 11;
U16 blue = (color & SG_BLUEMASK) >> 1;
U16 green = (color & SG_GREENMASK) >> 6;
U16 alpha = (color & SG_ALPHAMASK);
#if defined(TORQUE_OS_MAC)
// a bbbbb ggggg rrrrr
lmbits[i] = (alpha << 15) | (blue << 10) | (green << 5) | red;
#else
// bbbbb ggggg rrrrr a
lmbits[i] = (blue << 11) | (green << 6) | (red << 1) | alpha;
#endif
}
mCurrentBlock->lightMapTexture = TextureHandle(NULL, lm);
}
lightmap = mCurrentBlock->whiteMap;
}
else
{
lightmap = mCurrentBlock->lightMap;
}
mCurrentBlock->mBlender->blend(x, y, level, (const U16*)lightmap->getBits(), mips);
fixcolors(bmp);
mDynamicTextureCount++;
if(mTextureFreeList.size())
{
tex->handle = mTextureFreeList.last();
mTextureFreeList.last() = NULL;
mTextureFreeList.decrement();
tex->handle.refresh(mBlendBitmap);
}
else
{
tex->handle = TextureHandle((const char*)NULL, mBlendBitmap, TerrainTexture, true);
}
PROFILE_END();
}