Torque3D/Engine/source/terrain/terrCell.cpp
2020-05-11 15:37:43 -05:00

1215 lines
32 KiB
C++

//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
// Copyright (C) 2015 Faust Logic, Inc.
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
#include "platform/platform.h"
#include "terrain/terrCell.h"
#include "math/util/frustum.h"
#include "terrain/terrData.h"
#include "terrain/terrCellMaterial.h"
#include "scene/sceneRenderState.h"
#include "lighting/lightManager.h"
#include "gfx/gfxDrawUtil.h"
GFXImplementVertexFormat( TerrVertex )
{
addElement( "POSITION", GFXDeclType_Float3 );
addElement( "NORMAL", GFXDeclType_Float3 );
addElement( "TangentZ", GFXDeclType_Float, 0 );
addElement( "Empty", GFXDeclType_Float, 1 );
};
const U32 TerrCell::smMinCellSize = 64;
const U32 TerrCell::smVBStride = TerrCell::smMinCellSize + 1; // 129
const U32 TerrCell::smVBSize = ( TerrCell::smVBStride * TerrCell::smVBStride ) +
( TerrCell::smVBStride * 4 ); // 17,157
const U32 TerrCell::smPBSize = ( TerrCell::smMinCellSize * TerrCell::smMinCellSize * 6 ) +
( TerrCell::smMinCellSize * 4 * 6 ); // 101,376
const U32 TerrCell::smTriCount = TerrCell::smPBSize / 3; // 33,792
TerrCell::TerrCell()
: mTriCount( 0 ),
mHasEmpty( false ),
mMaterial( NULL ),
mMaterials( 0 ),
mIsInteriorOnly( false ),
mSize(smMinCellSize),
mLevel(0),
mTerrain(NULL),
mRadius(0.5f)
{
dMemset( mChildren, 0, sizeof( mChildren ) );
zode_vertexBuffer = 0;
}
TerrCell::~TerrCell()
{
SAFE_DELETE( mMaterial );
for ( U32 i=0; i < 4; i++ )
SAFE_DELETE( mChildren[i] );
deleteZodiacVertexBuffer();
}
void TerrCell::createPrimBuffer( GFXPrimitiveBufferHandle *primBuffer )
{
PROFILE_SCOPE( TerrCell_AllocPrimBuffer );
primBuffer->set( GFX, smPBSize, 1, GFXBufferTypeStatic, "TerrCell" );
// We don't use the primitive for normal clipmap
// rendering, but it is used for the shadow pass.
GFXPrimitive *prim = primBuffer->getPointer()->mPrimitiveArray;
prim->type = GFXTriangleList;
prim->numPrimitives = smTriCount;
prim->numVertices = smVBSize;
//
// The vertex pattern for the terrain is as
// follows...
//
// 0----1----2.....n
// |\ | /|
// | \ | / |
// | \ | / |
// | \|/ |
// n----n----n
// | /|\ |
// | / | \ |
// | / | \ |
// |/ | \|
// n----n----n
//
// Lock and fill it up!
U16 *idxBuff;
primBuffer->lock( &idxBuff );
U32 counter = 0;
U32 maxIndex = 0;
for ( U32 y = 0; y < smMinCellSize; y++ )
{
const U32 yTess = y % 2;
for ( U32 x = 0; x < smMinCellSize; x++ )
{
U32 index = ( y * smVBStride ) + x;
const U32 xTess = x % 2;
if ( ( xTess == 0 && yTess == 0 ) ||
( xTess != 0 && yTess != 0 ) )
{
idxBuff[0] = index + 0;
idxBuff[1] = index + smVBStride;
idxBuff[2] = index + smVBStride + 1;
idxBuff[3] = index + 0;
idxBuff[4] = index + smVBStride + 1;
idxBuff[5] = index + 1;
}
else
{
idxBuff[0] = index + 1;
idxBuff[1] = index;
idxBuff[2] = index + smVBStride;
idxBuff[3] = index + 1;
idxBuff[4] = index + smVBStride;
idxBuff[5] = index + smVBStride + 1;
}
idxBuff += 6;
maxIndex = index + 1 + smVBStride;
counter += 6;
}
}
// Now add indices for the 'skirts'.
// These could probably be reduced to a loop.
// Temporaries that hold triangle indices.
// Top/Bottom - 0,1
U32 t0, t1, b0, b1;
// Top edge skirt...
// Index to the first vert of the top row.
U32 startIndex = 0;
// Index to the first vert of the skirt under the top row.
U32 skirtStartIdx = smVBStride * smVBStride;
// Step to go one vert to the right.
U32 step = 1;
for ( U32 i = 0; i < smMinCellSize; i++ )
{
t0 = startIndex + i * step;
t1 = t0 + step;
b0 = skirtStartIdx + i;
b1 = skirtStartIdx + i + 1;
idxBuff[0] = b0;
idxBuff[1] = t0;
idxBuff[2] = t1;
idxBuff[3] = b1;
idxBuff[4] = b0;
idxBuff[5] = t1;
idxBuff += 6;
maxIndex = b1;
counter += 6;
}
// Bottom edge skirt...
// Index to the first vert of the bottom row.
startIndex = smVBStride * smVBStride - smVBStride;
// Index to the first vert of the skirt under the bottom row.
skirtStartIdx = startIndex + smVBStride * 2;
// Step to go one vert to the right.
step = 1;
for ( U32 i = 0; i < smMinCellSize; i++ )
{
t0 = startIndex + ( i * step );
t1 = t0 + step;
b0 = skirtStartIdx + i;
b1 = skirtStartIdx + i + 1;
idxBuff[0] = t1;
idxBuff[1] = t0;
idxBuff[2] = b0;
idxBuff[3] = t1;
idxBuff[4] = b0;
idxBuff[5] = b1;
idxBuff += 6;
maxIndex = b1;
counter += 6;
}
// Left edge skirt...
// Index to the first vert of the left column.
startIndex = 0;
// Index to the first vert of the skirt under the left column.
skirtStartIdx = smVBStride * smVBStride + smVBStride * 2;
// Step to go one vert down.
step = smVBStride;
for ( U32 i = 0; i < smMinCellSize; i++ )
{
t0 = startIndex + ( i * step );
t1 = t0 + step;
b0 = skirtStartIdx + i;
b1 = skirtStartIdx + i + 1;
idxBuff[0] = t1;
idxBuff[1] = t0;
idxBuff[2] = b0;
idxBuff[3] = t1;
idxBuff[4] = b0;
idxBuff[5] = b1;
idxBuff += 6;
maxIndex = b1;
counter += 6;
}
// Right edge skirt...
// Index to the first vert of the right column.
startIndex = smVBStride - 1;
// Index to the first vert of the skirt under the right column.
skirtStartIdx = smVBStride * smVBStride + smVBStride * 3;
// Step to go one vert down.
step = smVBStride;
for ( U32 i = 0; i < smMinCellSize; i++ )
{
t0 = startIndex + ( i * step );
t1 = t0 + step;
b0 = skirtStartIdx + i;
b1 = skirtStartIdx + i + 1;
idxBuff[0] = b0;
idxBuff[1] = t0;
idxBuff[2] = t1;
idxBuff[3] = b1;
idxBuff[4] = b0;
idxBuff[5] = t1;
idxBuff += 6;
maxIndex = b1;
counter += 6;
}
primBuffer->unlock();
}
TerrCell* TerrCell::init( TerrainBlock *terrain )
{
// Just create the root cell and call the inner init.
TerrCell *root = new TerrCell;
root->_init( terrain,
Point2I( 0, 0 ),
terrain->getBlockSize(),
0 );
// Set initial states of OBBs.
root->updateOBBs();
return root;
}
void TerrCell::_init( TerrainBlock *terrain,
const Point2I &point,
U32 size,
U32 level )
{
PROFILE_SCOPE( TerrCell_Init );
mTerrain = terrain;
mPoint = point;
mSize = size;
mLevel = level;
// Generate a VB (and maybe a PB) for this cell, unless we are the Root cell.
if ( level > 0 )
{
_updateVertexBuffer();
_updatePrimitiveBuffer();
}
if ( mSize <= smMinCellSize )
{
// Update our bounds and materials... the
// parent will use it to update itself.
_updateBounds();
_updateMaterials();
return;
}
// Create our children and update our
// bounds and materials from them.
const U32 childSize = mSize / 2;
const U32 childLevel = mLevel + 1;
mChildren[0] = new TerrCell;
mChildren[0]->_init( mTerrain,
Point2I( mPoint.x, mPoint.y ),
childSize,
childLevel );
mBounds = mChildren[0]->getBounds();
mMaterials = mChildren[0]->getMaterials();
mChildren[1] = new TerrCell;
mChildren[1]->_init( mTerrain,
Point2I( mPoint.x + childSize, mPoint.y ),
childSize,
childLevel );
mBounds.intersect( mChildren[1]->getBounds() );
mMaterials |= mChildren[1]->getMaterials();
mChildren[2] = new TerrCell;
mChildren[2]->_init( mTerrain,
Point2I( mPoint.x, mPoint.y + childSize ),
childSize,
childLevel );
mBounds.intersect( mChildren[2]->getBounds() );
mMaterials |= mChildren[2]->getMaterials();
mChildren[3] = new TerrCell;
mChildren[3]->_init( mTerrain,
Point2I( mPoint.x + childSize, mPoint.y + childSize ),
childSize,
childLevel );
mBounds.intersect( mChildren[3]->getBounds() );
mMaterials |= mChildren[3]->getMaterials();
mRadius = mBounds.len() * 0.5f;
_updateOBB();
}
void TerrCell::updateGrid( const RectI &gridRect, bool opacityOnly )
{
PROFILE_SCOPE( TerrCell_UpdateGrid );
// If we have a VB... then update it.
if ( mVertexBuffer.isValid() && !opacityOnly )
_updateVertexBuffer();
// Update our PB, if any
_updatePrimitiveBuffer();
// If we don't have children... then we're
// a leaf at the bottom of the cell quadtree
// and we should just update our bounds.
if ( !mChildren[0] )
{
if ( !opacityOnly )
_updateBounds();
_updateMaterials();
return;
}
// Otherwise, we must call updateGrid on our children
// and then update our bounds/materials AFTER to contain them.
mMaterials = 0;
for ( U32 i = 0; i < 4; i++ )
{
TerrCell *cell = mChildren[i];
// The overlap test doesn't hit shared edges
// so grow it a bit when we create it.
const RectI cellRect( cell->mPoint.x - 1,
cell->mPoint.y - 1,
cell->mSize + 2,
cell->mSize + 2 );
// We do an overlap and containment test as it
// properly handles zero sized rects.
if ( cellRect.contains( gridRect ) ||
cellRect.overlaps( gridRect ) )
cell->updateGrid( gridRect, opacityOnly );
// Update the bounds from our children.
if ( !opacityOnly )
{
if ( i == 0 )
mBounds = mChildren[i]->getBounds();
else
mBounds.intersect( mChildren[i]->getBounds() );
mRadius = mBounds.len() * 0.5f;
}
// Update the material flags.
mMaterials |= mChildren[i]->getMaterials();
}
if ( mMaterial )
mMaterial->init( mTerrain, mMaterials );
}
void TerrCell::_updateVertexBuffer()
{
PROFILE_SCOPE( TerrCell_UpdateVertexBuffer );
// Start off with no empty squares
mHasEmpty = false;
mEmptyVertexList.clear();
mVertexBuffer.set( GFX, smVBSize, GFXBufferTypeStatic );
const F32 squareSize = mTerrain->getSquareSize();
const U32 blockSize = mTerrain->getBlockSize();
const U32 stepSize = mSize / smMinCellSize;
U32 vbcounter = 0;
TerrVertex *vert = mVertexBuffer.lock();
Point2I gridPt;
Point2F point;
F32 height;
Point3F normal;
const TerrainFile *file = mTerrain->getFile();
for ( U32 y = 0; y < smVBStride; y++ )
{
for ( U32 x = 0; x < smVBStride; x++ )
{
// We clamp here to keep the geometry from reading across
// one side of the height map to the other causing walls
// around the edges of the terrain.
gridPt.x = mClamp( mPoint.x + x * stepSize, 0, blockSize - 1 );
gridPt.y = mClamp( mPoint.y + y * stepSize, 0, blockSize - 1 );
// Setup this point.
point.x = (F32)gridPt.x * squareSize;
point.y = (F32)gridPt.y * squareSize;
height = fixedToFloat( file->getHeight( gridPt.x, gridPt.y ) );
vert->point.x = point.x;
vert->point.y = point.y;
vert->point.z = height;
// Get the normal.
mTerrain->getSmoothNormal( point, &normal, true, false );
vert->normal = normal;
// Get the tangent z.
vert->tangentZ = fixedToFloat( file->getHeight( gridPt.x + 1, gridPt.y ) ) - height;
// Test the empty state for this vert.
if ( file->isEmptyAt( gridPt.x, gridPt.y ) )
{
mHasEmpty = true;
mEmptyVertexList.push_back( vbcounter );
}
vbcounter++;
++vert;
}
}
// Add verts for 'skirts' around/beneath the edge verts of this cell.
// This could probably be reduced to a loop...
const F32 skirtDepth = mSize / smMinCellSize * mTerrain->getSquareSize();
// Top edge skirt
for ( U32 i = 0; i < smVBStride; i++ )
{
gridPt.x = mClamp( mPoint.x + i * stepSize, 0, blockSize - 1 );
gridPt.y = mClamp( mPoint.y, 0, blockSize - 1 );
point.x = (F32)gridPt.x * squareSize;
point.y = (F32)gridPt.y * squareSize;
height = fixedToFloat( file->getHeight( gridPt.x, gridPt.y ) );
vert->point.x = point.x;
vert->point.y = point.y;
vert->point.z = height - skirtDepth;
// Get the normal.
mTerrain->getNormal( point, &normal, true, false );
vert->normal = normal;
// Get the tangent.
vert->tangentZ = height - fixedToFloat( file->getHeight( gridPt.x + 1, gridPt.y ) );
vbcounter++;
++vert;
}
// Bottom edge skirt
for ( U32 i = 0; i < smVBStride; i++ )
{
gridPt.x = mClamp( mPoint.x + i * stepSize, 0, blockSize - 1 );
gridPt.y = mClamp( mPoint.y + smMinCellSize * stepSize, 0, blockSize - 1 );
point.x = (F32)gridPt.x * squareSize;
point.y = (F32)gridPt.y * squareSize;
height = fixedToFloat( file->getHeight( gridPt.x, gridPt.y ) );
vert->point.x = point.x;
vert->point.y = point.y;
vert->point.z = height - skirtDepth;
// Get the normal.
mTerrain->getNormal( point, &normal, true, false );
vert->normal = normal;
// Get the tangent.
vert->tangentZ = height - fixedToFloat( file->getHeight( gridPt.x + 1, gridPt.y ) );
vbcounter++;
++vert;
}
// Left edge skirt
for ( U32 i = 0; i < smVBStride; i++ )
{
gridPt.x = mClamp( mPoint.x, 0, blockSize - 1 );
gridPt.y = mClamp( mPoint.y + i * stepSize, 0, blockSize - 1 );
point.x = (F32)gridPt.x * squareSize;
point.y = (F32)gridPt.y * squareSize;
height = fixedToFloat( file->getHeight( gridPt.x, gridPt.y ) );
vert->point.x = point.x;
vert->point.y = point.y;
vert->point.z = height - skirtDepth;
// Get the normal.
mTerrain->getNormal( point, &normal, true, false );
vert->normal = normal;
// Get the tangent.
vert->tangentZ = height - fixedToFloat( file->getHeight( gridPt.x + 1, gridPt.y ) );
vbcounter++;
++vert;
}
// Right edge skirt
for ( U32 i = 0; i < smVBStride; i++ )
{
gridPt.x = mClamp( mPoint.x + smMinCellSize * stepSize, 0, blockSize - 1 );
gridPt.y = mClamp( mPoint.y + i * stepSize, 0, blockSize - 1 );
point.x = (F32)gridPt.x * squareSize;
point.y = (F32)gridPt.y * squareSize;
height = fixedToFloat( file->getHeight( gridPt.x, gridPt.y ) );
vert->point.x = point.x;
vert->point.y = point.y;
vert->point.z = height - skirtDepth;
// Get the normal.
mTerrain->getNormal( point, &normal, true, false );
vert->normal = normal;
// Get the tangent.
vert->tangentZ = height - fixedToFloat( file->getHeight( gridPt.x + 1, gridPt.y ) );
vbcounter++;
++vert;
}
AssertFatal( vbcounter == smVBSize, "bad" );
mVertexBuffer.unlock();
deleteZodiacVertexBuffer();
}
void TerrCell::_updatePrimitiveBuffer()
{
PROFILE_SCOPE( TerrCell_UpdatePrimitiveBuffer );
if ( !mHasEmpty )
{
if ( mPrimBuffer.isValid() )
{
// There are no more empty squares for this cell, so
// get rid of the primitive buffer to use the standard one.
mPrimBuffer = NULL;
}
return;
}
// Build our custom primitive buffer. We're setting it to the maximum allowed
// size, but should be just shy of it depending on the number of empty squares
// in this cell. We could calculate it, but note that it would be different
// from mEmptyVertexList.size() as that can include vertices on the edges that
// are really considered part of another cell's squares. So we take a slightly
// larger buffer over running through the calculation.
mPrimBuffer.set( GFX, smPBSize, 1, GFXBufferTypeStatic, "TerrCell" );
GFXPrimitive *prim = mPrimBuffer.getPointer()->mPrimitiveArray;
prim->type = GFXTriangleList;
prim->numVertices = smVBSize;
mTriCount = 0;
// Lock and fill it up!
U16 *idxBuff;
mPrimBuffer.lock( &idxBuff );
U32 counter = 0;
U32 maxIndex = 0;
for ( U32 y = 0; y < smMinCellSize; y++ )
{
const U32 yTess = y % 2;
for ( U32 x = 0; x < smMinCellSize; x++ )
{
U32 index = ( y * smVBStride ) + x;
// Should this square be skipped?
if ( _isVertIndexEmpty(index) )
continue;
const U32 xTess = x % 2;
if ( ( xTess == 0 && yTess == 0 ) ||
( xTess != 0 && yTess != 0 ) )
{
idxBuff[0] = index + 0;
idxBuff[1] = index + smVBStride;
idxBuff[2] = index + smVBStride + 1;
idxBuff[3] = index + 0;
idxBuff[4] = index + smVBStride + 1;
idxBuff[5] = index + 1;
}
else
{
idxBuff[0] = index + 1;
idxBuff[1] = index;
idxBuff[2] = index + smVBStride;
idxBuff[3] = index + 1;
idxBuff[4] = index + smVBStride;
idxBuff[5] = index + smVBStride + 1;
}
idxBuff += 6;
maxIndex = index + 1 + smVBStride;
counter += 6;
mTriCount += 2;
}
}
// Now add indices for the 'skirts'.
// These could probably be reduced to a loop.
// Temporaries that hold triangle indices.
// Top/Bottom - 0,1
U32 t0, t1, b0, b1;
// Top edge skirt...
// Index to the first vert of the top row.
U32 startIndex = 0;
// Index to the first vert of the skirt under the top row.
U32 skirtStartIdx = smVBStride * smVBStride;
// Step to go one vert to the right.
U32 step = 1;
for ( U32 i = 0; i < smMinCellSize; i++ )
{
t0 = startIndex + i * step;
// Should this square be skipped?
if ( _isVertIndexEmpty(t0) )
continue;
t1 = t0 + step;
b0 = skirtStartIdx + i;
b1 = skirtStartIdx + i + 1;
idxBuff[0] = b0;
idxBuff[1] = t0;
idxBuff[2] = t1;
idxBuff[3] = b1;
idxBuff[4] = b0;
idxBuff[5] = t1;
idxBuff += 6;
maxIndex = b1;
counter += 6;
mTriCount += 2;
}
// Bottom edge skirt...
// Index to the first vert of the bottom row.
startIndex = smVBStride * smVBStride - smVBStride;
// Index to the first vert of the skirt under the bottom row.
skirtStartIdx = startIndex + smVBStride * 2;
// Step to go one vert to the right.
step = 1;
for ( U32 i = 0; i < smMinCellSize; i++ )
{
t0 = startIndex + ( i * step );
// Should this square be skipped? We actually need to test
// the vertex one row down as it defines the empty state
// for this square.
if ( _isVertIndexEmpty( t0 - smVBStride ) )
continue;
t1 = t0 + step;
b0 = skirtStartIdx + i;
b1 = skirtStartIdx + i + 1;
idxBuff[0] = t1;
idxBuff[1] = t0;
idxBuff[2] = b0;
idxBuff[3] = t1;
idxBuff[4] = b0;
idxBuff[5] = b1;
idxBuff += 6;
maxIndex = b1;
counter += 6;
mTriCount += 2;
}
// Left edge skirt...
// Index to the first vert of the left column.
startIndex = 0;
// Index to the first vert of the skirt under the left column.
skirtStartIdx = smVBStride * smVBStride + smVBStride * 2;
// Step to go one vert down.
step = smVBStride;
for ( U32 i = 0; i < smMinCellSize; i++ )
{
t0 = startIndex + ( i * step );
// Should this square be skipped?
if ( _isVertIndexEmpty(t0) )
continue;
t1 = t0 + step;
b0 = skirtStartIdx + i;
b1 = skirtStartIdx + i + 1;
idxBuff[0] = t1;
idxBuff[1] = t0;
idxBuff[2] = b0;
idxBuff[3] = t1;
idxBuff[4] = b0;
idxBuff[5] = b1;
idxBuff += 6;
maxIndex = b1;
counter += 6;
mTriCount += 2;
}
// Right edge skirt...
// Index to the first vert of the right column.
startIndex = smVBStride - 1;
// Index to the first vert of the skirt under the right column.
skirtStartIdx = smVBStride * smVBStride + smVBStride * 3;
// Step to go one vert down.
step = smVBStride;
for ( U32 i = 0; i < smMinCellSize; i++ )
{
t0 = startIndex + ( i * step );
// Should this square be skipped? We actually need to test
// the vertex one column to the left as it defines the empty
// state for this square.
if ( _isVertIndexEmpty( t0 - 1 ) )
continue;
t1 = t0 + step;
b0 = skirtStartIdx + i;
b1 = skirtStartIdx + i + 1;
idxBuff[0] = b0;
idxBuff[1] = t0;
idxBuff[2] = t1;
idxBuff[3] = b1;
idxBuff[4] = b0;
idxBuff[5] = t1;
idxBuff += 6;
maxIndex = b1;
counter += 6;
mTriCount += 2;
}
mPrimBuffer.unlock();
prim->numPrimitives = mTriCount;
}
void TerrCell::_updateMaterials()
{
PROFILE_SCOPE( TerrCell_UpdateMaterials );
// This should really only be called for cells of smMinCellSize,
// in which case stepSize is always one.
U32 stepSize = mSize / smMinCellSize;
mMaterials = 0;
U8 index;
U32 x, y;
const TerrainFile *file = mTerrain->getFile();
// Step thru the samples in the map then.
for ( y = 0; y < smVBStride; y++ )
{
for ( x = 0; x < smVBStride; x++ )
{
index = file->getLayerIndex( mPoint.x + x * stepSize,
mPoint.y + y * stepSize );
// Skip empty layers and anything that doesn't fit
// the 64bit material flags.
if ( index == U8_MAX || index > 63 )
continue;
mMaterials |= (U64)((U64)1<<index);
}
}
if ( mMaterial )
mMaterial->init( mTerrain, mMaterials );
}
void TerrCell::_updateBounds()
{
PROFILE_SCOPE( TerrCell_UpdateBounds );
const F32 squareSize = mTerrain->getSquareSize();
// This should really only be called for cells of smMinCellSize,
// in which case stepSize is always one.
const U32 stepSize = mSize / smMinCellSize;
// Prepare to expand the bounds.
mBounds.minExtents.set( F32_MAX, F32_MAX, F32_MAX );
mBounds.maxExtents.set( -F32_MAX, -F32_MAX, -F32_MAX );
Point3F vert;
Point2F texCoord;
const TerrainFile *file = mTerrain->getFile();
for ( U32 y = 0; y < smVBStride; y++ )
{
for ( U32 x = 0; x < smVBStride; x++ )
{
// Setup this point.
vert.x = (F32)( mPoint.x + x * stepSize ) * squareSize;
vert.y = (F32)( mPoint.y + y * stepSize ) * squareSize;
vert.z = fixedToFloat( file->getHeight( mPoint.x + x,
mPoint.y + y ) );
// HACK: Call it twice to deal with the inverted
// inital bounds state... shouldn't be a perf issue.
mBounds.extend( vert );
mBounds.extend( vert );
}
}
mRadius = mBounds.len() * 0.5;
_updateOBB();
}
void TerrCell::_updateOBB()
{
mOBB.set( mTerrain->getTransform(), mBounds );
}
void TerrCell::updateOBBs()
{
_updateOBB();
// Update children.
if( mChildren[ 0 ] )
for( U32 i = 0; i < 4; ++ i )
mChildren[ i ]->updateOBBs();
}
void TerrCell::updateZoning( const SceneZoneSpaceManager *zoneManager )
{
PROFILE_SCOPE( TerrCell_UpdateZoning );
mZoneOverlap.setSize( zoneManager->getNumZones() );
mZoneOverlap.clear();
mIsInteriorOnly = true;
if ( mChildren[0] == NULL )
{
Box3F worldBounds( mBounds );
mTerrain->getTransform().mul( worldBounds );
Vector<U32> zones;
zoneManager->findZones( worldBounds, zones );
for ( U32 i=0; i < zones.size(); i++ )
{
// Set overlap bit for zone except it's the outdoor zone.
if( zones[ i ] != SceneZoneSpaceManager::RootZoneId )
mZoneOverlap.set( zones[i] );
else
mIsInteriorOnly = false;
}
return;
}
for ( U32 i = 0; i < 4; i++ )
{
TerrCell *cell = mChildren[i];
cell->updateZoning( zoneManager );
mZoneOverlap.combineOR( cell->getZoneOverlap() );
mIsInteriorOnly &= cell->mIsInteriorOnly;
}
}
void TerrCell::cullCells( const SceneRenderState *state,
const Point3F &objLodPos,
Vector<TerrCell*> *outCells )
{
// If we have a VB and no children then just add
// ourselves to the results and return.
if ( mVertexBuffer.isValid() && !mChildren[0] )
{
outCells->push_back( this );
return;
}
const F32 screenError = mTerrain->getScreenError();
const BitVector &zoneState = state->getCullingState().getZoneVisibilityFlags();
for ( U32 i = 0; i < 4; i++ )
{
TerrCell *cell = mChildren[i];
// Test cell visibility for interior zones.
const bool visibleInside = !cell->getZoneOverlap().empty() ? zoneState.testAny( cell->getZoneOverlap() ) : false;
// Test cell visibility for outdoor zone, but only
// if we need to.
bool visibleOutside = false;
if( !mIsInteriorOnly && !visibleInside )
{
U32 outdoorZone = SceneZoneSpaceManager::RootZoneId;
visibleOutside = !state->getCullingState().isCulled( cell->mOBB, &outdoorZone, 1 );
}
// Skip cell if neither visible indoors nor outdoors.
if( !visibleInside && !visibleOutside )
continue;
// Lod based on screen error...
// If far enough, just add this child cells vb ( skipping its children ).
F32 dist = cell->getDistanceTo( objLodPos );
F32 errorMeters = ( cell->mSize / smMinCellSize ) * mTerrain->getSquareSize();
U32 errorPixels = mCeil( state->projectRadius( dist, errorMeters ) );
if ( errorPixels < screenError )
{
if ( cell->mVertexBuffer.isValid() )
outCells->push_back( cell );
}
else
cell->cullCells( state, objLodPos, outCells );
}
}
void TerrCell::getRenderPrimitive( GFXPrimitive *prim,
GFXVertexBufferHandleBase *vertBuff,
GFXPrimitiveBufferHandle *primBuff ) const
{
*vertBuff = mVertexBuffer;
// Only supply a primitive buffer if we're using our own
// due to empty squares.
bool useStaticPrimBuffer = true;
if ( mPrimBuffer.isValid() )
{
useStaticPrimBuffer = false;
*primBuff = mPrimBuffer;
}
prim->type = GFXTriangleList;
prim->startVertex = 0;
prim->minIndex = 0;
prim->startIndex = 0;
prim->numVertices = smVBSize;
if ( useStaticPrimBuffer )
{
// Use the standard primitive buffer count
prim->numPrimitives = smTriCount;
}
else
{
// Use our triangle count that matches out primitive buffer
prim->numPrimitives = mTriCount;
}
}
void TerrCell::renderBounds() const
{
LinearColorF color;
color.interpolate( ColorI::RED, ColorI::GREEN, (F32)mLevel / 3.0f );
GFXStateBlockDesc desc;
desc.setZReadWrite( true, false );
desc.fillMode = GFXFillWireframe;
GFX->getDrawUtil()->drawCube( desc, mBounds, color.toColorI());
}
void TerrCell::preloadMaterials()
{
PROFILE_SCOPE( TerrCell_PreloadMaterials );
// If we have a VB then we need a material.
if ( mVertexBuffer.isValid() )
{
TerrainCellMaterial *material = getMaterial();
material->getReflectMat();
if ( GFX->getPixelShaderVersion() > 2.0f &&
dStrcmp( LIGHTMGR->getId(), "BLM" ) != 0)
material->getDeferredMat();
}
for ( U32 i = 0; i < 4; i++ )
if ( mChildren[i] )
mChildren[i]->preloadMaterials();
}
TerrainCellMaterial* TerrCell::getMaterial()
{
if ( !mMaterial )
{
mMaterial = new TerrainCellMaterial;
mMaterial->init( mTerrain, mMaterials );
}
return mMaterial;
}
void TerrCell::deleteMaterials()
{
SAFE_DELETE( mMaterial );
for ( U32 i = 0; i < 4; i++ )
if ( mChildren[i] )
mChildren[i]->deleteMaterials();
}
const Point3F* TerrCell::getZodiacVertexBuffer()
{
if (!zode_vertexBuffer)
createZodiacVertexBuffer();
return zode_vertexBuffer;
}
void TerrCell::createZodiacPrimBuffer(U16** zode_primBuffer)
{
if (*zode_primBuffer != 0)
delete [] *zode_primBuffer;
*zode_primBuffer = new U16[TerrCell::smMinCellSize*TerrCell::smMinCellSize*6];
// Lock and fill it up!
U16* idxBuff = *zode_primBuffer;
U32 counter = 0;
U32 maxIndex = 0;
for ( U32 y = 0; y < smMinCellSize; y++ )
{
const U32 yTess = y % 2;
for ( U32 x = 0; x < smMinCellSize; x++ )
{
U32 index = ( y * smVBStride ) + x;
const U32 xTess = x % 2;
if ( ( xTess == 0 && yTess == 0 ) ||
( xTess != 0 && yTess != 0 ) )
{
idxBuff[0] = index + 0;
idxBuff[1] = index + smVBStride;
idxBuff[2] = index + smVBStride + 1;
idxBuff[3] = index + 0;
idxBuff[4] = index + smVBStride + 1;
idxBuff[5] = index + 1;
}
else
{
idxBuff[0] = index + 1;
idxBuff[1] = index;
idxBuff[2] = index + smVBStride;
idxBuff[3] = index + 1;
idxBuff[4] = index + smVBStride;
idxBuff[5] = index + smVBStride + 1;
}
idxBuff += 6;
maxIndex = index + 1 + smVBStride;
counter += 6;
}
}
}
void TerrCell::createZodiacVertexBuffer()
{
const F32 squareSize = mTerrain->getSquareSize();
const U32 blockSize = mTerrain->getBlockSize();
const U32 stepSize = mSize / smMinCellSize;
if (zode_vertexBuffer)
delete [] zode_vertexBuffer;
zode_vertexBuffer = new Point3F[smVBStride*smVBStride];
Point3F* vert = zode_vertexBuffer;
Point2I gridPt;
Point2F point;
F32 height;
const TerrainFile *file = mTerrain->getFile();
for ( U32 y = 0; y < smVBStride; y++ )
{
for ( U32 x = 0; x < smVBStride; x++ )
{
// We clamp here to keep the geometry from reading across
// one side of the height map to the other causing walls
// around the edges of the terrain.
gridPt.x = mClamp( mPoint.x + x * stepSize, 0, blockSize - 1 );
gridPt.y = mClamp( mPoint.y + y * stepSize, 0, blockSize - 1 );
// Setup this point.
point.x = (F32)gridPt.x * squareSize;
point.y = (F32)gridPt.y * squareSize;
height = fixedToFloat( file->getHeight( gridPt.x, gridPt.y ) );
vert->x = point.x;
vert->y = point.y;
vert->z = height;
++vert;
}
}
}
void TerrCell::deleteZodiacVertexBuffer()
{
if (zode_vertexBuffer)
{
delete [] zode_vertexBuffer;
zode_vertexBuffer = 0;
}
}