mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 04:34:48 +00:00
505 lines
14 KiB
C++
505 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;
|
||
|
|
|
||
|
|
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 ] );
|
||
|
|
}
|