1446 lines
48 KiB
C++
1446 lines
48 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() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|