Torque3D/Engine/source/util/scopeTracker.h
2018-03-13 21:27:31 -05:00

874 lines
27 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.
//-----------------------------------------------------------------------------
#ifndef _SCOPETRACKER_H_
#define _SCOPETRACKER_H_
#ifndef _TYPETRAITS_H_
#include "platform/typetraits.h"
#endif
#ifndef _BITSET_H_
#include "core/bitSet.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _CONSOLE_H_
#include "console/console.h"
#endif
#ifndef _PROFILER_H_
#include "platform/profiler.h"
#endif
//#define DEBUG_SPEW
/// @file
/// A mechanism for continuous tracking of point/box intersections.
/// Base class for objects registered with a ScopeTracker.
template< S32 NUM_DIMENSIONS >
class ScopeTrackerObject
{
public:
typedef void Parent;
/// TrackingNodes are used to track object bounds along individual world axes.
class TrackingNode
{
public:
typedef void Parent;
enum EFlags
{
FLAG_Min = BIT( 0 ),
FLAG_Max = BIT( 1 ),
FLAG_Reference = BIT( 2 ),
};
///
BitSet32 mFlags;
///
TrackingNode* mOpposite;
/// Distance along axis.
F32 mPosition;
/// The object being tracked by this node or NULL.
ScopeTrackerObject* mObject;
/// Next node on axis tracking chain.
TrackingNode* mNext;
/// Previous node on axis tracking chain.
TrackingNode* mPrev;
///
TrackingNode()
: mOpposite( NULL ), mPosition( 0.0f ), mObject( NULL ), mNext( NULL ), mPrev( NULL ) {}
/// Return the object to which this tracking node belongs.
ScopeTrackerObject* getObject() const { return mObject; }
///
TrackingNode* getOpposite() const { return mOpposite; }
///
F32 getPosition() const { return mPosition; }
///
void setPosition( F32 value ) { mPosition = value; }
///
TrackingNode* getNext() const { return mNext; }
///
void setNext( TrackingNode* node ) { mNext = node; }
///
TrackingNode* getPrev() const { return mPrev; }
///
void setPrev( TrackingNode* node ) { mPrev = node; }
/// Return true if this is left/lower bound node of an object.
bool isMin() const { return mFlags.test( FLAG_Min ); }
/// Return true if this is the right/upper bound node of an object.
bool isMax() const { return mFlags.test( FLAG_Max ); }
/// Return true if this is the reference center tracking node. There will only
/// ever be one such node on each tracking list.
bool isReference() const { return mFlags.test( FLAG_Reference ); }
};
enum
{
AllInScope = ( 0x01010101 >> ( ( 4 - NUM_DIMENSIONS ) * 8 ) )
};
protected:
///
union
{
U8 mBytes[ 4 ];
U32 mDWord;
} mScopeMask;
///
TrackingNode mTrackingNodes[ NUM_DIMENSIONS ][ 2 ];
public:
///
ScopeTrackerObject( U32 flags = 0 )
{
clearScopeMask();
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
{
TrackingNode* minNode = getMinTrackingNode( n );
TrackingNode* maxNode = getMaxTrackingNode( n );
minNode->mFlags = flags;
maxNode->mFlags = flags;
minNode->mObject = this;
maxNode->mObject = this;
minNode->mOpposite = maxNode;
maxNode->mOpposite = minNode;
minNode->mFlags.set( TrackingNode::FLAG_Min );
maxNode->mFlags.set( TrackingNode::FLAG_Max );
}
}
/// Return true if the object is currently being tracked.
bool isRegistered() const
{
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
if( getMinTrackingNode( n )->getNext() != NULL )
return true;
return false;
}
/// Return true if the reference center lies within the object bound's on all axes.
bool isInScope() const { return ( mScopeMask.mDWord == AllInScope ); }
///
bool isInScope( U32 dimension ) const { return mScopeMask.mBytes[ dimension ]; }
///
void setInScope( U32 dimension, bool state ) { mScopeMask.mBytes[ dimension ] = ( state ? 1 : 0 ); }
///
void clearScopeMask() { mScopeMask.mDWord = 0; }
///
TrackingNode* getMinTrackingNode( U32 dimension ) { return &mTrackingNodes[ dimension ][ 0 ]; }
const TrackingNode* getMinTrackingNode( U32 dimension ) const { return &mTrackingNodes[ dimension ][ 0 ]; }
///
TrackingNode* getMaxTrackingNode( U32 dimension ) { return &mTrackingNodes[ dimension ][ 1 ]; }
const TrackingNode* getMaxTrackingNode( U32 dimension ) const { return &mTrackingNodes[ dimension ][ 1 ]; }
/// @name Implementor Interface
///
/// The following methods must be implemented by the client. They are defined here
/// just for reference. If you don't override them, you'll get link errors.
///
/// @{
/// Return the position of the object in world-space.
void getPosition( F32 pos[ NUM_DIMENSIONS ] ) const;
/// If this object is the reference object, this method should return the world-space pivot
/// point in the object that will be the world reference center.
void getReferenceCenter( F32 pos[ NUM_DIMENSIONS ] ) const;
/// Return the object's bounding box in world-space.
void getBounds( F32 minBounds[ NUM_DIMENSIONS ], F32 maxBounds[ NUM_DIMENSIONS ] ) const;
///
String describeSelf() const;
/// @}
};
/// Helper class to track the position of a point in N-dimensional space relative to a collection
/// of N-dimensional volumes.
///
/// This class works by breaking the N-dimensional case down into N one-dimensional cases. By tracking
/// objects independently along each of their axes, intersection testing becomes a trivial matter of
/// doing point-on-line tests. The line segments can be conveniently represented as ordered linked
/// lists along which the reference point is being moved.
///
/// To determine N-dimensional containment of the reference point, the result of each of the one-dimensional
/// point-on-line tests simply has to be combined. If the point lies on each of N 1D segments of a
/// given volume, then the point is fully contained in the volume.
///
/// This class may be used in places where otherwise a spatial subdivision scheme would be necessary in
/// order to limit the number of containment tests triggered by each movement of the reference point.
/// Once the tracker has been set up, each position change of an object or the reference center will result
/// in a usually small number of incremental list updates.
///
/// Another advantage is that this class makes it easy to reduce 3D tracking to 2D tracking if tracking on
/// the height axis isn't important.
///
/// The following interface must be implemented by the given "Object" type:
///
/// @code
/// struct Object : public ScopeTrackerObject< NUM_DIMENSIONS >
/// {
/// /// Return the position of the object in world-space.
/// void getPosition( F32 pos[ NUM_DIMENSIONS ] ) const;
///
/// /// If this object is the reference object, this method should return the world-space pivot
/// /// point in the object that will be the world reference center.
/// void getReferenceCenter( F32 pos[ NUM_DIMENSIONS ] ) const;
///
/// /// Return the object's bounding box in world-space.
/// void getBounds( F32 minBounds[ NUM_DIMENSIONS ], F32 maxBounds[ NUM_DIMENSIONS ] ) const;
/// };
/// @endcode
///
/// Terminology:
///
/// - "In Scope": A volume is in scope if it fully contains the reference center.
/// - "Reference Object": Object that is the designated center of the world.
///
/// @param NUM_DIMENSIONS Number of dimensions to track; must be <=4.
/// @param Object Value type for objects tracked by the ScopeTracker. Must have pointer behavior.
template< S32 NUM_DIMENSIONS, typename Object >
class ScopeTracker
{
public:
typedef void Parent;
typedef typename TypeTraits< Object >::BaseType ObjectType;
typedef typename ObjectType::TrackingNode NodeType;
protected:
enum
{
MIN = 0,
MAX = 1
};
/// The reference object. This is the center relative to which all
/// tracking occurs. Any other object is in scope when it contains the
/// reference object.
Object mReferenceObject;
///
NodeType* mTrackingList[ NUM_DIMENSIONS ][ 2 ];
///
NodeType mBoundaryNodes[ NUM_DIMENSIONS ][ 2 ];
///
Vector< Object > mPotentialScopeInObjects;
/// @name Scoping
/// @{
virtual void _onScopeIn( Object object ) {}
virtual void _onScopeOut( Object object ) {}
/// Set the scoping state of the given object.
void _setScope( Object object );
/// @}
/// @name Tracking
/// @{
///
void _insertTrackingNode( U32 dimension, NodeType* node );
///
void _removeTrackingNode( U32 dimension, NodeType* node );
///
void _moveTrackingNode( U32 dimension, NodeType* node, F32 newPos );
///
void _initTracking();
///
void _uninitTracking();
/// @}
public:
///
ScopeTracker();
/// Add a volume object to the world.
void registerObject( Object object );
/// Remove a volume object from the world.
void unregisterObject( Object object );
/// Update the position of the object in the world.
void updateObject( Object object );
///
Object getReferenceObject() const { return mReferenceObject; }
///
///
/// @note Switching reference centers is potentially costly.
void setReferenceObject( Object object );
///
void debugDump();
};
//-----------------------------------------------------------------------------
template< S32 NUM_DIMENSIONS, class Object >
ScopeTracker< NUM_DIMENSIONS, Object >::ScopeTracker()
: mReferenceObject( NULL )
{
VECTOR_SET_ASSOCIATION( mPotentialScopeInObjects );
// Initialize the tracking lists. Put the boundary
// nodes in place that will always be the heads and tails
// of each list.
dMemset( mTrackingList, 0, sizeof( mTrackingList ) );
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
{
mBoundaryNodes[ n ][ MIN ].setPosition( TypeTraits< F32 >::MIN );
mBoundaryNodes[ n ][ MAX ].setPosition( TypeTraits< F32 >::MAX );
mBoundaryNodes[ n ][ MIN ].setNext( &mBoundaryNodes[ n ][ MAX ] );
mBoundaryNodes[ n ][ MAX ].setPrev( &mBoundaryNodes[ n ][ MIN ] );
mBoundaryNodes[ n ][ MIN ].mOpposite = &mBoundaryNodes[ n ][ MAX ];
mBoundaryNodes[ n ][ MAX ].mOpposite = &mBoundaryNodes[ n ][ MIN ];
mTrackingList[ n ][ MIN ] = &mBoundaryNodes[ n ][ MIN ];
mTrackingList[ n ][ MAX ] = &mBoundaryNodes[ n ][ MAX ];
}
}
//-----------------------------------------------------------------------------
template< S32 NUM_DIMENSIONS, class Object >
void ScopeTracker< NUM_DIMENSIONS, Object >::setReferenceObject( Object object )
{
AssertFatal( !object || !Deref( object ).isRegistered(),
"ScopeTracker::setReferenceObject - reference object must not be volume object" );
if( mReferenceObject == object )
return;
// If object is invalid, remove the reference center
// tracking.
if( !object )
{
// Transition all objects to out-of-scope and
// deactivate tracking.
_uninitTracking();
mReferenceObject = object;
return;
}
if( mReferenceObject )
{
//RDFIXME: this is very disruptive
// We have an existing reference object so we need to update
// the scoping to match it. Brute-force this for now.
_uninitTracking();
mReferenceObject = object;
_initTracking();
}
else
{
// No reference object yet.
mReferenceObject = object;
_initTracking();
}
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[ScopeTracker] Reference object is now 0x%x", object );
#endif
}
//-----------------------------------------------------------------------------
template< S32 NUM_DIMENSIONS, class Object >
void ScopeTracker< NUM_DIMENSIONS, Object >::registerObject( Object object )
{
PROFILE_SCOPE( ScopeTracker_registerObject );
// Get the object bounds.
F32 minBounds[ NUM_DIMENSIONS ];
F32 maxBounds[ NUM_DIMENSIONS ];
Deref( object ).getBounds( minBounds, maxBounds );
// Insert the object's tracking nodes.
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
{
NodeType* minNode = Deref( object ).getMinTrackingNode( n );
NodeType* maxNode = Deref( object ).getMaxTrackingNode( n );
minNode->setPosition( minBounds[ n ] );
maxNode->setPosition( maxBounds[ n ] );
// Insert max before min so that max always comes out
// to the right of min.
_insertTrackingNode( n, maxNode );
_insertTrackingNode( n, minNode );
}
// Set the scoping state of the object.
_setScope( object );
}
//-----------------------------------------------------------------------------
template< S32 NUM_DIMENSIONS, class Object >
void ScopeTracker< NUM_DIMENSIONS, Object >::unregisterObject( Object object )
{
PROFILE_SCOPE( ScopeTracker_unregisterObject );
if( !Deref( object ).isRegistered() )
return;
// Clear its scoping state.
if( Deref( object ).isInScope() )
_onScopeOut( object );
Deref( object ).clearScopeMask();
// Remove the tracking state.
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
{
_removeTrackingNode( n, Deref( object ).getMinTrackingNode( n ) );
_removeTrackingNode( n, Deref( object ).getMaxTrackingNode( n ) );
}
}
//-----------------------------------------------------------------------------
template< S32 NUM_DIMENSIONS, class Object >
void ScopeTracker< NUM_DIMENSIONS, Object >::updateObject( Object object )
{
PROFILE_SCOPE( ScopeTracker_updateObject );
if( object == mReferenceObject )
{
// Get the reference center position.
F32 position[ NUM_DIMENSIONS ];
Deref( mReferenceObject ).getReferenceCenter( position );
// Move the reference tracking node.
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
_moveTrackingNode( n, Deref( mReferenceObject ).getMinTrackingNode( n ), position[ n ] );
// Flush the potential-scope-in list.
while( !mPotentialScopeInObjects.empty() )
{
Object obj = mPotentialScopeInObjects.last();
mPotentialScopeInObjects.decrement();
if( Deref(obj).isInScope() )
_onScopeIn(obj);
}
}
else
{
// Get the object bounds.
F32 minBounds[ NUM_DIMENSIONS ];
F32 maxBounds[ NUM_DIMENSIONS ];
Deref( object ).getBounds( minBounds, maxBounds );
// Move the object's tracking nodes.
bool wasInScope = Deref( object ).isInScope();
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
{
NodeType* minNode = Deref( object ).getMinTrackingNode( n );
NodeType* maxNode = Deref( object ).getMaxTrackingNode( n );
_moveTrackingNode( n, minNode, minBounds[ n ] );
_moveTrackingNode( n, maxNode, maxBounds[ n ] );
}
// Rescope the object, if necessary.
if( wasInScope && !Deref( object ).isInScope() )
_onScopeOut( object );
else if( !wasInScope && Deref( object ).isInScope() )
_onScopeIn( object );
}
}
//-----------------------------------------------------------------------------
template< S32 NUM_DIMENSIONS, class Object >
void ScopeTracker< NUM_DIMENSIONS, Object >::_insertTrackingNode( U32 dimension, NodeType* node )
{
//RDTODO: substitute brute-force search with some smarter insertion algorithm
// (at least dynamically decide on direction for search)
F32 pos = node->getPosition();
NodeType* current = mTrackingList[ dimension ][ MIN ]->getNext();
NodeType* prev = mTrackingList[ dimension ][ MIN ];
while( current->getPosition() < pos )
{
prev = current;
current = current->getNext();
}
prev->setNext( node );
current->setPrev( node );
node->setPrev( prev );
node->setNext( current );
}
//-----------------------------------------------------------------------------
template< S32 NUM_DIMENSIONS, class Object >
void ScopeTracker< NUM_DIMENSIONS, Object >::_removeTrackingNode( U32 dimension, NodeType* node )
{
NodeType* next = node->getNext();
NodeType* prev = node->getPrev();
AssertFatal( next != NULL, "ScopeTracker::_insertTrackingNode - invalid list state (no next node)!" );
AssertFatal( prev != NULL, "ScopeTracker::_insertTrackingNode - invalid list state (no prev node)!" );
next->setPrev( prev );
prev->setNext( next );
node->setNext( NULL );
node->setPrev( NULL );
}
//-----------------------------------------------------------------------------
template< S32 NUM_DIMENSIONS, class Object >
void ScopeTracker< NUM_DIMENSIONS, Object >::_moveTrackingNode( U32 dimension, NodeType* node, F32 newPosition )
{
PROFILE_SCOPE( ScopeTracker_moveTrackingNode );
AssertFatal( TypeTraits< F32 >::MIN <= newPosition && newPosition <= TypeTraits< F32 >::MAX, "Invalid float in object coordinate!" );
enum EDirection
{
DIRECTION_Up,
DIRECTION_Down
};
// Determine in which direction we are sliding the node.
EDirection direction;
if( newPosition < node->getPosition() )
{
direction = DIRECTION_Down;
if( node->getPrev()->getPosition() <= newPosition )
{
node->setPosition( newPosition );
return; // Nothing to do.
}
}
else if( newPosition > node->getPosition() )
{
direction = DIRECTION_Up;
if( node->getNext()->getPosition() >= newPosition )
{
node->setPosition( newPosition );
return; // Nothing to do.
}
}
else
return; // Nothing to to.
const bool isReferenceNode = node->isReference();
// Unlink the node.
NodeType* next = node->getNext();
NodeType* prev = node->getPrev();
next->setPrev( prev );
prev->setNext( next );
// Iterate through to the node's new position.
while( ( direction == DIRECTION_Up && next->getPosition() < newPosition )
|| ( direction == DIRECTION_Down && prev->getPosition() > newPosition ) )
{
NodeType* current = 0;
switch( direction )
{
case DIRECTION_Up: current = next; break;
case DIRECTION_Down: current = prev; break;
}
if( isReferenceNode )
{
Object object = ( Object ) current->getObject();
if( ( direction == DIRECTION_Up && current->isMin() )
|| ( direction == DIRECTION_Down && current->isMax() ) )
{
Deref( object ).setInScope( dimension, true );
mPotentialScopeInObjects.push_back( object );
}
else
{
const bool wasInScope = Deref( object ).isInScope();
Deref( object ).setInScope( dimension, false );
if( wasInScope )
_onScopeOut( object );
}
}
else
{
if( current->isReference() )
{
Object object = ( Object ) node->getObject();
if( ( direction == DIRECTION_Up && node->isMin() )
|| ( direction == DIRECTION_Down && node->isMax() ) )
Deref( object ).setInScope( dimension, false );
else
Deref( object ).setInScope( dimension, true );
}
}
switch( direction )
{
case DIRECTION_Down:
next = current;
prev = current->getPrev();
break;
case DIRECTION_Up:
prev = current;
next = current->getNext();
break;
}
}
// Relink the node.
prev->setNext( node );
next->setPrev( node );
node->setPrev( prev );
node->setNext( next );
node->setPosition( newPosition );
}
//-----------------------------------------------------------------------------
template< S32 NUM_DIMENSIONS, class Object >
void ScopeTracker< NUM_DIMENSIONS, Object >::_setScope( Object object )
{
// If there's no reference object, all objects are out of scope.
if( !mReferenceObject || object == mReferenceObject )
{
Deref( object ).clearScopeMask();
return;
}
const bool wasInScope = Deref( object ).isInScope();
// Set the scoping state on each axis.
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
{
const F32 referencePos = Deref( mReferenceObject ).getMinTrackingNode( n )->getPosition();
const F32 objectMin = Deref( object ).getMinTrackingNode( n )->getPosition();
const F32 objectMax = Deref( object ).getMaxTrackingNode( n )->getPosition();
bool isInScope = referencePos >= objectMin
&& referencePos <= objectMax;
Deref( object ).setInScope( n, isInScope );
}
// Scope in/out if the scoping state has changed.
if( Deref( object ).isInScope() )
{
if( !wasInScope )
_onScopeIn( object );
}
else
{
if( wasInScope )
_onScopeOut( object );
}
}
//-----------------------------------------------------------------------------
template< S32 NUM_DIMENSIONS, class Object >
void ScopeTracker< NUM_DIMENSIONS, Object >::_initTracking()
{
PROFILE_SCOPE( ScopeTracker_initTracking );
AssertFatal( bool( getReferenceObject() ),
"ScopeTracker::_initTracking - can only be called with a valid reference object" );
// Put a single tracking node onto each of the lists for
// the reference object center.
F32 position[ NUM_DIMENSIONS ];
Deref( mReferenceObject ).getReferenceCenter( position );
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
{
AssertFatal( TypeTraits< F32 >::MIN <= position[ n ] && position[ n ] <= TypeTraits< F32 >::MAX, "Invalid float in object coordinate!" );
NodeType* node = Deref( mReferenceObject ).getMinTrackingNode( n );
node->mFlags.set( NodeType::FLAG_Reference );
node->setPosition( position[ n ] );
_insertTrackingNode( n, node );
}
// Update the surroundings of the reference object
// in the tracking lists for each dimension.
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
{
//TODO: this could be optimized by dynamically determining whether to walk upwards
// or downwards depending on which span has fewer nodes; finding that out is not immediately
// obvious, though
// Walk from the left bound node upwards until we reach the
// reference object's marker. Everything that has its max node
// past the reference object is in scope.
F32 referencePos = Deref( mReferenceObject ).getMinTrackingNode( n )->getPosition();
for( NodeType* node = mTrackingList[ n ][ 0 ]->getNext(); node->getPosition() < referencePos; node = node->getNext() )
if( !node->isMax() && node->getOpposite()->getPosition() > referencePos )
{
node->getObject()->setInScope( n, true );
// If this is the last dimension we're working on and
// the current object is in-scope on all dimension,
// promote to in-scope status.
if( n == ( NUM_DIMENSIONS - 1 ) && node->getObject()->isInScope() )
_onScopeIn( ( Object ) node->getObject() );
}
}
}
//-----------------------------------------------------------------------------
template< S32 NUM_DIMENSIONS, class Object >
void ScopeTracker< NUM_DIMENSIONS, Object >::_uninitTracking()
{
PROFILE_SCOPE( ScopeTracker_uninitTracking );
AssertFatal( bool( getReferenceObject() ),
"ScopeTracker::_uninitTracking - can only be called with a valid reference object" );
// Put all objects currently in scope, out of scope.
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
{
U32 referencePos = Deref( mReferenceObject ).getMinTrackingNode( n )->getPosition();
for( NodeType* node = mTrackingList[ n ][ 0 ]->getNext(); node->getPosition() < referencePos; node = node->getNext() )
{
if( node->getObject()->isInScope() )
_onScopeOut( ( Object ) node->getObject() );
node->getObject()->clearScopeMask();
}
}
// Remove the reference object's tracking nodes.
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
_removeTrackingNode( n, Deref( mReferenceObject ).getMinTrackingNode( n ) );
}
//-----------------------------------------------------------------------------
template< S32 NUM_DIMENSIONS, class Object >
void ScopeTracker< NUM_DIMENSIONS, Object >::debugDump()
{
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
{
Con::printf( "Dimension %i", n );
Con::printf( "----------------" );
for( NodeType* node = mTrackingList[ n ][ 0 ]; node != NULL; node = node->getNext() )
{
String desc;
if( node->getObject() )
{
Object object = ( Object ) node->getObject();
desc = Deref( object ).describeSelf();
}
Con::printf( "pos=%f, type=%s, scope=%s, object=%s",
node->getPosition(),
node->isReference() ? "reference" : node->isMin() ? "min" : "max",
node->getObject() ? node->getObject()->isInScope( n ) ? "1" : "0" : "0",
desc.c_str() );
}
Con::printf( "" );
}
}
#endif // !_SCOPETRACKER_H_