mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-19 20:24:49 +00:00
SPECIAL NOTE: highly suggest https://github.com/GarageGames/Torque3D/pull/1441 or a variation thereof to prevent debug spew and false-postives for occlusion results. With significant research, development and prototyping assistance from both @andr3wmac (shaders and partial hook work), and @LuisAntonRebollo (additional culling) System operates as follows: 1) materials are given an additional castDynamicShadows boolean entry. (Default at time of writing is true by request. Personal usage at time of writing defaults to false. value is default-initialized in materialDefinition.cpp. script/gui exposed) 2) lights are given a staticRefreshFreq and dynamicRefreshFreq (in milliseconds). script/gui exposed 3) materials are (effectively) sorted into dynamic and static shadowmap render lists based on flag. (see shadowMapPass.cpp) 4) initial shadowmaps are generated for each light and 'list'. 5) as each refreshFreq times out, the relevant shadowmap for a given light is refreshed. Special notes: dynamicRefreshFreq for all lights is set to a (script exposed) 8MS refresh timer. StaticRefreshFreq for the lions share of lights defaults to 250 MS (1/4 of a second) scattersky's embedded light, which is intended to operate in a mobile manner, defaults to 8 to reiterate, these are all customizable per-light via script/inspector gui in the case of alternate project needs.
508 lines
14 KiB
C++
508 lines
14 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.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "platform/platform.h"
|
|
#include "forest/forestCell.h"
|
|
|
|
#include "forest/forest.h"
|
|
#include "forest/forestCellBatch.h"
|
|
#include "forest/forestCollision.h"
|
|
#include "T3D/physics/physicsPlugin.h"
|
|
#include "T3D/physics/physicsBody.h"
|
|
#include "T3D/physics/physicsCollision.h"
|
|
#include "collision/concretePolyList.h"
|
|
|
|
#include "gfx/gfxDrawUtil.h"
|
|
#include "math/util/frustum.h"
|
|
|
|
|
|
ForestCell::ForestCell( const RectF &rect ) :
|
|
mRect( rect ),
|
|
mBounds( Box3F::Invalid ),
|
|
mLargestItem( ForestItem::Invalid ),
|
|
mIsDirty( false ),
|
|
mIsInteriorOnly( false )
|
|
{
|
|
dMemset( mSubCells, 0, sizeof( mSubCells ) );
|
|
dMemset( mPhysicsRep, 0, sizeof( mPhysicsRep ) );
|
|
}
|
|
|
|
ForestCell::~ForestCell()
|
|
{
|
|
mItems.clear();
|
|
|
|
for ( U32 i=0; i < 4; i++ )
|
|
SAFE_DELETE( mSubCells[i] );
|
|
|
|
freeBatches();
|
|
|
|
SAFE_DELETE( mPhysicsRep[0] );
|
|
SAFE_DELETE( mPhysicsRep[1] );
|
|
}
|
|
|
|
void ForestCell::freeBatches()
|
|
{
|
|
for ( U32 i=0; i < mBatches.size(); i++ )
|
|
SAFE_DELETE( mBatches[i] );
|
|
|
|
mBatches.clear();
|
|
}
|
|
|
|
void ForestCell::buildBatches()
|
|
{
|
|
// Gather items for batches.
|
|
Vector<ForestItem> items;
|
|
getItems( &items );
|
|
|
|
// Ask the item to batch itself.
|
|
Vector<ForestItem>::const_iterator item = items.begin();
|
|
bool batched = false;
|
|
for ( ; item != items.end(); item++ )
|
|
{
|
|
// Loop thru the batches till someone
|
|
// takes this guy off our hands.
|
|
batched = false;
|
|
for ( S32 i=0; i < mBatches.size(); i++ )
|
|
{
|
|
if ( mBatches[i]->add( *item ) )
|
|
{
|
|
batched = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( batched )
|
|
continue;
|
|
|
|
// Gotta create a new batch.
|
|
ForestCellBatch *batch = item->getData()->allocateBatch();
|
|
if ( batch )
|
|
{
|
|
batch->add( *item );
|
|
mBatches.push_back( batch );
|
|
}
|
|
}
|
|
}
|
|
|
|
S32 ForestCell::renderBatches( SceneRenderState *state, Frustum *culler )
|
|
{
|
|
PROFILE_SCOPE( ForestCell_renderBatches );
|
|
|
|
if ( !hasBatches() )
|
|
return 0;
|
|
|
|
S32 renderedItems = 0;
|
|
|
|
for ( S32 i=0; i < mBatches.size(); i++ )
|
|
{
|
|
// Is this batch entirely culled?
|
|
if ( culler && culler->isCulled( mBatches[i]->getWorldBox() ) )
|
|
continue;
|
|
|
|
if( state->getCullingState().isOccludedWithExtraPlanesCull( mBatches[i]->getWorldBox() ) )
|
|
continue;
|
|
|
|
mBatches[i]->render( state );
|
|
renderedItems += mBatches[i]->getItemCount();
|
|
}
|
|
|
|
return renderedItems;
|
|
}
|
|
|
|
S32 ForestCell::render( TSRenderState *rdata, const Frustum *culler )
|
|
{
|
|
PROFILE_SCOPE( ForestCell_render );
|
|
|
|
AssertFatal( isLeaf(), "ForestCell::render() - This shouldn't be called on non-leaf cells!" );
|
|
|
|
U32 itemsRendered = 0;
|
|
|
|
// TODO: Items are generated in order of type,
|
|
// so we can maybe save some overhead by preparing
|
|
// the item for rendering once.
|
|
|
|
Vector<ForestItem>::iterator item = mItems.begin();
|
|
for ( ; item != mItems.end(); item++ )
|
|
{
|
|
// Do we need to cull individual items?
|
|
if ( culler && culler->isCulled( item->getWorldBox() ) )
|
|
continue;
|
|
|
|
if ( item->getData()->render( rdata, *item ) )
|
|
++itemsRendered;
|
|
}
|
|
|
|
return itemsRendered;
|
|
}
|
|
|
|
void ForestCell::_updateBounds()
|
|
{
|
|
mIsDirty = false;
|
|
mBounds = Box3F::Invalid;
|
|
mLargestItem = ForestItem::Invalid;
|
|
|
|
F32 radius;
|
|
|
|
if ( isBranch() )
|
|
{
|
|
for ( U32 i=0; i < 4; i++ )
|
|
{
|
|
mBounds.intersect( mSubCells[i]->getBounds() );
|
|
|
|
radius = mSubCells[i]->mLargestItem.getRadius();
|
|
if ( radius > mLargestItem.getRadius() )
|
|
mLargestItem = mSubCells[i]->mLargestItem;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Loop thru all the items in this cell.
|
|
Vector<ForestItem>::const_iterator item = mItems.begin();
|
|
for ( ; item != mItems.end(); item++ )
|
|
{
|
|
mBounds.intersect( (*item).getWorldBox() );
|
|
|
|
radius = (*item).getRadius();
|
|
if ( radius > mLargestItem.getRadius() )
|
|
mLargestItem = (*item);
|
|
}
|
|
}
|
|
|
|
void ForestCell::_updateZoning( const SceneZoneSpaceManager *zoneManager )
|
|
{
|
|
PROFILE_SCOPE( ForestCell_UpdateZoning );
|
|
|
|
mZoneOverlap.setSize( zoneManager->getNumZones() );
|
|
mZoneOverlap.clear();
|
|
mIsInteriorOnly = true;
|
|
|
|
if ( isLeaf() )
|
|
{
|
|
// Skip empty cells... they don't have valid bounds.
|
|
if ( mItems.empty() )
|
|
return;
|
|
|
|
Vector<U32> zones;
|
|
zoneManager->findZones( getBounds(), 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++ )
|
|
{
|
|
ForestCell *cell = mSubCells[i];
|
|
cell->_updateZoning( zoneManager );
|
|
mZoneOverlap.combineOR( cell->getZoneOverlap() );
|
|
mIsInteriorOnly &= cell->mIsInteriorOnly;
|
|
}
|
|
}
|
|
|
|
bool ForestCell::findIndexByKey( ForestItemKey key, U32 *outIndex ) const
|
|
{
|
|
// Do a simple binary search.
|
|
|
|
U32 i = 0,
|
|
lo = 0,
|
|
hi = mItems.size();
|
|
|
|
const ForestItem *items = mItems.address();
|
|
|
|
while ( lo < hi )
|
|
{
|
|
i = (lo + hi) / 2;
|
|
|
|
if ( key < items[i].getKey() )
|
|
hi = i;
|
|
else if ( key > items[i].getKey() )
|
|
lo = i + 1;
|
|
else
|
|
{
|
|
*outIndex = i;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
*outIndex = lo;
|
|
return false;
|
|
}
|
|
|
|
const ForestItem& ForestCell::insertItem( ForestItemKey key,
|
|
ForestItemData *data,
|
|
const MatrixF &xfm,
|
|
F32 scale )
|
|
{
|
|
AssertFatal( key != 0, "ForestCell::insertItem() - Got null key!" );
|
|
AssertFatal( data != NULL, "ForestCell::insertItem() - Got null datablock!" );
|
|
|
|
// Make sure we update the bounds later.
|
|
mIsDirty = true;
|
|
|
|
// PhysicsBody is now invalid and must be rebuilt later.
|
|
SAFE_DELETE( mPhysicsRep[0] );
|
|
SAFE_DELETE( mPhysicsRep[1] );
|
|
|
|
// Destroy batches so we recreate it on
|
|
// the next next render.
|
|
freeBatches();
|
|
|
|
// Ok... do we need to split this cell?
|
|
if ( isLeaf() && mItems.size() > MaxItems )
|
|
{
|
|
// Add the children.
|
|
for ( U32 i=0; i < 4; i++ )
|
|
mSubCells[i] = new ForestCell( _makeChildRect( i ) );
|
|
|
|
// Now push all our current children down.
|
|
Vector<ForestItem>::iterator item = mItems.begin();
|
|
for ( ; item != mItems.end(); item++ )
|
|
{
|
|
U32 index = _getSubCell( item->getPosition().x, item->getPosition().y );
|
|
|
|
mSubCells[index]->insertItem( item->getKey(),
|
|
item->getData(),
|
|
item->getTransform(),
|
|
item->getScale() );
|
|
}
|
|
|
|
// Clean up.
|
|
mItems.clear();
|
|
mItems.compact();
|
|
}
|
|
|
|
// Do we have children?
|
|
if ( isBranch() )
|
|
{
|
|
// Ok... kick this item down then.
|
|
U32 index = _getSubCell( xfm.getPosition().x, xfm.getPosition().y );
|
|
const ForestItem &result = mSubCells[index]->insertItem( key, data, xfm, scale );
|
|
|
|
AssertFatal( index == _getSubCell( result.getPosition().x, result.getPosition().y ), "ForestCell::insertItem() - binning is hosed." );
|
|
|
|
return result;
|
|
}
|
|
|
|
// Do the datablock preload here to insure it happens
|
|
// before an item is used in the scene.
|
|
data->preload();
|
|
|
|
// First see if we can find it. This is nifty so
|
|
// I'll explain it a bit more.
|
|
//
|
|
// The find function does a binary search thru the
|
|
// sorted item list.
|
|
//
|
|
// If found the index is the position of the item.
|
|
//
|
|
// If not found the index is the correct insertion
|
|
// position for adding the new item.
|
|
//
|
|
// So not only do we have a fast find which is worst
|
|
// case O(log n)... but we also have the proper insert
|
|
// position to maintain a sorted item list.
|
|
//
|
|
U32 index;
|
|
bool found = findIndexByKey( key, &index );
|
|
|
|
// If we didn't find one then insert it.
|
|
if ( !found )
|
|
mItems.insert( index );
|
|
|
|
// Update the item settings.
|
|
ForestItem &item = mItems[ index ];
|
|
item.setData( data );
|
|
item.setTransform( xfm, scale );
|
|
|
|
if ( !found )
|
|
item.setKey( key );
|
|
|
|
return item;
|
|
}
|
|
|
|
bool ForestCell::removeItem( ForestItemKey key, const Point3F &keyPos, bool deleteIfEmpty )
|
|
{
|
|
PROFILE_SCOPE( ForestCell_removeItem );
|
|
|
|
AssertFatal( key != 0, "ForestCell::removeItem() - Got null key!" );
|
|
|
|
// If this cell has no items then check the children.
|
|
if ( mItems.empty() )
|
|
{
|
|
// Let the child deal with it.
|
|
U32 index = _getSubCell( keyPos.x, keyPos.y );
|
|
if ( !mSubCells[index]->removeItem( key, keyPos, deleteIfEmpty ) )
|
|
{
|
|
// For debugging lets make sure we didn't pick the wrong subCell...
|
|
|
|
return false;
|
|
}
|
|
|
|
// If requested by the caller delete our empty subcells.
|
|
// Note that by deleting SubCell[0] we have become a leaf with no items
|
|
// and will return true to our parent's isEmpty test.
|
|
if ( deleteIfEmpty &&
|
|
mSubCells[0]->isEmpty() &&
|
|
mSubCells[1]->isEmpty() &&
|
|
mSubCells[2]->isEmpty() &&
|
|
mSubCells[3]->isEmpty() )
|
|
{
|
|
SAFE_DELETE( mSubCells[0] );
|
|
SAFE_DELETE( mSubCells[1] );
|
|
SAFE_DELETE( mSubCells[2] );
|
|
SAFE_DELETE( mSubCells[3] );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// First see if we can find it.
|
|
U32 index;
|
|
if ( !findIndexByKey( key, &index ) )
|
|
return false;
|
|
|
|
// Erase it.
|
|
mItems.erase( index );
|
|
}
|
|
|
|
// Do a full bounds update on the next request.
|
|
mIsDirty = true;
|
|
|
|
// PhysicsBody is now invalid and must be rebuilt later.
|
|
SAFE_DELETE( mPhysicsRep[0] );
|
|
SAFE_DELETE( mPhysicsRep[1] );
|
|
|
|
// Destroy batches so we recreate it on
|
|
// the next next render.
|
|
freeBatches();
|
|
|
|
return true;
|
|
}
|
|
|
|
void ForestCell::getItems( Vector<ForestItem> *outItems ) const
|
|
{
|
|
Vector<const ForestCell*> stack;
|
|
stack.push_back( this );
|
|
|
|
// Now loop till we run out of cells.
|
|
while ( !stack.empty() )
|
|
{
|
|
// Pop off the next cell.
|
|
const ForestCell *cell = stack.last();
|
|
stack.pop_back();
|
|
|
|
// Recurse thru non-leaf cells.
|
|
if ( !cell->isLeaf() )
|
|
{
|
|
stack.merge( cell->mSubCells, 4 );
|
|
continue;
|
|
}
|
|
|
|
// Get the items.
|
|
outItems->merge( cell->getItems() );
|
|
}
|
|
}
|
|
|
|
void ForestCell::buildPhysicsRep( Forest *forest )
|
|
{
|
|
AssertFatal( isLeaf(), "ForestCell::buildPhysicsRep() - This shouldn't be called on non-leaf cells!" );
|
|
|
|
bool isServer = forest->isServerObject();
|
|
|
|
// Already has a PhysicsBody, if it needed to be rebuilt it would
|
|
// already be null.
|
|
if ( mPhysicsRep[ isServer ] )
|
|
return;
|
|
|
|
if ( !PHYSICSMGR )
|
|
return;
|
|
|
|
PhysicsCollision *colShape = NULL;
|
|
|
|
// If we can steal the collision shape from the server-side cell
|
|
// then do so as it saves us alot of cpu time and memory.
|
|
if ( mPhysicsRep[ 1 ] )
|
|
{
|
|
colShape = mPhysicsRep[ 1 ]->getColShape();
|
|
}
|
|
else
|
|
{
|
|
// We must pass a sphere to buildPolyList but it is not used.
|
|
const static SphereF dummySphere( Point3F::Zero, 0 );
|
|
|
|
// Step thru them and build collision data.
|
|
ForestItemVector::iterator itemItr = mItems.begin();
|
|
ConcretePolyList polyList;
|
|
for ( ; itemItr != mItems.end(); itemItr++ )
|
|
{
|
|
const ForestItem &item = *itemItr;
|
|
const ForestItemData *itemData = item.getData();
|
|
|
|
// If not collidable don't need to build anything.
|
|
if ( !itemData->mCollidable )
|
|
continue;
|
|
|
|
// TODO: When we add breakable tree support this is where
|
|
// we would need to store their collision data seperately.
|
|
|
|
item.buildPolyList( &polyList, item.getWorldBox(), dummySphere );
|
|
|
|
// TODO: Need to support multiple collision shapes
|
|
// for really big forests at some point in the future.
|
|
}
|
|
|
|
if ( !polyList.isEmpty() )
|
|
{
|
|
colShape = PHYSICSMGR->createCollision();
|
|
if ( !colShape->addTriangleMesh( polyList.mVertexList.address(),
|
|
polyList.mVertexList.size(),
|
|
polyList.mIndexList.address(),
|
|
polyList.mIndexList.size() / 3,
|
|
MatrixF::Identity ) )
|
|
{
|
|
SAFE_DELETE( colShape );
|
|
}
|
|
}
|
|
}
|
|
|
|
// We might not have any trees.
|
|
if ( !colShape )
|
|
return;
|
|
|
|
PhysicsWorld *world = PHYSICSMGR->getWorld( isServer ? "server" : "client" );
|
|
mPhysicsRep[ isServer ] = PHYSICSMGR->createBody();
|
|
mPhysicsRep[ isServer ]->init( colShape, 0, 0, forest, world );
|
|
}
|
|
|
|
void ForestCell::clearPhysicsRep( Forest *forest )
|
|
{
|
|
bool isServer = forest->isServerObject();
|
|
|
|
SAFE_DELETE( mPhysicsRep[ isServer ] );
|
|
} |