You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
348 lines
10 KiB
348 lines
10 KiB
//========= Copyright © Valve Corporation, All rights reserved. ============//
|
|
#include "feagglomerator.h"
|
|
#include "bitvec.h"
|
|
|
|
CFeAgglomerator::CFeAgglomerator( uint nReserveNodes )
|
|
{
|
|
m_Clusters.EnsureCapacity( nReserveNodes * 2 );
|
|
m_Clusters.SetCount( nReserveNodes );
|
|
m_Clusters.FillWithValue( NULL ); // client needs to set all the nodes
|
|
/*
|
|
for ( uint i = 0; i < nReserveNodes; ++i )
|
|
{
|
|
m_Clusters[ i ] = new CCluster;
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
CFeAgglomerator::~CFeAgglomerator()
|
|
{
|
|
m_Clusters.PurgeAndDeleteElements();
|
|
}
|
|
|
|
|
|
|
|
CFeAgglomerator::CCluster::CCluster( int nIndex, int nChildLeafs ) :m_nParent( -1 ), m_nIndex( nIndex ), m_nChildLeafs( nChildLeafs )
|
|
{
|
|
m_nChild[ 0 ] = -1;
|
|
m_nChild[ 1 ] = -1;
|
|
}
|
|
|
|
|
|
bool CFeAgglomerator::CCluster::HasLinks()const
|
|
{
|
|
return m_Links.Count() > 0;
|
|
}
|
|
|
|
float CFeAgglomerator::CCluster::GetBestCost()const
|
|
{
|
|
if ( HasLinks() )
|
|
{
|
|
return m_Links.ElementAtHead().m_flCost;
|
|
}
|
|
else
|
|
{
|
|
return FLT_MAX;
|
|
}
|
|
}
|
|
|
|
void CFeAgglomerator::CCluster::RemoveLink( CCluster *pOtherCluster )
|
|
{
|
|
for ( int i = 0; i < m_Links.Count(); ++i )
|
|
{
|
|
if ( m_Links.Element( i ).m_pOtherCluster == pOtherCluster )
|
|
{
|
|
m_Links.RemoveAt( i );
|
|
return;
|
|
}
|
|
}
|
|
Assert( !"Not found" );
|
|
}
|
|
|
|
const CFeAgglomerator::CLink *CFeAgglomerator::CCluster::FindLink( CCluster *pOtherCluster )
|
|
{
|
|
for ( int i = 0; i < m_Links.Count(); ++i )
|
|
{
|
|
const CLink &link = m_Links.Element( i );
|
|
if ( link.m_pOtherCluster == pOtherCluster )
|
|
{
|
|
return &link;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// The cost of a node of the tree is the probability of its collision with another bounding box, times number of points to test.
|
|
// The probably of collision is the probability of their minkowski sum containing the origin, which is proportional to the volume of the minkowski sum.
|
|
// It boils down to guessing the size of the other bounding box. Having no better heuristc, we can just say it'll probably be a box of average size 12x12x12 or something
|
|
//
|
|
float CFeAgglomerator::CCluster::ComputeCost( const Vector &vSize, int nChildLeafs )
|
|
{
|
|
return ( vSize.x + 12 ) * ( vSize.y + 12 ) * ( vSize.z + 12 ) * nChildLeafs;
|
|
}
|
|
|
|
void CFeAgglomerator::CCluster::AddLink( CCluster *pOtherCluster )
|
|
{
|
|
#ifdef _DEBUG
|
|
for ( int i = 0; i < m_Links.Count(); i++ )
|
|
{
|
|
Assert( m_Links.Element( i ).m_pOtherCluster != pOtherCluster );
|
|
}
|
|
#endif
|
|
|
|
CLink link;
|
|
link.m_pOtherCluster = pOtherCluster;
|
|
//
|
|
// Note: GetSurfaceArea() is not a valid heuristic for cost here. E.g. 2 horizontal points will have 0 cost, which is clearly wrong
|
|
// (m_Aabb + pOtherCluster->m_Aabb ).GetSurfaceArea()
|
|
//
|
|
link.m_flCost = ComputeCost( ( m_Aabb + pOtherCluster->m_Aabb ).GetSize(), m_nChildLeafs + pOtherCluster->m_nChildLeafs );
|
|
m_Links.Insert( link );
|
|
}
|
|
|
|
|
|
|
|
void CFeAgglomerator::AddLink( CCluster* pCluster0, CCluster *pCluster1, ClustersPriorityQueue_t &queue )
|
|
{
|
|
float flBestDist0 = pCluster0->GetBestCost();
|
|
float flBestDist1 = pCluster1->GetBestCost();
|
|
|
|
pCluster0->AddLink( pCluster1 );
|
|
pCluster1->AddLink( pCluster0 );
|
|
|
|
float flNewBestDist0 = pCluster0->GetBestCost();
|
|
float flNewBestDist1 = pCluster1->GetBestCost();
|
|
|
|
if ( flNewBestDist0 != flBestDist0 )
|
|
{
|
|
queue.RevaluateElement( pCluster0->m_nPriorityIndex );
|
|
}
|
|
|
|
if ( flNewBestDist1 != flBestDist1 )
|
|
{
|
|
queue.RevaluateElement( pCluster1->m_nPriorityIndex );
|
|
}
|
|
}
|
|
|
|
// register a link between the nodes.
|
|
// Call this to register all links between all nodes before building agglomerated clusters
|
|
void CFeAgglomerator::LinkNodes( int nNode0, int nNode1 )
|
|
{
|
|
if ( nNode0 == nNode1 )
|
|
return;
|
|
CCluster* pCluster0 = m_Clusters[ nNode0 ];
|
|
CCluster *pCluster1 = m_Clusters[ nNode1 ];
|
|
if ( pCluster0->FindLink( pCluster1 ) )
|
|
{
|
|
AssertDbg( pCluster1->FindLink( pCluster0 ) );
|
|
}
|
|
else
|
|
{
|
|
// link not duplicated, create new link
|
|
pCluster0->AddLink( pCluster1 );
|
|
pCluster1->AddLink( pCluster0 );
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Build agglomerated clusters; cannot call this function twice, because it will try to agglomerate clusters at all levels (nodes to root) the 2nd time
|
|
//
|
|
void CFeAgglomerator::Build( bool bSingleRoot )
|
|
{
|
|
ClustersPriorityQueue_t queue;
|
|
for ( int i = 0; i < m_Clusters.Count(); ++i )
|
|
{
|
|
queue.Insert( m_Clusters[ i ] );
|
|
}
|
|
Process( queue );
|
|
// create links of all-with-all
|
|
// this will be ultra-slow if we have degenerate case (like 1000 small disconnected pieces of cloth)
|
|
if ( bSingleRoot && queue.Count() > 1 )
|
|
{
|
|
for ( int i = queue.Count(); i-- > 1; )
|
|
{
|
|
for ( int j = i; j-- > 0; )
|
|
{
|
|
queue.Element( i )->AddLink( queue.Element( j ) );
|
|
queue.Element( j )->AddLink( queue.Element( i ) );
|
|
}
|
|
}
|
|
// the old queue order is destroyed; create a new queue (we could re-heapify the old queue
|
|
ClustersPriorityQueue_t queue2;
|
|
for ( int i = queue.Count(); i-- > 0; )
|
|
{
|
|
queue2.Insert( queue.Element( i ) );
|
|
}
|
|
|
|
Process( queue2 );
|
|
Assert( queue2.Count() == 1 );
|
|
}
|
|
}
|
|
|
|
|
|
void CFeAgglomerator::Validate( ClustersPriorityQueue_t *pQueue )
|
|
{
|
|
#ifdef _DEBUG
|
|
if ( pQueue )
|
|
{
|
|
Assert( pQueue->IsHeapified() );
|
|
CVarBitVec used( m_Clusters.Count() );
|
|
for ( int i = 0; i < pQueue->Count(); ++i )
|
|
{
|
|
int nCluster = pQueue->Element( i )->m_nIndex;
|
|
Assert( !used.IsBitSet( nCluster ) );
|
|
used.Set( nCluster );
|
|
}
|
|
for ( int i = 0; i < m_Clusters.Count(); ++i )
|
|
{
|
|
CCluster *pCluster = m_Clusters[ i ];
|
|
if ( !used.IsBitSet( i ) )
|
|
{
|
|
Assert( pCluster->m_Links.Count() == 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( int nIndex = 0; nIndex < m_Clusters.Count(); ++nIndex )
|
|
{
|
|
CCluster *pThisCluster = m_Clusters[ nIndex ];
|
|
Assert( pThisCluster->m_nIndex == nIndex );
|
|
if ( pThisCluster->m_nChild[ 0 ] < 0 )
|
|
{
|
|
Assert( pThisCluster->m_nChild[ 1 ] < 0 && pThisCluster->m_nChildLeafs == 1 );
|
|
}
|
|
else
|
|
{
|
|
Assert( m_Clusters[ pThisCluster->m_nChild[ 0 ] ]->m_nChildLeafs + m_Clusters[ pThisCluster->m_nChild[ 1 ] ]->m_nChildLeafs == pThisCluster->m_nChildLeafs );
|
|
}
|
|
|
|
ClusterLinkQueue_t &links = pThisCluster->m_Links;
|
|
Assert( links.IsHeapified() );
|
|
CVarBitVec used( m_Clusters.Count() );
|
|
for ( int i = 0; i < links.Count(); ++i )
|
|
{
|
|
CLink *pThisLink = &links.Element( i );
|
|
CCluster *pOtherCluster = pThisLink->m_pOtherCluster;
|
|
const CLink *pOtherLink = pOtherCluster->FindLink( pThisCluster );
|
|
Assert( pOtherLink );
|
|
Assert( pOtherLink->m_pOtherCluster == pThisCluster && pThisLink->m_flCost == pOtherLink->m_flCost ); // can't have a link with self & the cost of link should be the same from both sides
|
|
Assert( !used.IsBitSet( pOtherCluster->m_nIndex ) );// can't have 2 links to the same cluster
|
|
used.Set( pOtherCluster->m_nIndex );
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void CFeAgglomerator::Process( ClustersPriorityQueue_t &queue )
|
|
{
|
|
while ( queue.Count() > 0 && queue.ElementAtHead()->HasLinks() )
|
|
{
|
|
Validate( &queue );
|
|
|
|
// remove the clusters we're merging from priority queue
|
|
CCluster *pChild[ 2 ];
|
|
pChild[ 0 ] = queue.ElementAtHead();
|
|
pChild[ 1 ] = queue.ElementAtHead()->m_Links.ElementAtHead().m_pOtherCluster;
|
|
|
|
// remove the children from the queue
|
|
Assert( pChild[ 0 ]->m_nPriorityIndex == 0 );
|
|
queue.RemoveAtHead(); // removing pChild[0]
|
|
queue.RemoveAt( pChild[ 1 ]->m_nPriorityIndex );
|
|
|
|
pChild[ 0 ]->m_nPriorityIndex = -1;
|
|
pChild[ 1 ]->m_nPriorityIndex = -1;
|
|
|
|
// make the new cluster, link and compute its distances to nearest clusters
|
|
int nParentIndex = m_Clusters.AddToTail();
|
|
CCluster *pParent = new CCluster( nParentIndex, pChild[ 0 ]->m_nChildLeafs + pChild[ 1 ]->m_nChildLeafs );
|
|
m_Clusters[ nParentIndex ] = pParent ;
|
|
pParent->m_Aabb = pChild[ 0 ]->m_Aabb + pChild[ 1 ]->m_Aabb;
|
|
pParent->m_nChild[ 0 ] = pChild[ 0 ]->m_nIndex;
|
|
pParent->m_nChild[ 1 ] = pChild[ 1 ]->m_nIndex;
|
|
|
|
|
|
pChild[ 0 ]->m_nParent = nParentIndex;
|
|
pChild[ 1 ]->m_nParent = nParentIndex;
|
|
|
|
|
|
CUtlVectorFixedGrowable< CCluster*, 8 > reAdd;
|
|
CVarBitVec skipAddLink( m_Clusters.Count() );
|
|
// remove all links to the children, replace them with links to the parent
|
|
{
|
|
ClusterLinkQueue_t &links = pChild[ 0 ]->m_Links;
|
|
for ( int i = 0; i < links.Count(); ++i )
|
|
{
|
|
CCluster *pOther = links.Element( i ).m_pOtherCluster;
|
|
Assert( pOther != pChild[ 0 ] );
|
|
if ( pOther == pChild[ 1 ] )
|
|
continue; // just skip the connection to the other child
|
|
Assert( pOther->m_nPriorityIndex >= 0 );
|
|
// we see this cluster for the first time
|
|
float flOldDistance = pOther->GetBestCost();
|
|
pOther->RemoveLink( pChild[ 0 ] );
|
|
pOther->AddLink( pParent );
|
|
pParent->AddLink( pOther );
|
|
skipAddLink.Set( pOther->m_nIndex );
|
|
float flNewDistance = pOther->GetBestCost();
|
|
if ( flOldDistance != flNewDistance )
|
|
{
|
|
reAdd.AddToTail( pOther );
|
|
queue.RemoveAt( pOther->m_nPriorityIndex );
|
|
pOther->m_nPriorityIndex = -1;
|
|
}
|
|
}
|
|
links.Purge(); // the links don't matter any more, we can free the memory
|
|
}
|
|
{
|
|
ClusterLinkQueue_t &links = pChild[ 1 ]->m_Links;
|
|
for ( int i = 0; i < links.Count(); ++i )
|
|
{
|
|
CCluster *pOther = links.Element( i ).m_pOtherCluster;
|
|
Assert( pOther != pChild[ 1 ] );
|
|
if ( pOther == pChild[ 0 ] )
|
|
continue; // just skip the connection to the other child
|
|
if ( pOther->m_nPriorityIndex >= 0 )
|
|
{
|
|
// we see this cluster for the first time
|
|
float flOldDistance = pOther->GetBestCost();
|
|
pOther->RemoveLink( pChild[ 1 ] );
|
|
// if we saw this pOther already, and didn't remove it from the queue, then we marked it as added
|
|
if ( !skipAddLink.IsBitSet( pOther->m_nIndex ) )
|
|
{
|
|
pOther->AddLink( pParent );
|
|
pParent->AddLink( pOther );
|
|
}
|
|
float flNewDistance = pOther->GetBestCost();
|
|
if ( flOldDistance != flNewDistance )
|
|
{
|
|
queue.RevaluateElement( pOther->m_nPriorityIndex ); // no need to remove, this is the first and last edit of this other element
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we've seen this cluster before within this loop, just remove the link; we already added the new link to the new parent
|
|
pOther->RemoveLink( pChild[ 1 ] );
|
|
}
|
|
}
|
|
links.Purge(); // the links don't matter any more, we can free the memory
|
|
}
|
|
|
|
for ( int nCluster = 0; nCluster < reAdd.Count(); ++nCluster )
|
|
{
|
|
queue.Insert( reAdd[nCluster] );
|
|
}
|
|
queue.Insert( pParent );
|
|
|
|
Validate( &queue );
|
|
}
|
|
|
|
for ( int i = 0; i < queue.Count(); ++i )
|
|
{
|
|
Assert( !queue.Element( i )->HasLinks() );
|
|
}
|
|
}
|
|
|
|
|