mirror of
https://github.com/nillerusr/source-engine.git
synced 2024-12-23 14:46:53 +00:00
1092 lines
30 KiB
C++
1092 lines
30 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: Clones a physics object (usually with a matrix transform applied)
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "physicsshadowclone.h"
|
|
#include "portal_util_shared.h"
|
|
#include "vphysics/object_hash.h"
|
|
#include "trains.h"
|
|
#include "props.h"
|
|
#include "model_types.h"
|
|
#include "portal/weapon_physcannon.h" //grab controllers
|
|
|
|
#include "PortalSimulation.h"
|
|
|
|
#define MAX_SHADOW_CLONE_COUNT 200
|
|
|
|
static int g_iShadowCloneCount = 0;
|
|
ConVar sv_debug_physicsshadowclones("sv_debug_physicsshadowclones", "0", FCVAR_REPLICATED );
|
|
ConVar sv_use_shadow_clones( "sv_use_shadow_clones", "1", FCVAR_REPLICATED | FCVAR_CHEAT ); //should we create shadow clones?
|
|
|
|
static void DrawDebugOverlayForShadowClone( CPhysicsShadowClone *pClone );
|
|
|
|
LINK_ENTITY_TO_CLASS( physicsshadowclone, CPhysicsShadowClone );
|
|
|
|
static CUtlVector<CPhysicsShadowClone *> s_ActiveShadowClones;
|
|
CUtlVector<CPhysicsShadowClone *> const &CPhysicsShadowClone::g_ShadowCloneList = s_ActiveShadowClones;
|
|
static bool s_IsShadowClone[MAX_EDICTS] = { false };
|
|
|
|
static CPhysicsShadowCloneLL *s_EntityClones[MAX_EDICTS] = { NULL };
|
|
struct ShadowCloneLLEntryManager
|
|
{
|
|
CPhysicsShadowCloneLL m_ShadowCloneLLEntries[MAX_SHADOW_CLONE_COUNT];
|
|
CPhysicsShadowCloneLL *m_pFreeShadowCloneLLEntries[MAX_SHADOW_CLONE_COUNT];
|
|
int m_iUsedEntryIndex;
|
|
|
|
ShadowCloneLLEntryManager( void )
|
|
{
|
|
m_iUsedEntryIndex = 0;
|
|
for( int i = 0; i != MAX_SHADOW_CLONE_COUNT; ++i )
|
|
{
|
|
m_pFreeShadowCloneLLEntries[i] = &m_ShadowCloneLLEntries[i];
|
|
}
|
|
}
|
|
|
|
inline CPhysicsShadowCloneLL *Alloc( void )
|
|
{
|
|
return m_pFreeShadowCloneLLEntries[m_iUsedEntryIndex++];
|
|
}
|
|
|
|
inline void Free( CPhysicsShadowCloneLL *pFree )
|
|
{
|
|
m_pFreeShadowCloneLLEntries[--m_iUsedEntryIndex] = pFree;
|
|
}
|
|
};
|
|
static ShadowCloneLLEntryManager s_SCLLManager;
|
|
|
|
|
|
CPhysicsShadowClone::CPhysicsShadowClone( void )
|
|
{
|
|
m_matrixShadowTransform.Identity();
|
|
m_matrixShadowTransform_Inverse.Identity();
|
|
m_bShadowTransformIsIdentity = true;
|
|
s_ActiveShadowClones.AddToTail( this );
|
|
}
|
|
|
|
CPhysicsShadowClone::~CPhysicsShadowClone( void )
|
|
{
|
|
VPhysicsDestroyObject();
|
|
VPhysicsSetObject( NULL );
|
|
m_hClonedEntity = NULL;
|
|
s_ActiveShadowClones.FindAndRemove( this ); //also removed in UpdateOnRemove()
|
|
Assert( s_IsShadowClone[entindex()] == true );
|
|
s_IsShadowClone[entindex()] = false;
|
|
}
|
|
|
|
void CPhysicsShadowClone::UpdateOnRemove( void )
|
|
{
|
|
CBaseEntity *pSource = m_hClonedEntity;
|
|
if( pSource )
|
|
{
|
|
CPhysicsShadowCloneLL *pCloneListHead = s_EntityClones[pSource->entindex()];
|
|
Assert( pCloneListHead != NULL );
|
|
|
|
CPhysicsShadowCloneLL *pFind = pCloneListHead;
|
|
CPhysicsShadowCloneLL *pLast = pFind;
|
|
while( pFind->pClone != this )
|
|
{
|
|
pLast = pFind;
|
|
Assert( pFind->pNext != NULL );
|
|
pFind = pFind->pNext;
|
|
}
|
|
|
|
if( pFind == pCloneListHead )
|
|
{
|
|
s_EntityClones[pSource->entindex()] = pFind->pNext;
|
|
}
|
|
else
|
|
{
|
|
pLast->pNext = pFind->pNext;
|
|
}
|
|
s_SCLLManager.Free( pFind );
|
|
}
|
|
#ifdef _DEBUG
|
|
else
|
|
{
|
|
//verify that it didn't weasel into a list somewhere and get left behind
|
|
for( int i = 0; i != MAX_SHADOW_CLONE_COUNT; ++i )
|
|
{
|
|
CPhysicsShadowCloneLL *pCloneSearch = s_EntityClones[i];
|
|
while( pCloneSearch )
|
|
{
|
|
Assert( pCloneSearch->pClone != this );
|
|
pCloneSearch = pCloneSearch->pNext;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
VPhysicsDestroyObject();
|
|
VPhysicsSetObject( NULL );
|
|
m_hClonedEntity = NULL;
|
|
s_ActiveShadowClones.FindAndRemove( this ); //also removed in Destructor
|
|
BaseClass::UpdateOnRemove();
|
|
}
|
|
|
|
void CPhysicsShadowClone::Spawn( void )
|
|
{
|
|
AddFlag( FL_DONTTOUCH );
|
|
AddEffects( EF_NODRAW | EF_NOSHADOW | EF_NORECEIVESHADOW );
|
|
|
|
FullSync( false );
|
|
m_bInAssumedSyncState = false;
|
|
|
|
BaseClass::Spawn();
|
|
|
|
s_IsShadowClone[entindex()] = true;
|
|
}
|
|
|
|
|
|
void CPhysicsShadowClone::FullSync( bool bAllowAssumedSync )
|
|
{
|
|
Assert( IsMarkedForDeletion() == false );
|
|
|
|
CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
|
|
|
|
if( pClonedEntity == NULL )
|
|
{
|
|
AssertMsg( VPhysicsGetObject() != NULL, "Been linkless for more than this update, something should have killed this clone." );
|
|
SetMoveType( MOVETYPE_NONE );
|
|
SetSolid( SOLID_NONE );
|
|
SetSolidFlags( 0 );
|
|
SetCollisionGroup( COLLISION_GROUP_NONE );
|
|
VPhysicsDestroyObject();
|
|
return;
|
|
}
|
|
|
|
SetGroundEntity( NULL );
|
|
|
|
bool bIsSynced = bAllowAssumedSync;
|
|
bool bBigChanges = true; //assume there are, and be proven wrong
|
|
|
|
if( bAllowAssumedSync )
|
|
{
|
|
IPhysicsObject *pSourceObjects[1024];
|
|
int iObjectCount = pClonedEntity->VPhysicsGetObjectList( pSourceObjects, 1024 );
|
|
|
|
//scan for really big differences that would definitely require a full sync
|
|
bBigChanges = ( iObjectCount != m_CloneLinks.Count() );
|
|
if( !bBigChanges )
|
|
{
|
|
for( int i = 0; i != iObjectCount; ++i )
|
|
{
|
|
IPhysicsObject *pSourcePhysics = pSourceObjects[i];
|
|
IPhysicsObject *pClonedPhysics = m_CloneLinks[i].pClone;
|
|
|
|
if( (pSourcePhysics != m_CloneLinks[i].pSource) ||
|
|
(pSourcePhysics->IsCollisionEnabled() != pClonedPhysics->IsCollisionEnabled()) )
|
|
{
|
|
bBigChanges = true;
|
|
bIsSynced = false;
|
|
break;
|
|
}
|
|
|
|
Vector ptSourcePosition, ptClonePosition;
|
|
pSourcePhysics->GetPosition( &ptSourcePosition, NULL );
|
|
if( !m_bShadowTransformIsIdentity )
|
|
ptSourcePosition = m_matrixShadowTransform * ptSourcePosition;
|
|
|
|
pClonedPhysics->GetPosition( &ptClonePosition, NULL );
|
|
|
|
if( (ptClonePosition - ptSourcePosition).LengthSqr() > 2500.0f )
|
|
{
|
|
bBigChanges = true;
|
|
bIsSynced = false;
|
|
break;
|
|
}
|
|
|
|
//Vector vSourceVelocity, vCloneVelocity;
|
|
|
|
|
|
if( !pSourcePhysics->IsAsleep() ) //only allow full syncrosity if the source entity is entirely asleep
|
|
bIsSynced = false;
|
|
|
|
if( m_bInAssumedSyncState && !pClonedPhysics->IsAsleep() )
|
|
bIsSynced = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bIsSynced = false;
|
|
}
|
|
|
|
bIsSynced = false;
|
|
|
|
if( bIsSynced )
|
|
{
|
|
//good enough to skip a full update
|
|
if( !m_bInAssumedSyncState )
|
|
{
|
|
//do one last sync
|
|
PartialSync( true );
|
|
|
|
//if we don't do this, objects just fall out of the world (it happens, I swear)
|
|
|
|
for( int i = m_CloneLinks.Count(); --i >= 0; )
|
|
{
|
|
if( (m_CloneLinks[i].pSource->GetShadowController() == NULL) && m_CloneLinks[i].pClone->IsMotionEnabled() )
|
|
{
|
|
//m_CloneLinks[i].pClone->SetVelocityInstantaneous( &vec3_origin, &vec3_origin );
|
|
//m_CloneLinks[i].pClone->SetVelocity( &vec3_origin, &vec3_origin );
|
|
m_CloneLinks[i].pClone->EnableGravity( false );
|
|
m_CloneLinks[i].pClone->EnableMotion( false );
|
|
m_CloneLinks[i].pClone->Sleep();
|
|
}
|
|
}
|
|
|
|
m_bInAssumedSyncState = true;
|
|
}
|
|
|
|
if( sv_debug_physicsshadowclones.GetBool() )
|
|
DrawDebugOverlayForShadowClone( this );
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_bInAssumedSyncState = false;
|
|
|
|
|
|
|
|
|
|
|
|
//past this point, we're committed to a broad update
|
|
|
|
if( bBigChanges )
|
|
{
|
|
MoveType_t sourceMoveType = pClonedEntity->GetMoveType();
|
|
|
|
|
|
IPhysicsObject *pPhysObject = pClonedEntity->VPhysicsGetObject();
|
|
if( (sourceMoveType == MOVETYPE_CUSTOM) ||
|
|
(sourceMoveType == MOVETYPE_STEP) ||
|
|
(sourceMoveType == MOVETYPE_WALK) ||
|
|
(pPhysObject &&
|
|
(
|
|
(pPhysObject->GetGameFlags() & FVPHYSICS_PLAYER_HELD) ||
|
|
(pPhysObject->GetShadowController() != NULL)
|
|
)
|
|
)
|
|
)
|
|
{
|
|
//#ifdef _DEBUG
|
|
SetMoveType( MOVETYPE_NONE ); //to kill an assert
|
|
//#endif
|
|
//PUSH should be used sparingly, you can't stand on a MOVETYPE_PUSH object :/
|
|
SetMoveType( MOVETYPE_VPHYSICS, pClonedEntity->GetMoveCollide() ); //either an unclonable movetype, or a shadow/held object
|
|
}
|
|
/*else if(sourceMoveType == MOVETYPE_STEP)
|
|
{
|
|
//SetMoveType( MOVETYPE_NONE ); //to kill an assert
|
|
SetMoveType( MOVETYPE_VPHYSICS, pClonedEntity->GetMoveCollide() );
|
|
}*/
|
|
else
|
|
{
|
|
//if( m_bShadowTransformIsIdentity )
|
|
SetMoveType( sourceMoveType, pClonedEntity->GetMoveCollide() );
|
|
//else
|
|
//{
|
|
// SetMoveType( MOVETYPE_NONE ); //to kill an assert
|
|
// SetMoveType( MOVETYPE_PUSH, pClonedEntity->GetMoveCollide() );
|
|
//}
|
|
}
|
|
|
|
SolidType_t sourceSolidType = pClonedEntity->GetSolid();
|
|
if( sourceSolidType == SOLID_BBOX )
|
|
SetSolid( SOLID_VPHYSICS );
|
|
else
|
|
SetSolid( sourceSolidType );
|
|
//SetSolid( SOLID_VPHYSICS );
|
|
|
|
SetElasticity( pClonedEntity->GetElasticity() );
|
|
SetFriction( pClonedEntity->GetFriction() );
|
|
|
|
|
|
|
|
int iSolidFlags = pClonedEntity->GetSolidFlags() | FSOLID_CUSTOMRAYTEST;
|
|
if( m_bShadowTransformIsIdentity )
|
|
iSolidFlags |= FSOLID_CUSTOMBOXTEST; //need this at least for the player or they get stuck in themselves
|
|
else
|
|
iSolidFlags &= ~FSOLID_FORCE_WORLD_ALIGNED;
|
|
/*if( pClonedEntity->IsPlayer() )
|
|
{
|
|
iSolidFlags |= FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST;
|
|
}*/
|
|
|
|
SetSolidFlags( iSolidFlags );
|
|
|
|
|
|
|
|
SetEffects( pClonedEntity->GetEffects() | (EF_NODRAW | EF_NOSHADOW | EF_NORECEIVESHADOW) );
|
|
|
|
SetCollisionGroup( pClonedEntity->GetCollisionGroup() );
|
|
|
|
SetModelIndex( pClonedEntity->GetModelIndex() );
|
|
SetModelName( pClonedEntity->GetModelName() );
|
|
|
|
if( modelinfo->GetModelType( pClonedEntity->GetModel() ) == mod_studio )
|
|
SetModel( STRING( pClonedEntity->GetModelName() ) );
|
|
|
|
|
|
CCollisionProperty *pClonedCollisionProp = pClonedEntity->CollisionProp();
|
|
SetSize( pClonedCollisionProp->OBBMins(), pClonedCollisionProp->OBBMaxs() );
|
|
}
|
|
|
|
FullSyncClonedPhysicsObjects( bBigChanges );
|
|
SyncEntity( true );
|
|
|
|
if( bBigChanges )
|
|
CollisionRulesChanged();
|
|
|
|
if( sv_debug_physicsshadowclones.GetBool() )
|
|
DrawDebugOverlayForShadowClone( this );
|
|
}
|
|
|
|
void CPhysicsShadowClone::SyncEntity( bool bPullChanges )
|
|
{
|
|
m_bShouldUpSync = false;
|
|
|
|
CBaseEntity *pSource, *pDest;
|
|
VMatrix *pTransform;
|
|
if( bPullChanges )
|
|
{
|
|
pSource = m_hClonedEntity.Get();
|
|
pDest = this;
|
|
pTransform = &m_matrixShadowTransform;
|
|
|
|
if( pSource == NULL )
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
pSource = this;
|
|
pDest = m_hClonedEntity.Get();
|
|
pTransform = &m_matrixShadowTransform_Inverse;
|
|
|
|
if( pDest == NULL )
|
|
return;
|
|
}
|
|
|
|
|
|
Vector ptOrigin, vVelocity;
|
|
QAngle qAngles;
|
|
|
|
ptOrigin = pSource->GetAbsOrigin();
|
|
qAngles = pSource->GetAbsAngles();
|
|
vVelocity = pSource->GetAbsVelocity();
|
|
|
|
if( !m_bShadowTransformIsIdentity )
|
|
{
|
|
ptOrigin = (*pTransform) * ptOrigin;
|
|
qAngles = TransformAnglesToWorldSpace( qAngles, pTransform->As3x4() );
|
|
vVelocity = pTransform->ApplyRotation( vVelocity );
|
|
}
|
|
//else
|
|
//{
|
|
// pDest->SetGroundEntity( pSource->GetGroundEntity() );
|
|
//}
|
|
|
|
if( (ptOrigin != pDest->GetAbsOrigin()) || (qAngles != pDest->GetAbsAngles()) )
|
|
{
|
|
pDest->Teleport( &ptOrigin, &qAngles, NULL );
|
|
}
|
|
|
|
if( vVelocity != pDest->GetAbsVelocity() )
|
|
{
|
|
//pDest->IncrementInterpolationFrame();
|
|
pDest->SetAbsVelocity( vec3_origin ); //the two step process helps, I don't know why, but it does
|
|
pDest->ApplyAbsVelocityImpulse( vVelocity );
|
|
}
|
|
}
|
|
|
|
|
|
static void FullSyncPhysicsObject( IPhysicsObject *pSource, IPhysicsObject *pDest, const VMatrix *pTransform, bool bTeleport )
|
|
{
|
|
CGrabController *pGrabController = NULL;
|
|
|
|
if( !pSource->IsAsleep() )
|
|
pDest->Wake();
|
|
|
|
float fSavedMass = 0.0f, fSavedRotationalDamping; //setting mass to 0.0f purely to kill a warning that I can't seem to kill with pragmas
|
|
if( pSource->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
|
|
{
|
|
//CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
|
|
//Assert( pPlayer );
|
|
|
|
CBaseEntity *pLookingForEntity = (CBaseEntity *)pSource->GetGameData();
|
|
|
|
CBasePlayer *pHoldingPlayer = GetPlayerHoldingEntity( pLookingForEntity );
|
|
if( pHoldingPlayer )
|
|
{
|
|
pGrabController = GetGrabControllerForPlayer( pHoldingPlayer );
|
|
|
|
if ( !pGrabController )
|
|
pGrabController = GetGrabControllerForPhysCannon( pHoldingPlayer->GetActiveWeapon() );
|
|
}
|
|
|
|
AssertMsg( pGrabController, "Physics object is held, but we can't find the holding controller." );
|
|
GetSavedParamsForCarriedPhysObject( pGrabController, pSource, &fSavedMass, &fSavedRotationalDamping );
|
|
}
|
|
|
|
//Boiler plate
|
|
{
|
|
pDest->SetGameIndex( pSource->GetGameIndex() ); //what's it do?
|
|
pDest->SetCallbackFlags( pSource->GetCallbackFlags() ); //wise?
|
|
pDest->SetGameFlags( pSource->GetGameFlags() | FVPHYSICS_NO_SELF_COLLISIONS | FVPHYSICS_IS_SHADOWCLONE );
|
|
pDest->SetMaterialIndex( pSource->GetMaterialIndex() );
|
|
pDest->SetContents( pSource->GetContents() );
|
|
|
|
pDest->EnableCollisions( pSource->IsCollisionEnabled() );
|
|
pDest->EnableGravity( pSource->IsGravityEnabled() );
|
|
pDest->EnableDrag( pSource->IsDragEnabled() );
|
|
pDest->EnableMotion( pSource->IsMotionEnabled() );
|
|
}
|
|
|
|
//Damping
|
|
{
|
|
float fSpeedDamp, fRotDamp;
|
|
if( pGrabController )
|
|
{
|
|
pSource->GetDamping( &fSpeedDamp, NULL );
|
|
pDest->SetDamping( &fSpeedDamp, &fSavedRotationalDamping );
|
|
}
|
|
else
|
|
{
|
|
pSource->GetDamping( &fSpeedDamp, &fRotDamp );
|
|
pDest->SetDamping( &fSpeedDamp, &fRotDamp );
|
|
}
|
|
}
|
|
|
|
//stuff that we really care about
|
|
{
|
|
if( pGrabController )
|
|
pDest->SetMass( fSavedMass );
|
|
else
|
|
pDest->SetMass( pSource->GetMass() );
|
|
|
|
Vector ptOrigin, vVelocity, vAngularVelocity, vInertia;
|
|
QAngle qAngles;
|
|
|
|
pSource->GetPosition( &ptOrigin, &qAngles );
|
|
pSource->GetVelocity( &vVelocity, &vAngularVelocity );
|
|
vInertia = pSource->GetInertia();
|
|
|
|
if( pTransform )
|
|
{
|
|
#if 0
|
|
pDest->SetPositionMatrix( pTransform->As3x4(), true ); //works like we think?
|
|
#else
|
|
ptOrigin = (*pTransform) * ptOrigin;
|
|
qAngles = TransformAnglesToWorldSpace( qAngles, pTransform->As3x4() );
|
|
vVelocity = pTransform->ApplyRotation( vVelocity );
|
|
vAngularVelocity = pTransform->ApplyRotation( vAngularVelocity );
|
|
#endif
|
|
}
|
|
|
|
//avoid oversetting variables (I think that even setting them to the same value they already are disrupts the delicate physics balance)
|
|
if( vInertia != pDest->GetInertia() )
|
|
pDest->SetInertia( vInertia );
|
|
|
|
Vector ptDestOrigin, vDestVelocity, vDestAngularVelocity;
|
|
QAngle qDestAngles;
|
|
pDest->GetPosition( &ptDestOrigin, &qDestAngles );
|
|
|
|
if( (ptOrigin != ptDestOrigin) || (qAngles != qDestAngles) )
|
|
pDest->SetPosition( ptOrigin, qAngles, bTeleport );
|
|
|
|
//pDest->SetVelocityInstantaneous( &vec3_origin, &vec3_origin );
|
|
//pDest->Sleep();
|
|
|
|
pDest->GetVelocity( &vDestVelocity, &vDestAngularVelocity );
|
|
|
|
if( (vVelocity != vDestVelocity) || (vAngularVelocity != vDestAngularVelocity) )
|
|
pDest->SetVelocityInstantaneous( &vVelocity, &vAngularVelocity );
|
|
|
|
IPhysicsShadowController *pSourceController = pSource->GetShadowController();
|
|
if( pSourceController == NULL )
|
|
{
|
|
if( pDest->GetShadowController() != NULL )
|
|
{
|
|
//we don't need a shadow controller anymore
|
|
pDest->RemoveShadowController();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IPhysicsShadowController *pDestController = pDest->GetShadowController();
|
|
if( pDestController == NULL )
|
|
{
|
|
//we need a shadow controller
|
|
float fMaxSpeed, fMaxAngularSpeed;
|
|
pSourceController->GetMaxSpeed( &fMaxSpeed, &fMaxAngularSpeed );
|
|
|
|
pDest->SetShadow( fMaxSpeed, fMaxAngularSpeed, pSourceController->AllowsTranslation(), pSourceController->AllowsRotation() );
|
|
pDestController = pDest->GetShadowController();
|
|
pDestController->SetTeleportDistance( pSourceController->GetTeleportDistance() );
|
|
pDestController->SetPhysicallyControlled( pSourceController->IsPhysicallyControlled() );
|
|
}
|
|
|
|
//sync shadow controllers
|
|
float fTimeOffset;
|
|
Vector ptTargetPosition;
|
|
QAngle qTargetAngles;
|
|
fTimeOffset = pSourceController->GetTargetPosition( &ptTargetPosition, &qTargetAngles );
|
|
|
|
if( pTransform )
|
|
{
|
|
ptTargetPosition = (*pTransform) * ptTargetPosition;
|
|
qTargetAngles = TransformAnglesToWorldSpace( qTargetAngles, pTransform->As3x4() );
|
|
}
|
|
|
|
pDestController->Update( ptTargetPosition, qTargetAngles, fTimeOffset );
|
|
}
|
|
|
|
|
|
}
|
|
|
|
//pDest->RecheckContactPoints();
|
|
}
|
|
|
|
static void PartialSyncPhysicsObject( IPhysicsObject *pSource, IPhysicsObject *pDest, const VMatrix *pTransform )
|
|
{
|
|
Vector ptOrigin, vVelocity, vAngularVelocity, vInertia;
|
|
QAngle qAngles;
|
|
|
|
pSource->GetPosition( &ptOrigin, &qAngles );
|
|
pSource->GetVelocity( &vVelocity, &vAngularVelocity );
|
|
vInertia = pSource->GetInertia();
|
|
|
|
if( pTransform )
|
|
{
|
|
#if 0
|
|
//pDest->SetPositionMatrix( matTransform.As3x4(), true ); //works like we think?
|
|
#else
|
|
ptOrigin = (*pTransform) * ptOrigin;
|
|
qAngles = TransformAnglesToWorldSpace( qAngles, pTransform->As3x4() );
|
|
vVelocity = pTransform->ApplyRotation( vVelocity );
|
|
vAngularVelocity = pTransform->ApplyRotation( vAngularVelocity );
|
|
#endif
|
|
}
|
|
|
|
//avoid oversetting variables (I think that even setting them to the same value they already are disrupts the delicate physics balance)
|
|
if( vInertia != pDest->GetInertia() )
|
|
pDest->SetInertia( vInertia );
|
|
|
|
Vector ptDestOrigin, vDestVelocity, vDestAngularVelocity;
|
|
QAngle qDestAngles;
|
|
pDest->GetPosition( &ptDestOrigin, &qDestAngles );
|
|
pDest->GetVelocity( &vDestVelocity, &vDestAngularVelocity );
|
|
|
|
|
|
if( (ptOrigin != ptDestOrigin) || (qAngles != qDestAngles) )
|
|
pDest->SetPosition( ptOrigin, qAngles, false );
|
|
|
|
if( (vVelocity != vDestVelocity) || (vAngularVelocity != vDestAngularVelocity) )
|
|
pDest->SetVelocity( &vVelocity, &vAngularVelocity );
|
|
|
|
pDest->EnableCollisions( pSource->IsCollisionEnabled() );
|
|
}
|
|
|
|
|
|
|
|
void CPhysicsShadowClone::FullSyncClonedPhysicsObjects( bool bTeleport )
|
|
{
|
|
CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
|
|
if( pClonedEntity == NULL )
|
|
{
|
|
VPhysicsDestroyObject();
|
|
return;
|
|
}
|
|
|
|
VMatrix *pTransform;
|
|
if( m_bShadowTransformIsIdentity )
|
|
pTransform = NULL;
|
|
else
|
|
pTransform = &m_matrixShadowTransform;
|
|
|
|
IPhysicsObject *(pSourceObjects[1024]);
|
|
int iObjectCount = pClonedEntity->VPhysicsGetObjectList( pSourceObjects, 1024 );
|
|
|
|
//easy out if nothing has changed
|
|
if( iObjectCount == m_CloneLinks.Count() )
|
|
{
|
|
int i;
|
|
for( i = 0; i != iObjectCount; ++i )
|
|
{
|
|
if( pSourceObjects[i] == NULL )
|
|
break;
|
|
|
|
if( pSourceObjects[i] != m_CloneLinks[i].pSource )
|
|
break;
|
|
}
|
|
|
|
if( i == iObjectCount ) //no changes
|
|
{
|
|
for( i = 0; i != iObjectCount; ++i )
|
|
FullSyncPhysicsObject( m_CloneLinks[i].pSource, m_CloneLinks[i].pClone, pTransform, bTeleport );
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//copy the existing list of clone links to a temp array, we're going to be starting from scratch and copying links as we need them
|
|
PhysicsObjectCloneLink_t *pExistingLinks = NULL;
|
|
int iExistingLinkCount = m_CloneLinks.Count();
|
|
if( iExistingLinkCount != 0 )
|
|
{
|
|
pExistingLinks = (PhysicsObjectCloneLink_t *)stackalloc( sizeof(PhysicsObjectCloneLink_t) * m_CloneLinks.Count() );
|
|
memcpy( pExistingLinks, m_CloneLinks.Base(), sizeof(PhysicsObjectCloneLink_t) * m_CloneLinks.Count() );
|
|
}
|
|
m_CloneLinks.RemoveAll();
|
|
|
|
//now, go over the object list we just got from the source entity, and either copy or create links as necessary
|
|
int i;
|
|
for( i = 0; i != iObjectCount; ++i )
|
|
{
|
|
IPhysicsObject *pSource = pSourceObjects[i];
|
|
|
|
if( pSource == NULL ) //this really shouldn't happen, but it does >_<
|
|
continue;
|
|
|
|
PhysicsObjectCloneLink_t cloneLink;
|
|
|
|
int j;
|
|
for( j = 0; j != iExistingLinkCount; ++j )
|
|
{
|
|
if( pExistingLinks[j].pSource == pSource )
|
|
break;
|
|
}
|
|
|
|
if( j != iExistingLinkCount )
|
|
{
|
|
//copyable link found
|
|
cloneLink = pExistingLinks[j];
|
|
memset( &pExistingLinks[j], 0, sizeof( PhysicsObjectCloneLink_t ) ); //zero out this slot so we don't destroy it in cleanup
|
|
}
|
|
else
|
|
{
|
|
//no link found to copy, create a new one
|
|
cloneLink.pSource = pSource;
|
|
|
|
//apparently some collision code gets called on creation before we've set extra game flags, so we're going to cheat a bit and temporarily set our extra flags on the source
|
|
unsigned int iOldGameFlags = pSource->GetGameFlags();
|
|
pSource->SetGameFlags( iOldGameFlags | FVPHYSICS_IS_SHADOWCLONE );
|
|
|
|
unsigned int size = physenv->GetObjectSerializeSize(pSource);
|
|
byte *pBuffer = (byte *)stackalloc(size);
|
|
memset( pBuffer, 0, size );
|
|
|
|
physenv->SerializeObjectToBuffer( pSource, pBuffer, size ); //this should work across physics environments because the serializer doesn't write anything about itself to the template
|
|
pSource->SetGameFlags( iOldGameFlags );
|
|
cloneLink.pClone = m_pOwnerPhysEnvironment->UnserializeObjectFromBuffer( this, pBuffer, size, false ); //unserializer has to be in the target environment
|
|
assert( cloneLink.pClone ); //there should be absolutely no case where we can't clone a valid existing physics object
|
|
|
|
stackfree(pBuffer);
|
|
}
|
|
|
|
FullSyncPhysicsObject( cloneLink.pSource, cloneLink.pClone, pTransform, bTeleport );
|
|
|
|
//cloneLink.pClone->Wake();
|
|
|
|
m_CloneLinks.AddToTail( cloneLink );
|
|
}
|
|
|
|
|
|
//now go over the existing links, if any of them haven't been nullified, they need to be deleted
|
|
for( i = 0; i != iExistingLinkCount; ++i )
|
|
{
|
|
if( pExistingLinks[i].pClone )
|
|
m_pOwnerPhysEnvironment->DestroyObject( pExistingLinks[i].pClone ); //also destroys shadow controller
|
|
}
|
|
|
|
|
|
VPhysicsSetObject( NULL );
|
|
|
|
IPhysicsObject *pSource = m_hClonedEntity->VPhysicsGetObject();
|
|
|
|
for( i = m_CloneLinks.Count(); --i >= 0; )
|
|
{
|
|
if( m_CloneLinks[i].pSource == pSource )
|
|
{
|
|
//m_CloneLinks[i].pClone->Wake();
|
|
VPhysicsSetObject( m_CloneLinks[i].pClone );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( (i < 0) && (m_CloneLinks.Count() != 0) )
|
|
{
|
|
VPhysicsSetObject( m_CloneLinks[0].pClone );
|
|
}
|
|
|
|
stackfree( pExistingLinks );
|
|
|
|
//CollisionRulesChanged();
|
|
}
|
|
|
|
|
|
|
|
void CPhysicsShadowClone::PartialSync( bool bPullChanges )
|
|
{
|
|
VMatrix *pTransform;
|
|
|
|
if( bPullChanges )
|
|
{
|
|
if( m_bShadowTransformIsIdentity )
|
|
pTransform = NULL;
|
|
else
|
|
pTransform = &m_matrixShadowTransform;
|
|
|
|
for( int i = m_CloneLinks.Count(); --i >= 0; )
|
|
PartialSyncPhysicsObject( m_CloneLinks[i].pSource, m_CloneLinks[i].pClone, pTransform );
|
|
}
|
|
else
|
|
{
|
|
if( m_bShadowTransformIsIdentity )
|
|
pTransform = NULL;
|
|
else
|
|
pTransform = &m_matrixShadowTransform_Inverse;
|
|
|
|
for( int i = m_CloneLinks.Count(); --i >= 0; )
|
|
PartialSyncPhysicsObject( m_CloneLinks[i].pClone, m_CloneLinks[i].pSource, pTransform );
|
|
}
|
|
|
|
SyncEntity( bPullChanges );
|
|
}
|
|
|
|
|
|
|
|
int CPhysicsShadowClone::VPhysicsGetObjectList( IPhysicsObject **pList, int listMax )
|
|
{
|
|
int iCountStop = m_CloneLinks.Count();
|
|
if( iCountStop > listMax )
|
|
iCountStop = listMax;
|
|
|
|
for( int i = 0; i != iCountStop; ++i, ++pList )
|
|
*pList = m_CloneLinks[i].pClone;
|
|
|
|
return iCountStop;
|
|
}
|
|
|
|
|
|
void CPhysicsShadowClone::VPhysicsDestroyObject( void )
|
|
{
|
|
VPhysicsSetObject( NULL );
|
|
|
|
for( int i = m_CloneLinks.Count(); --i >= 0; )
|
|
{
|
|
Assert( m_CloneLinks[i].pClone != NULL );
|
|
m_pOwnerPhysEnvironment->DestroyObject( m_CloneLinks[i].pClone );
|
|
}
|
|
m_CloneLinks.RemoveAll();
|
|
|
|
SetMoveType( MOVETYPE_NONE );
|
|
SetSolid( SOLID_NONE );
|
|
SetSolidFlags( 0 );
|
|
SetCollisionGroup( COLLISION_GROUP_NONE );
|
|
|
|
BaseClass::VPhysicsDestroyObject();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool CPhysicsShadowClone::ShouldCollide( int collisionGroup, int contentsMask ) const
|
|
{
|
|
CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
|
|
|
|
if( pClonedEntity )
|
|
return pClonedEntity->ShouldCollide( collisionGroup, contentsMask );
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool CPhysicsShadowClone::TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& trace )
|
|
{
|
|
return false;
|
|
|
|
/*CBaseEntity *pSourceEntity = m_hClonedEntity.Get();
|
|
if( pSourceEntity == NULL )
|
|
return false;
|
|
|
|
enginetrace->ClipRayToEntity( ray, fContentsMask, pSourceEntity, &trace );
|
|
return trace.DidHit();*/
|
|
}
|
|
|
|
int CPhysicsShadowClone::ObjectCaps( void )
|
|
{
|
|
return ((BaseClass::ObjectCaps() | FCAP_DONT_SAVE) & ~(FCAP_FORCE_TRANSITION | FCAP_ACROSS_TRANSITION | FCAP_MUST_SPAWN | FCAP_SAVE_NON_NETWORKABLE));
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CPhysicsShadowClone::SetCloneTransformationMatrix( const matrix3x4_t &sourceMatrix )
|
|
{
|
|
m_matrixShadowTransform = sourceMatrix;
|
|
m_bShadowTransformIsIdentity = m_matrixShadowTransform.IsIdentity();
|
|
|
|
if( m_matrixShadowTransform.InverseGeneral( m_matrixShadowTransform_Inverse ) == false )
|
|
{
|
|
m_matrixShadowTransform.InverseTR( m_matrixShadowTransform_Inverse ); //probably not the right matrix, but we're out of options
|
|
}
|
|
|
|
FullSync();
|
|
//PartialSync( true );
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void CPhysicsShadowClone::SetClonedEntity( EHANDLE hEntToClone )
|
|
{
|
|
VPhysicsDestroyObject();
|
|
|
|
m_hClonedEntity = hEntToClone;
|
|
|
|
//FullSyncClonedPhysicsObjects();
|
|
}
|
|
|
|
EHANDLE CPhysicsShadowClone::GetClonedEntity( void )
|
|
{
|
|
return m_hClonedEntity;
|
|
}
|
|
|
|
|
|
|
|
|
|
//damage relays to source entity
|
|
bool CPhysicsShadowClone::PassesDamageFilter( const CTakeDamageInfo &info )
|
|
{
|
|
CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
|
|
|
|
if( pClonedEntity )
|
|
return pClonedEntity->PassesDamageFilter( info );
|
|
else
|
|
return BaseClass::PassesDamageFilter( info );
|
|
}
|
|
|
|
bool CPhysicsShadowClone::CanBeHitByMeleeAttack( CBaseEntity *pAttacker )
|
|
{
|
|
CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
|
|
|
|
if( pClonedEntity )
|
|
return pClonedEntity->CanBeHitByMeleeAttack( pAttacker );
|
|
else
|
|
return BaseClass::CanBeHitByMeleeAttack( pAttacker );
|
|
}
|
|
|
|
int CPhysicsShadowClone::OnTakeDamage( const CTakeDamageInfo &info )
|
|
{
|
|
CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
|
|
|
|
if( pClonedEntity )
|
|
return pClonedEntity->OnTakeDamage( info );
|
|
else
|
|
return BaseClass::OnTakeDamage( info );
|
|
}
|
|
|
|
int CPhysicsShadowClone::TakeHealth( float flHealth, int bitsDamageType )
|
|
{
|
|
CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
|
|
|
|
if( pClonedEntity )
|
|
return pClonedEntity->TakeHealth( flHealth, bitsDamageType );
|
|
else
|
|
return BaseClass::TakeHealth( flHealth, bitsDamageType );
|
|
}
|
|
|
|
void CPhysicsShadowClone::Event_Killed( const CTakeDamageInfo &info )
|
|
{
|
|
CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
|
|
|
|
if( pClonedEntity )
|
|
pClonedEntity->Event_Killed( info );
|
|
else
|
|
BaseClass::Event_Killed( info );
|
|
}
|
|
|
|
CPhysicsShadowClone *CPhysicsShadowClone::CreateShadowClone( IPhysicsEnvironment *pInPhysicsEnvironment, EHANDLE hEntToClone, const char *szDebugMarker, const matrix3x4_t *pTransformationMatrix /*= NULL*/ )
|
|
{
|
|
AssertMsg( szDebugMarker != NULL, "All shadow clones must have a debug marker for where it came from in debug builds." );
|
|
|
|
if( !sv_use_shadow_clones.GetBool() )
|
|
return NULL;
|
|
|
|
CBaseEntity *pClonedEntity = hEntToClone.Get();
|
|
if( pClonedEntity == NULL )
|
|
return NULL;
|
|
|
|
AssertMsg( IsShadowClone( pClonedEntity ) == false, "Shouldn't attempt to clone clones" );
|
|
|
|
if( pClonedEntity->IsMarkedForDeletion() )
|
|
return NULL;
|
|
|
|
//if( pClonedEntity->IsPlayer() )
|
|
// return NULL;
|
|
|
|
IPhysicsObject *pPhysics = pClonedEntity->VPhysicsGetObject();
|
|
|
|
if( pPhysics == NULL )
|
|
return NULL;
|
|
|
|
if( pPhysics->IsStatic() )
|
|
return NULL;
|
|
|
|
if( pClonedEntity->GetSolid() == SOLID_BSP )
|
|
return NULL;
|
|
|
|
if( pClonedEntity->GetSolidFlags() & (FSOLID_NOT_SOLID | FSOLID_TRIGGER) )
|
|
return NULL;
|
|
|
|
if( pClonedEntity->GetFlags() & (FL_WORLDBRUSH | FL_STATICPROP) )
|
|
return NULL;
|
|
|
|
/*if( FClassnameIs( pClonedEntity, "func_door" ) )
|
|
{
|
|
//only clone func_door's that are in front of the portal
|
|
|
|
return NULL;
|
|
}*/
|
|
|
|
// Too many shadow clones breaks the game (too many entities)
|
|
if( g_iShadowCloneCount >= MAX_SHADOW_CLONE_COUNT )
|
|
{
|
|
AssertMsg( false, "Too many shadow clones, consider upping the limit or reducing the level's physics props" );
|
|
return NULL;
|
|
}
|
|
++g_iShadowCloneCount;
|
|
|
|
CPhysicsShadowClone *pClone = (CPhysicsShadowClone*)CreateEntityByName("physicsshadowclone");
|
|
s_IsShadowClone[pClone->entindex()] = true;
|
|
pClone->m_pOwnerPhysEnvironment = pInPhysicsEnvironment;
|
|
pClone->m_hClonedEntity = hEntToClone;
|
|
DBG_CODE_NOSCOPE( pClone->m_szDebugMarker = szDebugMarker; );
|
|
|
|
CPhysicsShadowCloneLL *pCloneLLEntry = s_SCLLManager.Alloc();
|
|
pCloneLLEntry->pClone = pClone;
|
|
pCloneLLEntry->pNext = s_EntityClones[pClonedEntity->entindex()];
|
|
s_EntityClones[pClonedEntity->entindex()] = pCloneLLEntry;
|
|
|
|
if( pTransformationMatrix )
|
|
{
|
|
pClone->m_matrixShadowTransform = *pTransformationMatrix;
|
|
pClone->m_bShadowTransformIsIdentity = pClone->m_matrixShadowTransform.IsIdentity();
|
|
|
|
if( !pClone->m_bShadowTransformIsIdentity )
|
|
{
|
|
if( pClone->m_matrixShadowTransform.InverseGeneral( pClone->m_matrixShadowTransform_Inverse ) == false )
|
|
{
|
|
pClone->m_matrixShadowTransform.InverseTR( pClone->m_matrixShadowTransform_Inverse ); //probably not the right matrix, but we're out of options
|
|
}
|
|
}
|
|
}
|
|
|
|
DispatchSpawn( pClone );
|
|
|
|
return pClone;
|
|
}
|
|
|
|
void CPhysicsShadowClone::Free( void )
|
|
{
|
|
VPhysicsDestroyObject();
|
|
|
|
UTIL_Remove( this );
|
|
|
|
//Too many shadow clones breaks the game (too many entities)
|
|
--g_iShadowCloneCount;
|
|
}
|
|
|
|
|
|
void CPhysicsShadowClone::FullSyncAllClones( void )
|
|
{
|
|
for( int i = s_ActiveShadowClones.Count(); --i >= 0; )
|
|
{
|
|
s_ActiveShadowClones[i]->FullSync( true );
|
|
}
|
|
}
|
|
|
|
|
|
IPhysicsObject *CPhysicsShadowClone::TranslatePhysicsToClonedEnt( const IPhysicsObject *pPhysics )
|
|
{
|
|
if( m_hClonedEntity.Get() != NULL )
|
|
{
|
|
for( int i = m_CloneLinks.Count(); --i >= 0; )
|
|
{
|
|
if( m_CloneLinks[i].pClone == pPhysics )
|
|
return m_CloneLinks[i].pSource;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void CPhysicsShadowClone::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
|
|
{
|
|
//the baseclass just screenshakes, makes sounds, and outputs dust, we rely on the original entity to do this when applicable
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CPhysicsShadowClone::IsShadowClone( const CBaseEntity *pEntity )
|
|
{
|
|
return s_IsShadowClone[pEntity->entindex()];
|
|
}
|
|
|
|
CPhysicsShadowCloneLL *CPhysicsShadowClone::GetClonesOfEntity( const CBaseEntity *pEntity )
|
|
{
|
|
return s_EntityClones[pEntity->entindex()];
|
|
}
|
|
|
|
|
|
|
|
static void DrawDebugOverlayForShadowClone( CPhysicsShadowClone *pClone )
|
|
{
|
|
unsigned char iColorIntensity = (pClone->IsInAssumedSyncState())?(127):(255);
|
|
|
|
int iRed = (pClone->IsUntransformedClone())?(0):(iColorIntensity);
|
|
int iGreen = iColorIntensity;
|
|
int iBlue = iColorIntensity;
|
|
|
|
NDebugOverlay::EntityBounds( pClone, iRed, iGreen, iBlue, (iColorIntensity>>2), 0.05f );
|
|
}
|
|
|
|
|
|
bool CTraceFilterTranslateClones::ShouldHitEntity( IHandleEntity *pEntity, int contentsMask )
|
|
{
|
|
CBaseEntity *pEnt = EntityFromEntityHandle( pEntity );
|
|
if( CPhysicsShadowClone::IsShadowClone( pEnt ) )
|
|
{
|
|
CBaseEntity *pClonedEntity = ((CPhysicsShadowClone *)pEnt)->GetClonedEntity();
|
|
CPortalSimulator *pSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pClonedEntity );
|
|
if( pSimulator->m_DataAccess.Simulation.Dynamic.EntFlags[pClonedEntity->entindex()] & PSEF_IS_IN_PORTAL_HOLE )
|
|
return m_pActualFilter->ShouldHitEntity( pClonedEntity, contentsMask );
|
|
else
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return m_pActualFilter->ShouldHitEntity( pEntity, contentsMask );
|
|
}
|
|
}
|
|
|
|
TraceType_t CTraceFilterTranslateClones::GetTraceType() const
|
|
{
|
|
return m_pActualFilter->GetTraceType();
|
|
}
|
|
|
|
|
|
|