source-engine/game/shared/collisionproperty.cpp
2023-10-03 17:23:56 +03:00

1446 lines
47 KiB
C++

//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//===========================================================================//
#include "cbase.h"
#include "collisionproperty.h"
#include "igamesystem.h"
#include "utlvector.h"
#include "tier0/threadtools.h"
#include "tier0/tslist.h"
#ifdef CLIENT_DLL
#include "c_baseentity.h"
#include "c_baseanimating.h"
#include "recvproxy.h"
#include "engine/ivdebugoverlay.h"
#else
#include "baseentity.h"
#include "baseanimating.h"
#include "sendproxy.h"
#include "hierarchy.h"
#endif
#include "predictable_entity.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// KD tree query callbacks
//-----------------------------------------------------------------------------
class CDirtySpatialPartitionEntityList : public CAutoGameSystem, public IPartitionQueryCallback
{
public:
CDirtySpatialPartitionEntityList( char const *name );
// Members of IGameSystem
virtual bool Init();
virtual void Shutdown();
virtual void LevelShutdownPostEntity();
// Members of IPartitionQueryCallback
virtual void OnPreQuery( SpatialPartitionListMask_t listMask );
virtual void OnPostQuery( SpatialPartitionListMask_t listMask );
void AddEntity( CBaseEntity *pEntity );
~CDirtySpatialPartitionEntityList();
void LockPartitionForRead()
{
int nThreadId = g_nThreadID;
if ( m_nReadLockCount[nThreadId] == 0 )
{
m_partitionMutex.LockForRead();
}
m_nReadLockCount[nThreadId]++;
}
void UnlockPartitionForRead()
{
int nThreadId = g_nThreadID;
m_nReadLockCount[nThreadId]--;
if ( m_nReadLockCount[nThreadId] == 0 )
{
m_partitionMutex.UnlockRead();
}
}
private:
CTSListWithFreeList<CBaseHandle> m_DirtyEntities;
CThreadSpinRWLock m_partitionMutex;
int m_partitionWriteId;
int m_nReadLockCount[MAX_THREADS_SUPPORTED];
};
//-----------------------------------------------------------------------------
// Singleton instance
//-----------------------------------------------------------------------------
static CDirtySpatialPartitionEntityList s_DirtyKDTree( "CDirtySpatialPartitionEntityList" );
//-----------------------------------------------------------------------------
// Force spatial partition updates (to avoid threading problems caused by lazy update)
//-----------------------------------------------------------------------------
void UpdateDirtySpatialPartitionEntities()
{
SpatialPartitionListMask_t listMask;
#ifdef CLIENT_DLL
listMask = PARTITION_CLIENT_GAME_EDICTS;
#else
listMask = PARTITION_SERVER_GAME_EDICTS;
#endif
s_DirtyKDTree.OnPreQuery( listMask );
s_DirtyKDTree.OnPostQuery( listMask );
}
//-----------------------------------------------------------------------------
// Purpose: Constructor.
//-----------------------------------------------------------------------------
CDirtySpatialPartitionEntityList::CDirtySpatialPartitionEntityList( char const *name ) : CAutoGameSystem( name )
{
m_DirtyEntities.Purge();
memset( m_nReadLockCount, 0, sizeof( m_nReadLockCount ) );
}
//-----------------------------------------------------------------------------
// Purpose: Deconstructor.
//-----------------------------------------------------------------------------
CDirtySpatialPartitionEntityList::~CDirtySpatialPartitionEntityList()
{
m_DirtyEntities.Purge();
}
//-----------------------------------------------------------------------------
// Initialization, shutdown
//-----------------------------------------------------------------------------
bool CDirtySpatialPartitionEntityList::Init()
{
partition->InstallQueryCallback( this );
return true;
}
void CDirtySpatialPartitionEntityList::Shutdown()
{
partition->RemoveQueryCallback( this );
}
//-----------------------------------------------------------------------------
// Makes sure all entries in the KD tree are in the correct position
//-----------------------------------------------------------------------------
void CDirtySpatialPartitionEntityList::AddEntity( CBaseEntity *pEntity )
{
m_DirtyEntities.PushItem( pEntity->GetRefEHandle() );
}
//-----------------------------------------------------------------------------
// Members of IGameSystem
//-----------------------------------------------------------------------------
void CDirtySpatialPartitionEntityList::LevelShutdownPostEntity()
{
m_DirtyEntities.RemoveAll();
}
//-----------------------------------------------------------------------------
// Makes sure all entries in the KD tree are in the correct position
//-----------------------------------------------------------------------------
void CDirtySpatialPartitionEntityList::OnPreQuery( SpatialPartitionListMask_t listMask )
{
#ifdef CLIENT_DLL
const int validMask = PARTITION_CLIENT_GAME_EDICTS;
#else
const int validMask = PARTITION_SERVER_GAME_EDICTS;
#endif
if ( !( listMask & validMask ) )
return;
int nThreadID = g_nThreadID;
if ( m_partitionWriteId != 0 && m_partitionWriteId == nThreadID + 1 )
return;
#ifdef CLIENT_DLL
// FIXME: This should really be an assertion... feh!
if ( !C_BaseEntity::IsAbsRecomputationsEnabled() )
{
LockPartitionForRead();
return;
}
#endif
// if you're holding a read lock, then these are entities that were still dirty after your trace started
// or became dirty due to some other thread or callback. Updating them may cause corruption further up the
// stack (e.g. partition iterator). Ignoring the state change should be safe since it happened after the
// trace was requested or was unable to be resolved in a previous attempt (still dirty).
if ( m_DirtyEntities.Count() && !m_nReadLockCount[nThreadID] )
{
CUtlVector< CBaseHandle > vecStillDirty;
m_partitionMutex.LockForWrite();
m_partitionWriteId = nThreadID + 1;
CTSListWithFreeList<CBaseHandle>::Node_t *pCurrent, *pNext;
while ( ( pCurrent = m_DirtyEntities.Detach() ) != NULL )
{
while ( pCurrent )
{
CBaseHandle handle = pCurrent->elem;
pNext = (CTSListWithFreeList<CBaseHandle>::Node_t *)pCurrent->Next;
m_DirtyEntities.FreeNode( pCurrent );
pCurrent = pNext;
#ifndef CLIENT_DLL
CBaseEntity *pEntity = gEntList.GetBaseEntity( handle );
#else
CBaseEntity *pEntity = cl_entitylist->GetBaseEntityFromHandle( handle );
#endif
if ( pEntity )
{
// If an entity is in the middle of bone setup, don't call UpdatePartition
// which can cause it to redo bone setup on the same frame causing a recursive
// call to bone setup.
if ( !pEntity->IsEFlagSet( EFL_SETTING_UP_BONES ) )
{
pEntity->CollisionProp()->UpdatePartition();
}
else
{
vecStillDirty.AddToTail( handle );
}
}
}
}
if ( vecStillDirty.Count() > 0 )
{
for ( int i = 0; i < vecStillDirty.Count(); i++ )
{
m_DirtyEntities.PushItem( vecStillDirty[i] );
}
}
m_partitionWriteId = 0;
m_partitionMutex.UnlockWrite();
}
LockPartitionForRead();
}
//-----------------------------------------------------------------------------
// Makes sure all entries in the KD tree are in the correct position
//-----------------------------------------------------------------------------
void CDirtySpatialPartitionEntityList::OnPostQuery( SpatialPartitionListMask_t listMask )
{
#ifdef CLIENT_DLL
if ( !( listMask & PARTITION_CLIENT_GAME_EDICTS ) )
return;
#else
if ( !( listMask & PARTITION_SERVER_GAME_EDICTS ) )
return;
#endif
if ( m_partitionWriteId != 0 )
return;
UnlockPartitionForRead();
}
//-----------------------------------------------------------------------------
// Save/load
//-----------------------------------------------------------------------------
#ifndef CLIENT_DLL
BEGIN_DATADESC_NO_BASE( CCollisionProperty )
// DEFINE_FIELD( m_pOuter, FIELD_CLASSPTR ),
DEFINE_GLOBAL_FIELD( m_vecMins, FIELD_VECTOR ),
DEFINE_GLOBAL_FIELD( m_vecMaxs, FIELD_VECTOR ),
DEFINE_KEYFIELD( m_nSolidType, FIELD_CHARACTER, "solid" ),
DEFINE_FIELD( m_usSolidFlags, FIELD_SHORT ),
DEFINE_FIELD( m_nSurroundType, FIELD_CHARACTER ),
DEFINE_FIELD( m_flRadius, FIELD_FLOAT ),
DEFINE_FIELD( m_triggerBloat, FIELD_CHARACTER ),
DEFINE_FIELD( m_vecSpecifiedSurroundingMins, FIELD_VECTOR ),
DEFINE_FIELD( m_vecSpecifiedSurroundingMaxs, FIELD_VECTOR ),
DEFINE_FIELD( m_vecSurroundingMins, FIELD_VECTOR ),
DEFINE_FIELD( m_vecSurroundingMaxs, FIELD_VECTOR ),
// DEFINE_FIELD( m_Partition, FIELD_SHORT ),
// DEFINE_PHYSPTR( m_pPhysicsObject ),
END_DATADESC()
#else
//-----------------------------------------------------------------------------
// Prediction
//-----------------------------------------------------------------------------
BEGIN_PREDICTION_DATA_NO_BASE( CCollisionProperty )
DEFINE_PRED_FIELD( m_vecMins, FIELD_VECTOR, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_vecMaxs, FIELD_VECTOR, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_nSolidType, FIELD_CHARACTER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_usSolidFlags, FIELD_SHORT, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_triggerBloat, FIELD_CHARACTER, FTYPEDESC_INSENDTABLE ),
END_PREDICTION_DATA()
#endif
//-----------------------------------------------------------------------------
// Networking
//-----------------------------------------------------------------------------
#ifdef CLIENT_DLL
static void RecvProxy_Solid( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
((CCollisionProperty*)pStruct)->SetSolid( (SolidType_t)pData->m_Value.m_Int );
}
static void RecvProxy_SolidFlags( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
((CCollisionProperty*)pStruct)->SetSolidFlags( pData->m_Value.m_Int );
}
static void RecvProxy_OBBMins( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
CCollisionProperty *pProp = ((CCollisionProperty*)pStruct);
Vector &vecMins = *((Vector*)pData->m_Value.m_Vector);
pProp->SetCollisionBounds( vecMins, pProp->OBBMaxs() );
}
static void RecvProxy_OBBMaxs( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
CCollisionProperty *pProp = ((CCollisionProperty*)pStruct);
Vector &vecMaxs = *((Vector*)pData->m_Value.m_Vector);
pProp->SetCollisionBounds( pProp->OBBMins(), vecMaxs );
}
static void RecvProxy_VectorDirtySurround( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
Vector &vecold = *((Vector*)pOut);
Vector vecnew( pData->m_Value.m_Vector[0], pData->m_Value.m_Vector[1], pData->m_Value.m_Vector[2] );
if ( vecold != vecnew )
{
vecold = vecnew;
((CCollisionProperty*)pStruct)->MarkSurroundingBoundsDirty();
}
}
static void RecvProxy_IntDirtySurround( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
if ( *((unsigned char*)pOut) != pData->m_Value.m_Int )
{
*((unsigned char*)pOut) = pData->m_Value.m_Int;
((CCollisionProperty*)pStruct)->MarkSurroundingBoundsDirty();
}
}
#else
static void SendProxy_Solid( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
{
pOut->m_Int = ((CCollisionProperty*)pStruct)->GetSolid();
}
static void SendProxy_SolidFlags( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
{
pOut->m_Int = ((CCollisionProperty*)pStruct)->GetSolidFlags();
}
#endif
BEGIN_NETWORK_TABLE_NOBASE( CCollisionProperty, DT_CollisionProperty )
#ifdef CLIENT_DLL
RecvPropVector( RECVINFO(m_vecMins), 0, RecvProxy_OBBMins ),
RecvPropVector( RECVINFO(m_vecMaxs), 0, RecvProxy_OBBMaxs ),
RecvPropInt( RECVINFO( m_nSolidType ), 0, RecvProxy_Solid ),
RecvPropInt( RECVINFO( m_usSolidFlags ), 0, RecvProxy_SolidFlags ),
RecvPropInt( RECVINFO(m_nSurroundType), 0, RecvProxy_IntDirtySurround ),
RecvPropInt( RECVINFO(m_triggerBloat), 0, RecvProxy_IntDirtySurround ),
RecvPropVector( RECVINFO(m_vecSpecifiedSurroundingMins), 0, RecvProxy_VectorDirtySurround ),
RecvPropVector( RECVINFO(m_vecSpecifiedSurroundingMaxs), 0, RecvProxy_VectorDirtySurround ),
#else
SendPropVector( SENDINFO(m_vecMins), 0, SPROP_NOSCALE),
SendPropVector( SENDINFO(m_vecMaxs), 0, SPROP_NOSCALE),
SendPropInt( SENDINFO( m_nSolidType ), 3, SPROP_UNSIGNED, SendProxy_Solid ),
SendPropInt( SENDINFO( m_usSolidFlags ), FSOLID_MAX_BITS, SPROP_UNSIGNED, SendProxy_SolidFlags ),
SendPropInt( SENDINFO( m_nSurroundType ), SURROUNDING_TYPE_BIT_COUNT, SPROP_UNSIGNED ),
SendPropInt( SENDINFO(m_triggerBloat), 0, SPROP_UNSIGNED),
SendPropVector( SENDINFO(m_vecSpecifiedSurroundingMins), 0, SPROP_NOSCALE),
SendPropVector( SENDINFO(m_vecSpecifiedSurroundingMaxs), 0, SPROP_NOSCALE),
#endif
END_NETWORK_TABLE()
//-----------------------------------------------------------------------------
// Constructor, destructor
//-----------------------------------------------------------------------------
CCollisionProperty::CCollisionProperty()
{
m_Partition = PARTITION_INVALID_HANDLE;
Init( NULL );
}
CCollisionProperty::~CCollisionProperty()
{
DestroyPartitionHandle();
}
//-----------------------------------------------------------------------------
// Initialization
//-----------------------------------------------------------------------------
void CCollisionProperty::Init( CBaseEntity *pEntity )
{
m_pOuter = pEntity;
m_vecMins.GetForModify().Init();
m_vecMaxs.GetForModify().Init();
m_flRadius = 0.0f;
m_triggerBloat = 0;
m_usSolidFlags = 0;
m_nSolidType = SOLID_NONE;
// NOTE: This replicates previous behavior; we may always want to use BEST_COLLISION_BOUNDS
m_nSurroundType = USE_OBB_COLLISION_BOUNDS;
m_vecSurroundingMins = vec3_origin;
m_vecSurroundingMaxs = vec3_origin;
m_vecSpecifiedSurroundingMins.GetForModify().Init();
m_vecSpecifiedSurroundingMaxs.GetForModify().Init();
}
//-----------------------------------------------------------------------------
// EntityHandle
//-----------------------------------------------------------------------------
IHandleEntity *CCollisionProperty::GetEntityHandle()
{
return m_pOuter;
}
//-----------------------------------------------------------------------------
// Collision group
//-----------------------------------------------------------------------------
int CCollisionProperty::GetCollisionGroup() const
{
return m_pOuter->GetCollisionGroup();
}
bool CCollisionProperty::ShouldTouchTrigger( int triggerSolidFlags ) const
{
// debris only touches certain triggers
if ( GetCollisionGroup() == COLLISION_GROUP_DEBRIS )
{
if ( triggerSolidFlags & FSOLID_TRIGGER_TOUCH_DEBRIS )
return true;
return false;
}
// triggers don't touch other triggers (might be solid to other ents as well as trigger)
if ( IsSolidFlagSet( FSOLID_TRIGGER ) )
return false;
return true;
}
const matrix3x4_t *CCollisionProperty::GetRootParentToWorldTransform() const
{
if ( IsSolidFlagSet( FSOLID_ROOT_PARENT_ALIGNED ) )
{
CBaseEntity *pEntity = m_pOuter->GetRootMoveParent();
Assert(pEntity);
if ( pEntity )
{
return &pEntity->CollisionProp()->CollisionToWorldTransform();
}
}
return NULL;
}
//-----------------------------------------------------------------------------
// IClientUnknown
//-----------------------------------------------------------------------------
IClientUnknown* CCollisionProperty::GetIClientUnknown()
{
#ifdef CLIENT_DLL
return m_pOuter->GetIClientUnknown();
#else
return NULL;
#endif
}
//-----------------------------------------------------------------------------
// Check for untouch
//-----------------------------------------------------------------------------
void CCollisionProperty::CheckForUntouch()
{
#ifndef CLIENT_DLL
if ( !IsSolid() && !IsSolidFlagSet(FSOLID_TRIGGER))
{
// If this ent's touch list isn't empty, it's transitioning to not solid
if ( m_pOuter->IsCurrentlyTouching() )
{
// mark ent so that at the end of frame it will check to
// see if it's no longer touching ents
m_pOuter->SetCheckUntouch( true );
}
}
#endif
}
//-----------------------------------------------------------------------------
// Sets the solid type
//-----------------------------------------------------------------------------
void CCollisionProperty::SetSolid( SolidType_t val )
{
if ( m_nSolidType == val )
return;
#ifndef CLIENT_DLL
bool bWasNotSolid = IsSolid();
#endif
MarkSurroundingBoundsDirty();
// OBB is not yet implemented
if ( val == SOLID_BSP )
{
if ( GetOuter()->GetMoveParent() )
{
if ( GetOuter()->GetRootMoveParent()->GetSolid() != SOLID_BSP )
{
// must be SOLID_VPHYSICS because parent might rotate
val = SOLID_VPHYSICS;
}
}
#ifndef CLIENT_DLL
// UNDONE: This should be fine in the client DLL too. Move GetAllChildren() into shared code.
// If the root of the hierarchy is SOLID_BSP, then assume that the designer
// wants the collisions to rotate with this hierarchy so that the player can
// move while riding the hierarchy.
if ( !GetOuter()->GetMoveParent() )
{
// NOTE: This assumes things don't change back from SOLID_BSP
// NOTE: This is 100% true for HL2 - need to support removing the flag to support changing from SOLID_BSP
CUtlVector<CBaseEntity *> list;
GetAllChildren( GetOuter(), list );
for ( int i = list.Count()-1; i>=0; --i )
{
list[i]->AddSolidFlags( FSOLID_ROOT_PARENT_ALIGNED );
}
}
#endif
}
m_nSolidType = val;
#ifndef CLIENT_DLL
m_pOuter->CollisionRulesChanged();
UpdateServerPartitionMask( );
if ( bWasNotSolid != IsSolid() )
{
CheckForUntouch();
}
#endif
}
SolidType_t CCollisionProperty::GetSolid() const
{
return (SolidType_t)m_nSolidType.Get();
}
//-----------------------------------------------------------------------------
// Sets the solid flags
//-----------------------------------------------------------------------------
void CCollisionProperty::SetSolidFlags( int flags )
{
int oldFlags = m_usSolidFlags;
m_usSolidFlags = (unsigned short)(flags & 0xFFFF);
if ( oldFlags == m_usSolidFlags )
return;
// These two flags, if changed, can produce different surrounding bounds
if ( (oldFlags & (FSOLID_FORCE_WORLD_ALIGNED | FSOLID_USE_TRIGGER_BOUNDS)) !=
(m_usSolidFlags & (FSOLID_FORCE_WORLD_ALIGNED | FSOLID_USE_TRIGGER_BOUNDS)) )
{
MarkSurroundingBoundsDirty();
}
if ( (oldFlags & (FSOLID_NOT_SOLID|FSOLID_TRIGGER)) != (m_usSolidFlags & (FSOLID_NOT_SOLID|FSOLID_TRIGGER)) )
{
m_pOuter->CollisionRulesChanged();
}
#ifndef CLIENT_DLL
if ( (oldFlags & (FSOLID_NOT_SOLID | FSOLID_TRIGGER)) != (m_usSolidFlags & (FSOLID_NOT_SOLID | FSOLID_TRIGGER)) )
{
UpdateServerPartitionMask( );
CheckForUntouch();
}
#endif
}
//-----------------------------------------------------------------------------
// Coordinate system of the collision model
//-----------------------------------------------------------------------------
const Vector& CCollisionProperty::GetCollisionOrigin() const
{
return m_pOuter->GetAbsOrigin();
}
const QAngle& CCollisionProperty::GetCollisionAngles() const
{
if ( IsBoundsDefinedInEntitySpace() )
{
return m_pOuter->GetAbsAngles();
}
return vec3_angle;
}
const matrix3x4_t& CCollisionProperty::CollisionToWorldTransform() const
{
static matrix3x4_t s_matTemp[4];
static int s_nIndex = 0;
matrix3x4_t &matResult = s_matTemp[s_nIndex];
s_nIndex = (s_nIndex+1) & 0x3;
if ( IsBoundsDefinedInEntitySpace() )
{
return m_pOuter->EntityToWorldTransform();
}
SetIdentityMatrix( matResult );
MatrixSetColumn( GetCollisionOrigin(), 3, matResult );
return matResult;
}
//-----------------------------------------------------------------------------
// Sets the collision bounds + the size
//-----------------------------------------------------------------------------
void CCollisionProperty::SetCollisionBounds( const Vector& mins, const Vector &maxs )
{
if ( (m_vecMins == mins) && (m_vecMaxs == maxs) )
return;
m_vecMins = mins;
m_vecMaxs = maxs;
//ASSERT_COORD( mins );
//ASSERT_COORD( maxs );
Vector vecSize;
VectorSubtract( maxs, mins, vecSize );
m_flRadius = vecSize.Length() * 0.5f;
MarkSurroundingBoundsDirty();
}
//-----------------------------------------------------------------------------
// Lazily calculates the 2D bounding radius. If we do this enough, we should
// calculate this in SetCollisionBounds above and cache the results in a data member!
//-----------------------------------------------------------------------------
float CCollisionProperty::BoundingRadius2D() const
{
Vector vecSize;
VectorSubtract( m_vecMaxs, m_vecMins, vecSize );
vecSize.z = 0;
return vecSize.Length() * 0.5f;
}
//-----------------------------------------------------------------------------
// Bounding representation (OBB)
//-----------------------------------------------------------------------------
const Vector& CCollisionProperty::OBBMins( ) const
{
return m_vecMins.Get();
}
const Vector& CCollisionProperty::OBBMaxs( ) const
{
return m_vecMaxs.Get();
}
//-----------------------------------------------------------------------------
// Special trigger representation (OBB)
//-----------------------------------------------------------------------------
void CCollisionProperty::WorldSpaceTriggerBounds( Vector *pVecWorldMins, Vector *pVecWorldMaxs ) const
{
WorldSpaceAABB( pVecWorldMins, pVecWorldMaxs );
if ( ( GetSolidFlags() & FSOLID_USE_TRIGGER_BOUNDS ) == 0 )
return;
// Don't bloat below, we don't want to trigger it with our heads
pVecWorldMins->x -= m_triggerBloat;
pVecWorldMins->y -= m_triggerBloat;
pVecWorldMaxs->x += m_triggerBloat;
pVecWorldMaxs->y += m_triggerBloat;
pVecWorldMaxs->z += (float)m_triggerBloat * 0.5f;
}
void CCollisionProperty::UseTriggerBounds( bool bEnable, float flBloat )
{
Assert( flBloat <= 127.0f );
m_triggerBloat = (char )flBloat;
if ( bEnable )
{
AddSolidFlags( FSOLID_USE_TRIGGER_BOUNDS );
Assert( flBloat > 0.0f );
}
else
{
RemoveSolidFlags( FSOLID_USE_TRIGGER_BOUNDS );
}
}
//-----------------------------------------------------------------------------
// Collision model (BSP)
//-----------------------------------------------------------------------------
int CCollisionProperty::GetCollisionModelIndex()
{
return m_pOuter->GetModelIndex();
}
const model_t* CCollisionProperty::GetCollisionModel()
{
return m_pOuter->GetModel();
}
//-----------------------------------------------------------------------------
// Collision methods implemented in the entity
// FIXME: This shouldn't happen there!!
//-----------------------------------------------------------------------------
bool CCollisionProperty::TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
{
return m_pOuter->TestCollision( ray, fContentsMask, tr );
}
bool CCollisionProperty::TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
{
return m_pOuter->TestHitboxes( ray, fContentsMask, tr );
}
//-----------------------------------------------------------------------------
// Computes a "normalized" point (range 0,0,0 - 1,1,1) in collision space
//-----------------------------------------------------------------------------
const Vector & CCollisionProperty::NormalizedToCollisionSpace( const Vector &in, Vector *pResult ) const
{
pResult->x = Lerp( in.x, m_vecMins.Get().x, m_vecMaxs.Get().x );
pResult->y = Lerp( in.y, m_vecMins.Get().y, m_vecMaxs.Get().y );
pResult->z = Lerp( in.z, m_vecMins.Get().z, m_vecMaxs.Get().z );
return *pResult;
}
//-----------------------------------------------------------------------------
// Transforms a point in collision space to normalized space
//-----------------------------------------------------------------------------
const Vector & CCollisionProperty::CollisionToNormalizedSpace( const Vector &in, Vector *pResult ) const
{
Vector vecSize = OBBSize( );
pResult->x = ( vecSize.x != 0.0f ) ? ( in.x - m_vecMins.Get().x ) / vecSize.x : 0.5f;
pResult->y = ( vecSize.y != 0.0f ) ? ( in.y - m_vecMins.Get().y ) / vecSize.y : 0.5f;
pResult->z = ( vecSize.z != 0.0f ) ? ( in.z - m_vecMins.Get().z ) / vecSize.z : 0.5f;
return *pResult;
}
//-----------------------------------------------------------------------------
// Computes a "normalized" point (range 0,0,0 - 1,1,1) in world space
//-----------------------------------------------------------------------------
const Vector & CCollisionProperty::NormalizedToWorldSpace( const Vector &in, Vector *pResult ) const
{
Vector vecCollisionSpace;
NormalizedToCollisionSpace( in, &vecCollisionSpace );
CollisionToWorldSpace( vecCollisionSpace, pResult );
return *pResult;
}
//-----------------------------------------------------------------------------
// Transforms a point in world space to normalized space
//-----------------------------------------------------------------------------
const Vector & CCollisionProperty::WorldToNormalizedSpace( const Vector &in, Vector *pResult ) const
{
Vector vecCollisionSpace;
WorldToCollisionSpace( in, &vecCollisionSpace );
CollisionToNormalizedSpace( vecCollisionSpace, pResult );
return *pResult;
}
//-----------------------------------------------------------------------------
// Selects a random point in the bounds given the normalized 0-1 bounds
//-----------------------------------------------------------------------------
void CCollisionProperty::RandomPointInBounds( const Vector &vecNormalizedMins, const Vector &vecNormalizedMaxs, Vector *pPoint) const
{
Vector vecNormalizedSpace;
vecNormalizedSpace.x = random->RandomFloat( vecNormalizedMins.x, vecNormalizedMaxs.x );
vecNormalizedSpace.y = random->RandomFloat( vecNormalizedMins.y, vecNormalizedMaxs.y );
vecNormalizedSpace.z = random->RandomFloat( vecNormalizedMins.z, vecNormalizedMaxs.z );
NormalizedToWorldSpace( vecNormalizedSpace, pPoint );
}
//-----------------------------------------------------------------------------
// Transforms an AABB measured in entity space to a box that surrounds it in world space
//-----------------------------------------------------------------------------
void CCollisionProperty::CollisionAABBToWorldAABB( const Vector &entityMins,
const Vector &entityMaxs, Vector *pWorldMins, Vector *pWorldMaxs ) const
{
if ( !IsBoundsDefinedInEntitySpace() || (GetCollisionAngles() == vec3_angle) )
{
VectorAdd( entityMins, GetCollisionOrigin(), *pWorldMins );
VectorAdd( entityMaxs, GetCollisionOrigin(), *pWorldMaxs );
}
else
{
TransformAABB( CollisionToWorldTransform(), entityMins, entityMaxs, *pWorldMins, *pWorldMaxs );
}
}
/*
void CCollisionProperty::WorldAABBToCollisionAABB( const Vector &worldMins, const Vector &worldMaxs, Vector *pEntityMins, Vector *pEntityMaxs ) const
{
if ( !IsBoundsDefinedInEntitySpace() || (GetCollisionAngles() == vec3_angle) )
{
VectorSubtract( worldMins, GetAbsOrigin(), *pEntityMins );
VectorSubtract( worldMaxs, GetAbsOrigin(), *pEntityMaxs );
}
else
{
ITransformAABB( CollisionToWorldTransform(), worldMins, worldMaxs, *pEntityMins, *pEntityMaxs );
}
}
*/
//-----------------------------------------------------------------------------
// Is a worldspace point within the bounds of the OBB?
//-----------------------------------------------------------------------------
bool CCollisionProperty::IsPointInBounds( const Vector &vecWorldPt ) const
{
Vector vecLocalSpace;
WorldToCollisionSpace( vecWorldPt, &vecLocalSpace );
return ( ( vecLocalSpace.x >= m_vecMins.Get().x && vecLocalSpace.x <= m_vecMaxs.Get().x ) &&
( vecLocalSpace.y >= m_vecMins.Get().y && vecLocalSpace.y <= m_vecMaxs.Get().y ) &&
( vecLocalSpace.z >= m_vecMins.Get().z && vecLocalSpace.z <= m_vecMaxs.Get().z ) );
}
//-----------------------------------------------------------------------------
// Computes the nearest point in the OBB to a point specified in world space
//-----------------------------------------------------------------------------
void CCollisionProperty::CalcNearestPoint( const Vector &vecWorldPt, Vector *pVecNearestWorldPt ) const
{
// Calculate physics force
Vector localPt, localClosestPt;
WorldToCollisionSpace( vecWorldPt, &localPt );
CalcClosestPointOnAABB( m_vecMins.Get(), m_vecMaxs.Get(), localPt, localClosestPt );
CollisionToWorldSpace( localClosestPt, pVecNearestWorldPt );
}
//-----------------------------------------------------------------------------
// Computes the nearest point in the OBB to a point specified in world space
//-----------------------------------------------------------------------------
float CCollisionProperty::CalcDistanceFromPoint( const Vector &vecWorldPt ) const
{
// Calculate physics force
Vector localPt, localClosestPt;
WorldToCollisionSpace( vecWorldPt, &localPt );
CalcClosestPointOnAABB( m_vecMins.Get(), m_vecMaxs.Get(), localPt, localClosestPt );
return localPt.DistTo( localClosestPt );
}
//-----------------------------------------------------------------------------
// Computes the square distance of the closest point in the OBB to a point specified in world space
//-----------------------------------------------------------------------------
float CCollisionProperty::CalcSqrDistanceFromPoint( const Vector &vecWorldPt ) const
{
// Calculate physics force
Vector localPt, localClosestPt;
WorldToCollisionSpace( vecWorldPt, &localPt );
CalcClosestPointOnAABB( m_vecMins.Get(), m_vecMaxs.Get(), localPt, localClosestPt );
return localPt.DistToSqr( localClosestPt );
}
//-----------------------------------------------------------------------------
// Compute the largest dot product of the OBB and the specified direction vector
//-----------------------------------------------------------------------------
float CCollisionProperty::ComputeSupportMap( const Vector &vecDirection ) const
{
Vector vecCollisionDir;
WorldDirectionToCollisionSpace( vecDirection, &vecCollisionDir );
float flResult = DotProduct( GetCollisionOrigin(), vecDirection );
flResult += (( vecCollisionDir.x >= 0.0f ) ? m_vecMaxs.Get().x : m_vecMins.Get().x) * vecCollisionDir.x;
flResult += (( vecCollisionDir.y >= 0.0f ) ? m_vecMaxs.Get().y : m_vecMins.Get().y) * vecCollisionDir.y;
flResult += (( vecCollisionDir.z >= 0.0f ) ? m_vecMaxs.Get().z : m_vecMins.Get().z) * vecCollisionDir.z;
return flResult;
}
//-----------------------------------------------------------------------------
// Expand trigger bounds..
//-----------------------------------------------------------------------------
void CCollisionProperty::ComputeVPhysicsSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs )
{
bool bSetBounds = false;
IPhysicsObject *pPhysicsObject = GetOuter()->VPhysicsGetObject();
if ( pPhysicsObject )
{
if ( pPhysicsObject->GetCollide() )
{
physcollision->CollideGetAABB( pVecWorldMins, pVecWorldMaxs,
pPhysicsObject->GetCollide(), GetCollisionOrigin(), GetCollisionAngles() );
bSetBounds = true;
}
else if ( pPhysicsObject->GetSphereRadius( ) )
{
float flRadius = pPhysicsObject->GetSphereRadius( );
Vector vecExtents( flRadius, flRadius, flRadius );
VectorSubtract( GetCollisionOrigin(), vecExtents, *pVecWorldMins );
VectorAdd( GetCollisionOrigin(), vecExtents, *pVecWorldMaxs );
bSetBounds = true;
}
}
if ( !bSetBounds )
{
*pVecWorldMins = GetCollisionOrigin();
*pVecWorldMaxs = *pVecWorldMins;
}
// Also, lets expand for the trigger bounds also
if ( IsSolidFlagSet( FSOLID_USE_TRIGGER_BOUNDS ) )
{
Vector vecWorldTriggerMins, vecWorldTriggerMaxs;
WorldSpaceTriggerBounds( &vecWorldTriggerMins, &vecWorldTriggerMaxs );
VectorMin( vecWorldTriggerMins, *pVecWorldMins, *pVecWorldMins );
VectorMax( vecWorldTriggerMaxs, *pVecWorldMaxs, *pVecWorldMaxs );
}
}
//-----------------------------------------------------------------------------
// Expand trigger bounds..
//-----------------------------------------------------------------------------
bool CCollisionProperty::ComputeHitboxSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs )
{
CBaseAnimating *pAnim = GetOuter()->GetBaseAnimating();
if (pAnim)
{
return pAnim->ComputeHitboxSurroundingBox( pVecWorldMins, pVecWorldMaxs );
}
return false;
}
//-----------------------------------------------------------------------------
// Computes the surrounding collision bounds based on the current sequence box
//-----------------------------------------------------------------------------
void CCollisionProperty::ComputeOBBBounds( Vector *pVecWorldMins, Vector *pVecWorldMaxs )
{
bool bUseVPhysics = false;
if ( ( GetSolid() == SOLID_VPHYSICS ) && ( GetOuter()->GetMoveType() == MOVETYPE_VPHYSICS ) )
{
// UNDONE: This may not be necessary any more.
IPhysicsObject *pPhysics = GetOuter()->VPhysicsGetObject();
bUseVPhysics = pPhysics && pPhysics->IsAsleep();
}
ComputeCollisionSurroundingBox( bUseVPhysics, pVecWorldMins, pVecWorldMaxs );
}
//-----------------------------------------------------------------------------
// Computes the surrounding collision bounds from the current sequence box
//-----------------------------------------------------------------------------
void CCollisionProperty::ComputeRotationExpandedSequenceBounds( Vector *pVecWorldMins, Vector *pVecWorldMaxs )
{
CBaseAnimating *pAnim = GetOuter()->GetBaseAnimating();
if ( !pAnim )
{
ComputeOBBBounds( pVecWorldMins, pVecWorldMaxs );
return;
}
Vector mins, maxs;
pAnim->ExtractBbox( pAnim->GetSequence(), mins, maxs );
float flRadius = MAX( MAX( FloatMakePositive( mins.x ), FloatMakePositive( maxs.x ) ),
MAX( FloatMakePositive( mins.y ), FloatMakePositive( maxs.y ) ) );
mins.x = mins.y = -flRadius;
maxs.x = maxs.y = flRadius;
// Add bloat to account for gesture sequences
Vector vecBloat( 6, 6, 0 );
mins -= vecBloat;
maxs += vecBloat;
// NOTE: This is necessary because the server doesn't know how to blend
// animations together. Therefore, we have to just pick a box that can
// surround all of our potential sequences. This should be something we
// should be able to compute @ tool time instead, however.
VectorMin( mins, m_vecSurroundingMins, mins );
VectorMax( maxs, m_vecSurroundingMaxs, maxs );
VectorAdd( mins, GetCollisionOrigin(), *pVecWorldMins );
VectorAdd( maxs, GetCollisionOrigin(), *pVecWorldMaxs );
}
//-----------------------------------------------------------------------------
// Expand trigger bounds..
//-----------------------------------------------------------------------------
bool CCollisionProperty::ComputeEntitySpaceHitboxSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs )
{
CBaseAnimating *pAnim = GetOuter()->GetBaseAnimating();
if (pAnim)
{
return pAnim->ComputeEntitySpaceHitboxSurroundingBox( pVecWorldMins, pVecWorldMaxs );
}
return false;
}
//-----------------------------------------------------------------------------
// Computes the surrounding collision bounds from the the OBB (not vphysics)
//-----------------------------------------------------------------------------
void CCollisionProperty::ComputeRotationExpandedBounds( Vector *pVecWorldMins, Vector *pVecWorldMaxs )
{
if ( !IsBoundsDefinedInEntitySpace() )
{
*pVecWorldMins = m_vecMins;
*pVecWorldMaxs = m_vecMaxs;
}
else
{
float flMaxVal;
flMaxVal = MAX( FloatMakePositive(m_vecMins.Get().x), FloatMakePositive(m_vecMaxs.Get().x) );
pVecWorldMins->x = -flMaxVal;
pVecWorldMaxs->x = flMaxVal;
flMaxVal = MAX( FloatMakePositive(m_vecMins.Get().y), FloatMakePositive(m_vecMaxs.Get().y) );
pVecWorldMins->y = -flMaxVal;
pVecWorldMaxs->y = flMaxVal;
flMaxVal = MAX( FloatMakePositive(m_vecMins.Get().z), FloatMakePositive(m_vecMaxs.Get().z) );
pVecWorldMins->z = -flMaxVal;
pVecWorldMaxs->z = flMaxVal;
}
}
//-----------------------------------------------------------------------------
// Computes the surrounding collision bounds based on whatever algorithm we want...
//-----------------------------------------------------------------------------
void CCollisionProperty::ComputeCollisionSurroundingBox( bool bUseVPhysics, Vector *pVecWorldMins, Vector *pVecWorldMaxs )
{
Assert( GetSolid() != SOLID_CUSTOM );
// NOTE: For solid none, we are still going to use the bounds; necessary because
// the surrounding box is used for the PVS...
// FIXME: Should we make some other call for the PVS stuff?? If so, we should return
// a point bounds for SOLID_NONE...
// if ( GetSolid() == SOLID_NONE )
// {
// *pVecWorldMins = GetCollisionOrigin();
// *pVecWorldMaxs = *pVecWorldMins;
// return;
// }
if ( bUseVPhysics )
{
ComputeVPhysicsSurroundingBox( pVecWorldMins, pVecWorldMaxs );
}
else
{
// Will expand the bounds for the trigger, if it is a trigger
WorldSpaceTriggerBounds( pVecWorldMins, pVecWorldMaxs );
}
}
//-----------------------------------------------------------------------------
// Computes the surrounding collision bounds based on whatever algorithm we want...
//-----------------------------------------------------------------------------
#ifdef CLIENT_DLL
static ConVar cl_show_bounds_errors( "cl_show_bounds_errors", "0" );
#endif
void CCollisionProperty::ComputeSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs )
{
if (( GetSolid() == SOLID_CUSTOM ) && (m_nSurroundType != USE_GAME_CODE ))
{
// NOTE: This can only happen in transition periods, say during network
// reception on the client. We expect USE_GAME_CODE to be used with SOLID_CUSTOM
*pVecWorldMins = GetCollisionOrigin();
*pVecWorldMaxs = *pVecWorldMins;
return;
}
switch( m_nSurroundType )
{
case USE_OBB_COLLISION_BOUNDS:
Assert( GetSolid() != SOLID_CUSTOM );
ComputeOBBBounds( pVecWorldMins, pVecWorldMaxs );
break;
case USE_BEST_COLLISION_BOUNDS:
Assert( GetSolid() != SOLID_CUSTOM );
ComputeCollisionSurroundingBox( (GetSolid() == SOLID_VPHYSICS), pVecWorldMins, pVecWorldMaxs );
break;
case USE_ROTATION_EXPANDED_SEQUENCE_BOUNDS:
ComputeRotationExpandedSequenceBounds( pVecWorldMins, pVecWorldMaxs );
break;
case USE_COLLISION_BOUNDS_NEVER_VPHYSICS:
Assert( GetSolid() != SOLID_CUSTOM );
ComputeCollisionSurroundingBox( false, pVecWorldMins, pVecWorldMaxs );
break;
case USE_HITBOXES:
ComputeHitboxSurroundingBox( pVecWorldMins, pVecWorldMaxs );
break;
case USE_ROTATION_EXPANDED_BOUNDS:
ComputeRotationExpandedBounds( pVecWorldMins, pVecWorldMaxs );
break;
case USE_SPECIFIED_BOUNDS:
VectorAdd( GetCollisionOrigin(), m_vecSpecifiedSurroundingMins, *pVecWorldMins );
VectorAdd( GetCollisionOrigin(), m_vecSpecifiedSurroundingMaxs, *pVecWorldMaxs );
break;
case USE_GAME_CODE:
GetOuter()->ComputeWorldSpaceSurroundingBox( pVecWorldMins, pVecWorldMaxs );
Assert( pVecWorldMins->x <= pVecWorldMaxs->x );
Assert( pVecWorldMins->y <= pVecWorldMaxs->y );
Assert( pVecWorldMins->z <= pVecWorldMaxs->z );
return;
}
//#ifdef DEBUG
#ifdef CLIENT_DLL
if ( cl_show_bounds_errors.GetBool() && ( m_nSurroundType == USE_ROTATION_EXPANDED_SEQUENCE_BOUNDS ) )
{
// For debugging purposes, make sure the bounds actually does surround the thing.
// Otherwise the optimization we were using isn't really all that great, is it?
Vector vecTestMins, vecTestMaxs;
if ( GetOuter()->GetBaseAnimating() )
{
GetOuter()->GetBaseAnimating()->InvalidateBoneCache();
}
ComputeHitboxSurroundingBox( &vecTestMins, &vecTestMaxs );
Assert( vecTestMins.x >= pVecWorldMins->x && vecTestMins.y >= pVecWorldMins->y && vecTestMins.z >= pVecWorldMins->z );
Assert( vecTestMaxs.x <= pVecWorldMaxs->x && vecTestMaxs.y <= pVecWorldMaxs->y && vecTestMaxs.z <= pVecWorldMaxs->z );
if ( vecTestMins.x < pVecWorldMins->x || vecTestMins.y < pVecWorldMins->y || vecTestMins.z < pVecWorldMins->z ||
vecTestMaxs.x > pVecWorldMaxs->x || vecTestMaxs.y > pVecWorldMaxs->y || vecTestMaxs.z > pVecWorldMaxs->z )
{
const char *pSeqName = "<unknown seq>";
C_BaseAnimating *pAnim = GetOuter()->GetBaseAnimating();
if ( pAnim )
{
int nSequence = pAnim->GetSequence();
pSeqName = pAnim->GetSequenceName( nSequence );
}
Warning( "*** Bounds problem, index %d Eng %s, Seqeuence %s ", GetOuter()->entindex(), GetOuter()->GetClassname(), pSeqName );
Vector vecDelta = *pVecWorldMins - vecTestMins;
Vector vecDelta2 = vecTestMaxs - *pVecWorldMaxs;
if ( vecDelta.x > 0.0f || vecDelta2.x > 0.0f || vecDelta.y > 0.0f || vecDelta2.y > 0.0f )
{
Msg( "Outside X/Y by %.2f ", MAX( MAX( vecDelta.x, vecDelta2.x ), MAX( vecDelta.y, vecDelta2.y ) ) );
}
if ( vecDelta.z > 0.0f || vecDelta2.z > 0.0f )
{
Msg( "Outside Z by (below) %.2f, (above) %.2f ", MAX( vecDelta.z, 0.0f ), MAX( vecDelta2.z, 0.0f ) );
}
Msg( "\n" );
char pTemp[MAX_PATH];
Q_snprintf( pTemp, sizeof(pTemp), "%s [seq: %s]", GetOuter()->GetClassname(), pSeqName );
debugoverlay->AddBoxOverlay( vec3_origin, vecTestMins, vecTestMaxs, vec3_angle, 255, 0, 0, 0, 2 );
debugoverlay->AddBoxOverlay( vec3_origin, *pVecWorldMins, *pVecWorldMaxs, vec3_angle, 0, 0, 255, 0, 2 );
debugoverlay->AddTextOverlay( ( vecTestMins + vecTestMaxs ) * 0.5f, 2, pTemp );
}
}
#endif
//#endif
}
//-----------------------------------------------------------------------------
// Sets the method by which the surrounding collision bounds is set
//-----------------------------------------------------------------------------
void CCollisionProperty::SetSurroundingBoundsType( SurroundingBoundsType_t type, const Vector *pMins, const Vector *pMaxs )
{
m_nSurroundType = type;
if (type != USE_SPECIFIED_BOUNDS)
{
Assert( !pMins && !pMaxs );
MarkSurroundingBoundsDirty();
}
else
{
Assert( pMins && pMaxs );
m_vecSpecifiedSurroundingMins = *pMins;
m_vecSpecifiedSurroundingMaxs = *pMaxs;
m_vecSurroundingMins = *pMins;
m_vecSurroundingMaxs = *pMaxs;
ASSERT_COORD( m_vecSurroundingMins );
ASSERT_COORD( m_vecSurroundingMaxs );
}
}
//-----------------------------------------------------------------------------
// Marks the entity has having a dirty surrounding box
//-----------------------------------------------------------------------------
void CCollisionProperty::MarkSurroundingBoundsDirty()
{
// don't bother with the world
if ( m_pOuter->entindex() == 0 )
return;
GetOuter()->AddEFlags( EFL_DIRTY_SURROUNDING_COLLISION_BOUNDS );
MarkPartitionHandleDirty();
#ifdef CLIENT_DLL
GetOuter()->MarkRenderHandleDirty();
g_pClientShadowMgr->AddToDirtyShadowList( GetOuter() );
g_pClientShadowMgr->MarkRenderToTextureShadowDirty( GetOuter()->GetShadowHandle() );
#else
GetOuter()->NetworkProp()->MarkPVSInformationDirty();
#endif
}
//-----------------------------------------------------------------------------
// Does VPhysicsUpdate make us need to recompute the surrounding box?
//-----------------------------------------------------------------------------
bool CCollisionProperty::DoesVPhysicsInvalidateSurroundingBox( ) const
{
switch ( m_nSurroundType )
{
case USE_BEST_COLLISION_BOUNDS:
return true;
case USE_OBB_COLLISION_BOUNDS:
return (GetSolid() == SOLID_VPHYSICS) && (GetOuter()->GetMoveType() == MOVETYPE_VPHYSICS) && GetOuter()->VPhysicsGetObject();
// In the case of game code, we don't really know, so we have to assume it does
case USE_GAME_CODE:
return true;
case USE_COLLISION_BOUNDS_NEVER_VPHYSICS:
case USE_HITBOXES:
case USE_ROTATION_EXPANDED_BOUNDS:
case USE_SPECIFIED_BOUNDS:
case USE_ROTATION_EXPANDED_SEQUENCE_BOUNDS:
return false;
default:
Assert(0);
return true;
}
}
//-----------------------------------------------------------------------------
// Computes the surrounding collision bounds based on whatever algorithm we want...
//-----------------------------------------------------------------------------
void CCollisionProperty::WorldSpaceSurroundingBounds( Vector *pVecMins, Vector *pVecMaxs )
{
const Vector &vecAbsOrigin = GetCollisionOrigin();
if ( GetOuter()->IsEFlagSet( EFL_DIRTY_SURROUNDING_COLLISION_BOUNDS ))
{
GetOuter()->RemoveEFlags( EFL_DIRTY_SURROUNDING_COLLISION_BOUNDS );
ComputeSurroundingBox( pVecMins, pVecMaxs );
VectorSubtract( *pVecMins, vecAbsOrigin, m_vecSurroundingMins );
VectorSubtract( *pVecMaxs, vecAbsOrigin, m_vecSurroundingMaxs );
ASSERT_COORD( m_vecSurroundingMins );
ASSERT_COORD( m_vecSurroundingMaxs );
}
else
{
VectorAdd( m_vecSurroundingMins, vecAbsOrigin, *pVecMins );
VectorAdd( m_vecSurroundingMaxs, vecAbsOrigin, *pVecMaxs );
}
}
//-----------------------------------------------------------------------------
// Spatial partition
//-----------------------------------------------------------------------------
void CCollisionProperty::CreatePartitionHandle()
{
// Put the entity into the spatial partition.
Assert( m_Partition == PARTITION_INVALID_HANDLE );
m_Partition = partition->CreateHandle( GetEntityHandle() );
}
void CCollisionProperty::DestroyPartitionHandle()
{
if ( m_Partition != PARTITION_INVALID_HANDLE )
{
partition->DestroyHandle( m_Partition );
m_Partition = PARTITION_INVALID_HANDLE;
}
}
//-----------------------------------------------------------------------------
// Updates the spatial partition
//-----------------------------------------------------------------------------
void CCollisionProperty::UpdateServerPartitionMask( )
{
#ifndef CLIENT_DLL
SpatialPartitionHandle_t handle = GetPartitionHandle();
if ( handle == PARTITION_INVALID_HANDLE )
return;
// Remove it from whatever lists it may be in at the moment
// We'll re-add it below if we need to.
partition->Remove( handle );
// Don't bother with deleted things
if ( !m_pOuter->edict() )
return;
// don't add the world
if ( m_pOuter->entindex() == 0 )
return;
// Make sure it's in the list of all entities
bool bIsSolid = IsSolid() || IsSolidFlagSet(FSOLID_TRIGGER);
if ( bIsSolid || m_pOuter->IsEFlagSet(EFL_USE_PARTITION_WHEN_NOT_SOLID) )
{
partition->Insert( PARTITION_ENGINE_NON_STATIC_EDICTS, handle );
}
if ( !bIsSolid )
return;
// Insert it into the appropriate lists.
// We have to continually reinsert it because its solid type may have changed
SpatialPartitionListMask_t mask = 0;
if ( !IsSolidFlagSet(FSOLID_NOT_SOLID) )
{
mask |= PARTITION_ENGINE_SOLID_EDICTS;
}
if ( IsSolidFlagSet(FSOLID_TRIGGER) )
{
mask |= PARTITION_ENGINE_TRIGGER_EDICTS;
}
Assert( mask != 0 );
partition->Insert( mask, handle );
#endif
}
//-----------------------------------------------------------------------------
// Marks the spatial partition dirty
//-----------------------------------------------------------------------------
void CCollisionProperty::MarkPartitionHandleDirty()
{
if ( !m_pOuter->IsEFlagSet( EFL_DIRTY_SPATIAL_PARTITION ) )
{
s_DirtyKDTree.AddEntity( m_pOuter );
m_pOuter->AddEFlags( EFL_DIRTY_SPATIAL_PARTITION );
}
}
//-----------------------------------------------------------------------------
// Updates the spatial partition
//-----------------------------------------------------------------------------
void CCollisionProperty::UpdatePartition( )
{
if ( m_pOuter->IsEFlagSet( EFL_DIRTY_SPATIAL_PARTITION ) )
{
m_pOuter->RemoveEFlags( EFL_DIRTY_SPATIAL_PARTITION );
#ifndef CLIENT_DLL
Assert( m_pOuter->entindex() != 0 );
// Don't bother with deleted things
if ( !m_pOuter->edict() )
return;
if ( GetPartitionHandle() == PARTITION_INVALID_HANDLE )
{
CreatePartitionHandle();
UpdateServerPartitionMask();
}
#else
if ( GetPartitionHandle() == PARTITION_INVALID_HANDLE )
return;
#endif
// We don't need to bother if it's not a trigger or solid
if ( IsSolid() || IsSolidFlagSet( FSOLID_TRIGGER ) || m_pOuter->IsEFlagSet( EFL_USE_PARTITION_WHEN_NOT_SOLID ) )
{
// Bloat a little bit...
if ( BoundingRadius() != 0.0f )
{
Vector vecSurroundMins, vecSurroundMaxs;
WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs );
vecSurroundMins -= Vector( 1, 1, 1 );
vecSurroundMaxs += Vector( 1, 1, 1 );
partition->ElementMoved( GetPartitionHandle(), vecSurroundMins, vecSurroundMaxs );
}
else
{
partition->ElementMoved( GetPartitionHandle(), GetCollisionOrigin(), GetCollisionOrigin() );
}
}
}
}