//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Interface layer for ipion IVP physics.
//
// $Workfile: $
// $Date: $
// $NoKeywords: $
//===========================================================================//
# include "cbase.h"
# include "coordsize.h"
# include "entitylist.h"
# include "vcollide_parse.h"
# include "soundenvelope.h"
# include "game.h"
# include "utlvector.h"
# include "init_factory.h"
# include "igamesystem.h"
# include "hierarchy.h"
# include "IEffects.h"
# include "engine/IEngineSound.h"
# include "world.h"
# include "decals.h"
# include "physics_fx.h"
# include "vphysics_sound.h"
# include "vphysics/vehicles.h"
# include "vehicle_sounds.h"
# include "movevars_shared.h"
# include "physics_saverestore.h"
# include "solidsetdefaults.h"
# include "tier0/vprof.h"
# include "engine/IStaticPropMgr.h"
# include "physics_prop_ragdoll.h"
# if HL2_EPISODIC
# include "particle_parse.h"
# endif
# include "vphysics/object_hash.h"
# include "vphysics/collision_set.h"
# include "vphysics/friction.h"
# include "fmtstr.h"
# include "physics_npc_solver.h"
# include "physics_collisionevent.h"
# include "vphysics/performance.h"
# include "positionwatcher.h"
# include "tier1/callqueue.h"
# include "vphysics/constraints.h"
# ifdef PORTAL
# include "portal_physics_collisionevent.h"
# include "physicsshadowclone.h"
# include "PortalSimulation.h"
void PortalPhysFrame ( float deltaTime ) ; //small wrapper for PhysFrame that simulates all 3 environments at once
# endif
void PrecachePhysicsSounds ( void ) ;
// memdbgon must be the last include file in a .cpp file!!!
# include "tier0/memdbgon.h"
ConVar phys_speeds ( " phys_speeds " , " 0 " ) ;
// defined in phys_constraint
extern IPhysicsConstraintEvent * g_pConstraintEvents ;
CEntityList * g_pShadowEntities = NULL ;
# ifdef PORTAL
CEntityList * g_pShadowEntities_Main = NULL ;
# endif
// local variables
static float g_PhysAverageSimTime ;
CCallQueue g_PostSimulationQueue ;
// local roeutines
static IPhysicsObject * PhysCreateWorld ( CBaseEntity * pWorld ) ;
static void PhysFrame ( float deltaTime ) ;
static bool IsDebris ( int collisionGroup ) ;
void TimescaleChanged ( IConVar * var , const char * pOldString , float flOldValue )
{
if ( physenv )
{
physenv - > ResetSimulationClock ( ) ;
}
}
ConVar phys_timescale ( " phys_timescale " , " 1 " , 0 , " Scale time for physics " , TimescaleChanged ) ;
# if _DEBUG
ConVar phys_dontprintint ( " phys_dontprintint " , " 1 " , FCVAR_NONE , " Don't print inter-penetration warnings. " ) ;
# endif
# ifdef PORTAL
CPortal_CollisionEvent g_Collisions ;
# else
CCollisionEvent g_Collisions ;
# endif
IPhysicsCollisionSolver * const g_pCollisionSolver = & g_Collisions ;
IPhysicsCollisionEvent * const g_pCollisionEventHandler = & g_Collisions ;
IPhysicsObjectEvent * const g_pObjectEventHandler = & g_Collisions ;
struct vehiclescript_t
{
string_t scriptName ;
vehicleparams_t params ;
vehiclesounds_t sounds ;
} ;
class CPhysicsHook : public CBaseGameSystemPerFrame
{
public :
virtual const char * Name ( ) { return " CPhysicsHook " ; }
virtual bool Init ( ) ;
virtual void LevelInitPreEntity ( ) ;
virtual void LevelInitPostEntity ( ) ;
virtual void LevelShutdownPreEntity ( ) ;
virtual void LevelShutdownPostEntity ( ) ;
virtual void FrameUpdatePostEntityThink ( ) ;
virtual void PreClientUpdate ( ) ;
bool FindOrAddVehicleScript ( const char * pScriptName , vehicleparams_t * pVehicle , vehiclesounds_t * pSounds ) ;
void FlushVehicleScripts ( )
{
m_vehicleScripts . RemoveAll ( ) ;
}
bool ShouldSimulate ( )
{
return ( physenv & & ! m_bPaused ) ? true : false ;
}
physicssound : : soundlist_t m_impactSounds ;
CUtlVector < physicssound : : breaksound_t > m_breakSounds ;
CUtlVector < masscenteroverride_t > m_massCenterOverrides ;
CUtlVector < vehiclescript_t > m_vehicleScripts ;
float m_impactSoundTime ;
bool m_bPaused ;
bool m_isFinalTick ;
} ;
CPhysicsHook g_PhysicsHook ;
//-----------------------------------------------------------------------------
// Singleton access
//-----------------------------------------------------------------------------
IGameSystem * PhysicsGameSystem ( )
{
return & g_PhysicsHook ;
}
//-----------------------------------------------------------------------------
// Purpose: The physics hook callback implementations
//-----------------------------------------------------------------------------
bool CPhysicsHook : : Init ( void )
{
factorylist_t factories ;
// Get the list of interface factories to extract the physics DLL's factory
FactoryList_Retrieve ( factories ) ;
if ( ! factories . physicsFactory )
return false ;
if ( ( physics = ( IPhysics * ) factories . physicsFactory ( VPHYSICS_INTERFACE_VERSION , NULL ) ) = = NULL | |
( physcollision = ( IPhysicsCollision * ) factories . physicsFactory ( VPHYSICS_COLLISION_INTERFACE_VERSION , NULL ) ) = = NULL | |
( physprops = ( IPhysicsSurfaceProps * ) factories . physicsFactory ( VPHYSICS_SURFACEPROPS_INTERFACE_VERSION , NULL ) ) = = NULL
)
return false ;
PhysParseSurfaceData ( physprops , filesystem ) ;
m_isFinalTick = true ;
m_impactSoundTime = 0 ;
m_vehicleScripts . EnsureCapacity ( 4 ) ;
return true ;
}
// a little debug wrapper to help fix bugs when entity pointers get trashed
#if 0
struct physcheck_t
{
IPhysicsObject * pPhys ;
char string [ 512 ] ;
} ;
CUtlVector < physcheck_t > physCheck ;
void PhysCheckAdd ( IPhysicsObject * pPhys , const char * pString )
{
physcheck_t tmp ;
tmp . pPhys = pPhys ;
Q_strncpy ( tmp . string , pString , sizeof ( tmp . string ) ) ;
physCheck . AddToTail ( tmp ) ;
}
const char * PhysCheck ( IPhysicsObject * pPhys )
{
for ( int i = 0 ; i < physCheck . Size ( ) ; i + + )
{
if ( physCheck [ i ] . pPhys = = pPhys )
return physCheck [ i ] . string ;
}
return " unknown " ;
}
# endif
void CPhysicsHook : : LevelInitPreEntity ( )
{
physenv = physics - > CreateEnvironment ( ) ;
physics_performanceparams_t params ;
params . Defaults ( ) ;
params . maxCollisionsPerObjectPerTimestep = 10 ;
physenv - > SetPerformanceSettings ( & params ) ;
# ifdef PORTAL
physenv_main = physenv ;
# endif
{
g_EntityCollisionHash = physics - > CreateObjectPairHash ( ) ;
}
factorylist_t factories ;
FactoryList_Retrieve ( factories ) ;
physenv - > SetDebugOverlay ( factories . engineFactory ) ;
physenv - > EnableDeleteQueue ( true ) ;
physenv - > SetCollisionSolver ( & g_Collisions ) ;
physenv - > SetCollisionEventHandler ( & g_Collisions ) ;
physenv - > SetConstraintEventHandler ( g_pConstraintEvents ) ;
physenv - > EnableConstraintNotify ( true ) ; // callback when an object gets deleted that is attached to a constraint
physenv - > SetObjectEventHandler ( & g_Collisions ) ;
physenv - > SetSimulationTimestep ( gpGlobals - > interval_per_tick ) ; // 15 ms per tick
// HL Game gravity, not real-world gravity
physenv - > SetGravity ( Vector ( 0 , 0 , - GetCurrentGravity ( ) ) ) ;
g_PhysAverageSimTime = 0 ;
g_PhysWorldObject = PhysCreateWorld ( GetWorldEntity ( ) ) ;
g_pShadowEntities = new CEntityList ;
# ifdef PORTAL
g_pShadowEntities_Main = g_pShadowEntities ;
# endif
PrecachePhysicsSounds ( ) ;
m_bPaused = true ;
}
void CPhysicsHook : : LevelInitPostEntity ( )
{
m_bPaused = false ;
}
void CPhysicsHook : : LevelShutdownPreEntity ( )
{
if ( ! physenv )
return ;
physenv - > SetQuickDelete ( true ) ;
}
void CPhysicsHook : : LevelShutdownPostEntity ( )
{
if ( ! physenv )
return ;
g_pPhysSaveRestoreManager - > ForgetAllModels ( ) ;
g_Collisions . LevelShutdown ( ) ;
physics - > DestroyEnvironment ( physenv ) ;
physenv = NULL ;
physics - > DestroyObjectPairHash ( g_EntityCollisionHash ) ;
g_EntityCollisionHash = NULL ;
physics - > DestroyAllCollisionSets ( ) ;
g_PhysWorldObject = NULL ;
delete g_pShadowEntities ;
g_pShadowEntities = NULL ;
m_impactSounds . RemoveAll ( ) ;
m_breakSounds . RemoveAll ( ) ;
m_massCenterOverrides . Purge ( ) ;
FlushVehicleScripts ( ) ;
}
bool CPhysicsHook : : FindOrAddVehicleScript ( const char * pScriptName , vehicleparams_t * pVehicle , vehiclesounds_t * pSounds )
{
bool bLoadedSounds = false ;
int index = - 1 ;
for ( int i = 0 ; i < m_vehicleScripts . Count ( ) ; i + + )
{
if ( ! Q_stricmp ( m_vehicleScripts [ i ] . scriptName . ToCStr ( ) , pScriptName ) )
{
index = i ;
bLoadedSounds = true ;
break ;
}
}
if ( index < 0 )
{
byte * pFile = UTIL_LoadFileForMe ( pScriptName , NULL ) ;
if ( pFile )
{
// new script, parse it and write to the table
index = m_vehicleScripts . AddToTail ( ) ;
m_vehicleScripts [ index ] . scriptName = AllocPooledString ( pScriptName ) ;
m_vehicleScripts [ index ] . sounds . Init ( ) ;
IVPhysicsKeyParser * pParse = physcollision - > VPhysicsKeyParserCreate ( ( char * ) pFile ) ;
while ( ! pParse - > Finished ( ) )
{
const char * pBlock = pParse - > GetCurrentBlockName ( ) ;
if ( ! strcmpi ( pBlock , " vehicle " ) )
{
pParse - > ParseVehicle ( & m_vehicleScripts [ index ] . params , NULL ) ;
}
else if ( ! Q_stricmp ( pBlock , " vehicle_sounds " ) )
{
bLoadedSounds = true ;
CVehicleSoundsParser soundParser ;
pParse - > ParseCustom ( & m_vehicleScripts [ index ] . sounds , & soundParser ) ;
}
else
{
pParse - > SkipBlock ( ) ;
}
}
physcollision - > VPhysicsKeyParserDestroy ( pParse ) ;
UTIL_FreeFile ( pFile ) ;
}
}
if ( index > = 0 )
{
if ( pVehicle )
{
* pVehicle = m_vehicleScripts [ index ] . params ;
}
if ( pSounds )
{
// We must pass back valid data here!
if ( bLoadedSounds = = false )
return false ;
* pSounds = m_vehicleScripts [ index ] . sounds ;
}
return true ;
}
return false ;
}
// called after entities think
void CPhysicsHook : : FrameUpdatePostEntityThink ( )
{
VPROF_BUDGET ( " CPhysicsHook::FrameUpdatePostEntityThink " , VPROF_BUDGETGROUP_PHYSICS ) ;
// Tracker 24846: If game is paused, don't simulate vphysics
float interval = ( gpGlobals - > frametime > 0.0f ) ? TICK_INTERVAL : 0.0f ;
// update the physics simulation, not we don't use gpGlobals->frametime, since that can be 30 msec or 15 msec
// depending on whether IsSimulatingOnAlternateTicks is true or not
if ( CBaseEntity : : IsSimulatingOnAlternateTicks ( ) )
{
m_isFinalTick = false ;
# ifdef PORTAL //slight detour if we're the portal mod
PortalPhysFrame ( interval ) ;
# else
PhysFrame ( interval ) ;
# endif
}
m_isFinalTick = true ;
# ifdef PORTAL //slight detour if we're the portal mod
PortalPhysFrame ( interval ) ;
# else
PhysFrame ( interval ) ;
# endif
}
void CPhysicsHook : : PreClientUpdate ( )
{
m_impactSoundTime + = gpGlobals - > frametime ;
if ( m_impactSoundTime > 0.05f )
{
physicssound : : PlayImpactSounds ( m_impactSounds ) ;
m_impactSoundTime = 0.0f ;
physicssound : : PlayBreakSounds ( m_breakSounds ) ;
}
}
bool PhysIsFinalTick ( )
{
return g_PhysicsHook . m_isFinalTick ;
}
IPhysicsObject * PhysCreateWorld ( CBaseEntity * pWorld )
{
staticpropmgr - > CreateVPhysicsRepresentations ( physenv , & g_SolidSetup , pWorld ) ;
return PhysCreateWorld_Shared ( pWorld , modelinfo - > GetVCollide ( 1 ) , g_PhysDefaultObjectParams ) ;
}
// vehicle wheels can only collide with things that can't get stuck in them during game physics
// because they aren't in the game physics world at present
static bool WheelCollidesWith ( IPhysicsObject * pObj , CBaseEntity * pEntity )
{
# if defined( INVASION_DLL )
if ( pEntity - > GetCollisionGroup ( ) = = TFCOLLISION_GROUP_OBJECT )
return false ;
# endif
// Cull against interactive debris
if ( pEntity - > GetCollisionGroup ( ) = = COLLISION_GROUP_INTERACTIVE_DEBRIS )
return false ;
// Hit physics ents
if ( pEntity - > GetMoveType ( ) = = MOVETYPE_PUSH | | pEntity - > GetMoveType ( ) = = MOVETYPE_VPHYSICS | | pObj - > IsStatic ( ) )
return true ;
return false ;
}
CCollisionEvent : : CCollisionEvent ( )
{
m_inCallback = 0 ;
m_bBufferTouchEvents = false ;
m_lastTickFrictionError = 0 ;
}
int CCollisionEvent : : ShouldCollide ( IPhysicsObject * pObj0 , IPhysicsObject * pObj1 , void * pGameData0 , void * pGameData1 )
# if _DEBUG
{
int x0 = ShouldCollide_2 ( pObj0 , pObj1 , pGameData0 , pGameData1 ) ;
int x1 = ShouldCollide_2 ( pObj1 , pObj0 , pGameData1 , pGameData0 ) ;
Assert ( x0 = = x1 ) ;
return x0 ;
}
int CCollisionEvent : : ShouldCollide_2 ( IPhysicsObject * pObj0 , IPhysicsObject * pObj1 , void * pGameData0 , void * pGameData1 )
# endif
{
CallbackContext check ( this ) ;
CBaseEntity * pEntity0 = static_cast < CBaseEntity * > ( pGameData0 ) ;
CBaseEntity * pEntity1 = static_cast < CBaseEntity * > ( pGameData1 ) ;
if ( ! pEntity0 | | ! pEntity1 )
return 1 ;
unsigned short gameFlags0 = pObj0 - > GetGameFlags ( ) ;
unsigned short gameFlags1 = pObj1 - > GetGameFlags ( ) ;
if ( pEntity0 = = pEntity1 )
{
// allow all-or-nothing per-entity disable
if ( ( gameFlags0 | gameFlags1 ) & FVPHYSICS_NO_SELF_COLLISIONS )
return 0 ;
IPhysicsCollisionSet * pSet = physics - > FindCollisionSet ( pEntity0 - > GetModelIndex ( ) ) ;
if ( pSet )
return pSet - > ShouldCollide ( pObj0 - > GetGameIndex ( ) , pObj1 - > GetGameIndex ( ) ) ;
return 1 ;
}
// objects that are both constrained to the world don't collide with each other
if ( ( gameFlags0 & gameFlags1 ) & FVPHYSICS_CONSTRAINT_STATIC )
{
return 0 ;
}
// Special collision rules for vehicle wheels
// Their entity collides with stuff using the normal rules, but they
// have different rules than the vehicle body for various reasons.
// sort of a hack because we don't have spheres to represent them in the game
// world for speculative collisions.
if ( pObj0 - > GetCallbackFlags ( ) & CALLBACK_IS_VEHICLE_WHEEL )
{
if ( ! WheelCollidesWith ( pObj1 , pEntity1 ) )
return false ;
}
if ( pObj1 - > GetCallbackFlags ( ) & CALLBACK_IS_VEHICLE_WHEEL )
{
if ( ! WheelCollidesWith ( pObj0 , pEntity0 ) )
return false ;
}
if ( pEntity0 - > ForceVPhysicsCollide ( pEntity1 ) | | pEntity1 - > ForceVPhysicsCollide ( pEntity0 ) )
return 1 ;
if ( pEntity0 - > edict ( ) & & pEntity1 - > edict ( ) )
{
// don't collide with your owner
if ( pEntity0 - > GetOwnerEntity ( ) = = pEntity1 | | pEntity1 - > GetOwnerEntity ( ) = = pEntity0 )
return 0 ;
}
if ( pEntity0 - > GetMoveParent ( ) | | pEntity1 - > GetMoveParent ( ) )
{
CBaseEntity * pParent0 = pEntity0 - > GetRootMoveParent ( ) ;
CBaseEntity * pParent1 = pEntity1 - > GetRootMoveParent ( ) ;
// NOTE: Don't let siblings/parents collide. If you want this behavior, do it
// with constraints, not hierarchy!
if ( pParent0 = = pParent1 )
return 0 ;
if ( g_EntityCollisionHash - > IsObjectPairInHash ( pParent0 , pParent1 ) )
return 0 ;
IPhysicsObject * p0 = pParent0 - > VPhysicsGetObject ( ) ;
IPhysicsObject * p1 = pParent1 - > VPhysicsGetObject ( ) ;
if ( p0 & & p1 )
{
if ( g_EntityCollisionHash - > IsObjectPairInHash ( p0 , p1 ) )
return 0 ;
}
}
int solid0 = pEntity0 - > GetSolid ( ) ;
int solid1 = pEntity1 - > GetSolid ( ) ;
int nSolidFlags0 = pEntity0 - > GetSolidFlags ( ) ;
int nSolidFlags1 = pEntity1 - > GetSolidFlags ( ) ;
int movetype0 = pEntity0 - > GetMoveType ( ) ;
int movetype1 = pEntity1 - > GetMoveType ( ) ;
// entities with non-physical move parents or entities with MOVETYPE_PUSH
// are considered as "AI movers". They are unchanged by collision; they exert
// physics forces on the rest of the system.
bool aiMove0 = ( movetype0 = = MOVETYPE_PUSH ) ? true : false ;
bool aiMove1 = ( movetype1 = = MOVETYPE_PUSH ) ? true : false ;
if ( pEntity0 - > GetMoveParent ( ) )
{
// if the object & its parent are both MOVETYPE_VPHYSICS, then this must be a special case
// like a prop_ragdoll_attached
if ( ! ( movetype0 = = MOVETYPE_VPHYSICS & & pEntity0 - > GetRootMoveParent ( ) - > GetMoveType ( ) = = MOVETYPE_VPHYSICS ) )
{
aiMove0 = true ;
}
}
if ( pEntity1 - > GetMoveParent ( ) )
{
// if the object & its parent are both MOVETYPE_VPHYSICS, then this must be a special case.
if ( ! ( movetype1 = = MOVETYPE_VPHYSICS & & pEntity1 - > GetRootMoveParent ( ) - > GetMoveType ( ) = = MOVETYPE_VPHYSICS ) )
{
aiMove1 = true ;
}
}
// AI movers don't collide with the world/static/pinned objects or other AI movers
if ( ( aiMove0 & & ! pObj1 - > IsMoveable ( ) ) | |
( aiMove1 & & ! pObj0 - > IsMoveable ( ) ) | |
( aiMove0 & & aiMove1 ) )
return 0 ;
// two objects under shadow control should not collide. The AI will figure it out
if ( pObj0 - > GetShadowController ( ) & & pObj1 - > GetShadowController ( ) )
return 0 ;
// BRJ 1/24/03
// You can remove the assert if it's problematic; I *believe* this condition
// should be met, but I'm not sure.
//Assert ( (solid0 != SOLID_NONE) && (solid1 != SOLID_NONE) );
if ( ( solid0 = = SOLID_NONE ) | | ( solid1 = = SOLID_NONE ) )
return 0 ;
// not solid doesn't collide with anything
if ( ( nSolidFlags0 | nSolidFlags1 ) & FSOLID_NOT_SOLID )
{
// might be a vphysics trigger, collide with everything but "not solid"
if ( pObj0 - > IsTrigger ( ) & & ! ( nSolidFlags1 & FSOLID_NOT_SOLID ) )
return 1 ;
if ( pObj1 - > IsTrigger ( ) & & ! ( nSolidFlags0 & FSOLID_NOT_SOLID ) )
return 1 ;
return 0 ;
}
if ( ( nSolidFlags0 & FSOLID_TRIGGER ) & &
! ( solid1 = = SOLID_VPHYSICS | | solid1 = = SOLID_BSP | | movetype1 = = MOVETYPE_VPHYSICS ) )
return 0 ;
if ( ( nSolidFlags1 & FSOLID_TRIGGER ) & &
! ( solid0 = = SOLID_VPHYSICS | | solid0 = = SOLID_BSP | | movetype0 = = MOVETYPE_VPHYSICS ) )
return 0 ;
if ( ! g_pGameRules - > ShouldCollide ( pEntity0 - > GetCollisionGroup ( ) , pEntity1 - > GetCollisionGroup ( ) ) )
return 0 ;
// check contents
if ( ! ( pObj0 - > GetContents ( ) & pEntity1 - > PhysicsSolidMaskForEntity ( ) ) | | ! ( pObj1 - > GetContents ( ) & pEntity0 - > PhysicsSolidMaskForEntity ( ) ) )
return 0 ;
if ( g_EntityCollisionHash - > IsObjectPairInHash ( pGameData0 , pGameData1 ) )
return 0 ;
if ( g_EntityCollisionHash - > IsObjectPairInHash ( pObj0 , pObj1 ) )
return 0 ;
return 1 ;
}
bool FindMaxContact ( IPhysicsObject * pObject , float minForce , IPhysicsObject * * pOtherObject , Vector * contactPos , Vector * pForce )
{
float mass = pObject - > GetMass ( ) ;
float maxForce = minForce ;
* pOtherObject = NULL ;
IPhysicsFrictionSnapshot * pSnapshot = pObject - > CreateFrictionSnapshot ( ) ;
while ( pSnapshot - > IsValid ( ) )
{
IPhysicsObject * pOther = pSnapshot - > GetObject ( 1 ) ;
if ( pOther - > IsMoveable ( ) & & pOther - > GetMass ( ) > mass )
{
float force = pSnapshot - > GetNormalForce ( ) ;
if ( force > maxForce )
{
* pOtherObject = pOther ;
pSnapshot - > GetContactPoint ( * contactPos ) ;
pSnapshot - > GetSurfaceNormal ( * pForce ) ;
* pForce * = force ;
}
}
pSnapshot - > NextFrictionData ( ) ;
}
pObject - > DestroyFrictionSnapshot ( pSnapshot ) ;
if ( * pOtherObject )
return true ;
return false ;
}
bool CCollisionEvent : : ShouldFreezeObject ( IPhysicsObject * pObject )
{
extern bool PropIsGib ( CBaseEntity * pEntity ) ;
// for now, don't apply a per-object limit to ai MOVETYPE_PUSH objects
// NOTE: If this becomes a problem (too many collision checks this tick) we should add a path
// to inform the logic in VPhysicsUpdatePusher() about the limit being applied so
// that it doesn't falsely block the object when it's simply been temporarily frozen
// for performance reasons
CBaseEntity * pEntity = static_cast < CBaseEntity * > ( pObject - > GetGameData ( ) ) ;
if ( pEntity )
{
if ( pEntity - > GetMoveType ( ) = = MOVETYPE_PUSH )
return false ;
// don't limit vehicle collisions either, limit can make breaking through a pile of breakable
// props very hitchy
if ( pEntity - > GetServerVehicle ( ) & & ! ( pObject - > GetCallbackFlags ( ) & CALLBACK_IS_VEHICLE_WHEEL ) )
return false ;
}
// if we're freezing a debris object, then it's probably due to some kind of solver issue
// usually this is a large object resting on the debris object in question which is not
// very stable.
// After doing the experiment of constraining the dynamic range of mass while solving friction
// contacts, I like the results of this tradeoff better. So damage or remove the debris object
// wherever possible once we hit this case:
if ( IsDebris ( pEntity - > GetCollisionGroup ( ) ) & & ! pEntity - > IsNPC ( ) )
{
IPhysicsObject * pOtherObject = NULL ;
Vector contactPos ;
Vector force ;
// find the contact with the moveable object applying the most contact force
if ( FindMaxContact ( pObject , pObject - > GetMass ( ) * 10 , & pOtherObject , & contactPos , & force ) )
{
CBaseEntity * pOther = static_cast < CBaseEntity * > ( pOtherObject - > GetGameData ( ) ) ;
// this object can take damage, crush it
if ( pEntity - > m_takedamage > DAMAGE_EVENTS_ONLY )
{
CTakeDamageInfo dmgInfo ( pOther , pOther , force , contactPos , force . Length ( ) * 0.1f , DMG_CRUSH ) ;
PhysCallbackDamage ( pEntity , dmgInfo ) ;
}
else
{
// can't be damaged, so do something else:
if ( PropIsGib ( pEntity ) )
{
// it's always safe to delete gibs, so kill this one to avoid simulation problems
PhysCallbackRemove ( pEntity - > NetworkProp ( ) ) ;
}
else
{
// not a gib, create a solver:
// UNDONE: Add a property to override this in gameplay critical scenarios?
g_PostSimulationQueue . QueueCall ( EntityPhysics_CreateSolver , pOther , pEntity , true , 1.0f ) ;
}
}
}
}
return true ;
}
bool CCollisionEvent : : ShouldFreezeContacts ( IPhysicsObject * * pObjectList , int objectCount )
{
if ( m_lastTickFrictionError > gpGlobals - > tickcount | | m_lastTickFrictionError < ( gpGlobals - > tickcount - 1 ) )
{
DevWarning ( " Performance Warning: large friction system (%d objects)!!! \n " , objectCount ) ;
# if _DEBUG
for ( int i = 0 ; i < objectCount ; i + + )
{
CBaseEntity * pEntity = static_cast < CBaseEntity * > ( pObjectList [ i ] - > GetGameData ( ) ) ;
pEntity - > m_debugOverlays | = OVERLAY_ABSBOX_BIT | OVERLAY_PIVOT_BIT ;
}
# endif
}
m_lastTickFrictionError = gpGlobals - > tickcount ;
return false ;
}
// NOTE: these are fully edge triggered events
// called when an object wakes up (starts simulating)
void CCollisionEvent : : ObjectWake ( IPhysicsObject * pObject )
{
CBaseEntity * pEntity = static_cast < CBaseEntity * > ( pObject - > GetGameData ( ) ) ;
if ( pEntity & & pEntity - > HasDataObjectType ( VPHYSICSWATCHER ) )
{
ReportVPhysicsStateChanged ( pObject , pEntity , true ) ;
}
}
// called when an object goes to sleep (no longer simulating)
void CCollisionEvent : : ObjectSleep ( IPhysicsObject * pObject )
{
CBaseEntity * pEntity = static_cast < CBaseEntity * > ( pObject - > GetGameData ( ) ) ;
if ( pEntity & & pEntity - > HasDataObjectType ( VPHYSICSWATCHER ) )
{
ReportVPhysicsStateChanged ( pObject , pEntity , false ) ;
}
}
bool PhysShouldCollide ( IPhysicsObject * pObj0 , IPhysicsObject * pObj1 )
{
void * pGameData0 = pObj0 - > GetGameData ( ) ;
void * pGameData1 = pObj1 - > GetGameData ( ) ;
if ( ! pGameData0 | | ! pGameData1 )
return false ;
return g_Collisions . ShouldCollide ( pObj0 , pObj1 , pGameData0 , pGameData1 ) ? true : false ;
}
bool PhysIsInCallback ( )
{
if ( ( physenv & & physenv - > IsInSimulation ( ) ) | | g_Collisions . IsInCallback ( ) )
return true ;
return false ;
}
static void ReportPenetration ( CBaseEntity * pEntity , float duration )
{
if ( pEntity - > GetMoveType ( ) = = MOVETYPE_VPHYSICS )
{
if ( g_pDeveloper - > GetInt ( ) > 1 )
{
pEntity - > m_debugOverlays | = OVERLAY_ABSBOX_BIT ;
}
pEntity - > AddTimedOverlay ( UTIL_VarArgs ( " VPhysics Penetration Error (%s)! " , pEntity - > GetDebugName ( ) ) , duration ) ;
}
}
static bool IsDebris ( int collisionGroup )
{
switch ( collisionGroup )
{
case COLLISION_GROUP_DEBRIS :
case COLLISION_GROUP_INTERACTIVE_DEBRIS :
case COLLISION_GROUP_DEBRIS_TRIGGER :
return true ;
default :
break ;
}
return false ;
}
static void UpdateEntityPenetrationFlag ( CBaseEntity * pEntity , bool isPenetrating )
{
if ( ! pEntity )
return ;
IPhysicsObject * pList [ VPHYSICS_MAX_OBJECT_LIST_COUNT ] ;
int count = pEntity - > VPhysicsGetObjectList ( pList , ARRAYSIZE ( pList ) ) ;
for ( int i = 0 ; i < count ; i + + )
{
if ( ! pList [ i ] - > IsStatic ( ) )
{
if ( isPenetrating )
{
PhysSetGameFlags ( pList [ i ] , FVPHYSICS_PENETRATING ) ;
}
else
{
PhysClearGameFlags ( pList [ i ] , FVPHYSICS_PENETRATING ) ;
}
}
}
}
void CCollisionEvent : : GetListOfPenetratingEntities ( CBaseEntity * pSearch , CUtlVector < CBaseEntity * > & list )
{
for ( int i = m_penetrateEvents . Count ( ) - 1 ; i > = 0 ; - - i )
{
if ( m_penetrateEvents [ i ] . hEntity0 = = pSearch & & m_penetrateEvents [ i ] . hEntity1 . Get ( ) ! = NULL )
{
list . AddToTail ( m_penetrateEvents [ i ] . hEntity1 ) ;
}
else if ( m_penetrateEvents [ i ] . hEntity1 = = pSearch & & m_penetrateEvents [ i ] . hEntity0 . Get ( ) ! = NULL )
{
list . AddToTail ( m_penetrateEvents [ i ] . hEntity0 ) ;
}
}
}
void CCollisionEvent : : UpdatePenetrateEvents ( void )
{
for ( int i = m_penetrateEvents . Count ( ) - 1 ; i > = 0 ; - - i )
{
CBaseEntity * pEntity0 = m_penetrateEvents [ i ] . hEntity0 ;
CBaseEntity * pEntity1 = m_penetrateEvents [ i ] . hEntity1 ;
if ( m_penetrateEvents [ i ] . collisionState = = COLLSTATE_TRYDISABLE )
{
if ( pEntity0 & & pEntity1 )
{
IPhysicsObject * pObj0 = pEntity0 - > VPhysicsGetObject ( ) ;
if ( pObj0 )
{
PhysForceEntityToSleep ( pEntity0 , pObj0 ) ;
}
IPhysicsObject * pObj1 = pEntity1 - > VPhysicsGetObject ( ) ;
if ( pObj1 )
{
PhysForceEntityToSleep ( pEntity1 , pObj1 ) ;
}
m_penetrateEvents [ i ] . collisionState = COLLSTATE_DISABLED ;
continue ;
}
// missing entity or object, clear event
}
else if ( m_penetrateEvents [ i ] . collisionState = = COLLSTATE_TRYNPCSOLVER )
{
if ( pEntity0 & & pEntity1 )
{
CAI_BaseNPC * pNPC = pEntity0 - > MyNPCPointer ( ) ;
CBaseEntity * pBlocker = pEntity1 ;
if ( ! pNPC )
{
pNPC = pEntity1 - > MyNPCPointer ( ) ;
Assert ( pNPC ) ;
pBlocker = pEntity0 ;
}
NPCPhysics_CreateSolver ( pNPC , pBlocker , true , 1.0f ) ;
}
// transferred to solver, clear event
}
else if ( m_penetrateEvents [ i ] . collisionState = = COLLSTATE_TRYENTITYSOLVER )
{
if ( pEntity0 & & pEntity1 )
{
if ( ! IsDebris ( pEntity1 - > GetCollisionGroup ( ) ) | | pEntity1 - > GetMoveType ( ) ! = MOVETYPE_VPHYSICS )
{
CBaseEntity * pTmp = pEntity0 ;
pEntity0 = pEntity1 ;
pEntity1 = pTmp ;
}
EntityPhysics_CreateSolver ( pEntity0 , pEntity1 , true , 1.0f ) ;
}
// transferred to solver, clear event
}
else if ( gpGlobals - > curtime - m_penetrateEvents [ i ] . timeStamp > 1.0 )
{
if ( m_penetrateEvents [ i ] . collisionState = = COLLSTATE_DISABLED )
{
if ( pEntity0 & & pEntity1 )
{
IPhysicsObject * pObj0 = pEntity0 - > VPhysicsGetObject ( ) ;
IPhysicsObject * pObj1 = pEntity1 - > VPhysicsGetObject ( ) ;
if ( pObj0 & & pObj1 )
{
m_penetrateEvents [ i ] . collisionState = COLLSTATE_ENABLED ;
continue ;
}
}
}
// haven't penetrated for 1 second, so remove
}
else
{
// recent timestamp, don't remove the event yet
continue ;
}
// done, clear event
m_penetrateEvents . FastRemove ( i ) ;
UpdateEntityPenetrationFlag ( pEntity0 , false ) ;
UpdateEntityPenetrationFlag ( pEntity1 , false ) ;
}
}
penetrateevent_t & CCollisionEvent : : FindOrAddPenetrateEvent ( CBaseEntity * pEntity0 , CBaseEntity * pEntity1 )
{
int index = - 1 ;
for ( int i = m_penetrateEvents . Count ( ) - 1 ; i > = 0 ; - - i )
{
if ( m_penetrateEvents [ i ] . hEntity0 . Get ( ) = = pEntity0 & & m_penetrateEvents [ i ] . hEntity1 . Get ( ) = = pEntity1 )
{
index = i ;
break ;
}
}
if ( index < 0 )
{
index = m_penetrateEvents . AddToTail ( ) ;
penetrateevent_t & event = m_penetrateEvents [ index ] ;
event . hEntity0 = pEntity0 ;
event . hEntity1 = pEntity1 ;
event . startTime = gpGlobals - > curtime ;
event . collisionState = COLLSTATE_ENABLED ;
UpdateEntityPenetrationFlag ( pEntity0 , true ) ;
UpdateEntityPenetrationFlag ( pEntity1 , true ) ;
}
penetrateevent_t & event = m_penetrateEvents [ index ] ;
event . timeStamp = gpGlobals - > curtime ;
return event ;
}
static ConVar phys_penetration_error_time ( " phys_penetration_error_time " , " 10 " , 0 , " Controls the duration of vphysics penetration error boxes. " ) ;
static bool CanResolvePenetrationWithNPC ( CBaseEntity * pEntity , IPhysicsObject * pObject )
{
if ( pEntity - > GetMoveType ( ) = = MOVETYPE_VPHYSICS )
{
// hinged objects won't be able to be pushed out anyway, so don't try the npc solver
if ( ! pObject - > IsHinged ( ) & & ! pObject - > IsAttachedToConstraint ( true ) )
{
if ( pObject - > IsMoveable ( ) | | pEntity - > GetServerVehicle ( ) )
return true ;
}
}
return false ;
}
int CCollisionEvent : : ShouldSolvePenetration ( IPhysicsObject * pObj0 , IPhysicsObject * pObj1 , void * pGameData0 , void * pGameData1 , float dt )
{
CallbackContext check ( this ) ;
// Pointers to the entity for each physics object
CBaseEntity * pEntity0 = static_cast < CBaseEntity * > ( pGameData0 ) ;
CBaseEntity * pEntity1 = static_cast < CBaseEntity * > ( pGameData1 ) ;
// this can get called as entities are being constructed on the other side of a game load or level transition
// Some entities may not be fully constructed, so don't call into their code until the level is running
if ( g_PhysicsHook . m_bPaused )
return true ;
// solve it yourself here and return 0, or have the default implementation do it
if ( pEntity0 > pEntity1 )
{
// swap sort
CBaseEntity * pTmp = pEntity0 ;
pEntity0 = pEntity1 ;
pEntity1 = pTmp ;
IPhysicsObject * pTmpObj = pObj0 ;
pObj0 = pObj1 ;
pObj1 = pTmpObj ;
}
if ( pEntity0 = = pEntity1 )
{
if ( pObj0 - > GetGameFlags ( ) & FVPHYSICS_PART_OF_RAGDOLL )
{
DevMsg ( 2 , " Solving ragdoll self penetration! %s (%s) (%d v %d) \n " , pObj0 - > GetName ( ) , pEntity0 - > GetDebugName ( ) , pObj0 - > GetGameIndex ( ) , pObj1 - > GetGameIndex ( ) ) ;
ragdoll_t * pRagdoll = Ragdoll_GetRagdoll ( pEntity0 ) ;
pRagdoll - > pGroup - > SolvePenetration ( pObj0 , pObj1 ) ;
return false ;
}
}
penetrateevent_t & event = FindOrAddPenetrateEvent ( pEntity0 , pEntity1 ) ;
float eventTime = gpGlobals - > curtime - event . startTime ;
// NPC vs. physics object. Create a game DLL solver and remove this event
if ( ( pEntity0 - > MyNPCPointer ( ) & & CanResolvePenetrationWithNPC ( pEntity1 , pObj1 ) ) | |
( pEntity1 - > MyNPCPointer ( ) & & CanResolvePenetrationWithNPC ( pEntity0 , pObj0 ) ) )
{
event . collisionState = COLLSTATE_TRYNPCSOLVER ;
}
if ( ( IsDebris ( pEntity0 - > GetCollisionGroup ( ) ) & & ! pObj1 - > IsStatic ( ) ) | | ( IsDebris ( pEntity1 - > GetCollisionGroup ( ) ) & & ! pObj0 - > IsStatic ( ) ) )
{
if ( eventTime > 0.5f )
{
//Msg("Debris stuck in non-static!\n");
event . collisionState = COLLSTATE_TRYENTITYSOLVER ;
}
}
# if _DEBUG
if ( phys_dontprintint . GetBool ( ) = = false )
{
const char * pName1 = STRING ( pEntity0 - > GetModelName ( ) ) ;
const char * pName2 = STRING ( pEntity1 - > GetModelName ( ) ) ;
if ( pEntity0 = = pEntity1 )
{
int index0 = physcollision - > CollideIndex ( pObj0 - > GetCollide ( ) ) ;
int index1 = physcollision - > CollideIndex ( pObj1 - > GetCollide ( ) ) ;
DevMsg ( 1 , " ***Inter-penetration on %s (%d & %d) (%.0f, %.0f) \n " , pName1 ? pName1 : " (null) " , index0 , index1 , gpGlobals - > curtime , eventTime ) ;
}
else
{
DevMsg ( 1 , " ***Inter-penetration between %s(%s) AND %s(%s) (%.0f, %.0f) \n " , pName1 ? pName1 : " (null) " , pEntity0 - > GetDebugName ( ) , pName2 ? pName2 : " (null) " , pEntity1 - > GetDebugName ( ) , gpGlobals - > curtime , eventTime ) ;
}
}
# endif
if ( eventTime > 3 )
{
// don't report penetrations on ragdolls with themselves, or outside of developer mode
if ( g_pDeveloper - > GetInt ( ) & & pEntity0 ! = pEntity1 )
{
ReportPenetration ( pEntity0 , phys_penetration_error_time . GetFloat ( ) ) ;
ReportPenetration ( pEntity1 , phys_penetration_error_time . GetFloat ( ) ) ;
}
event . startTime = gpGlobals - > curtime ;
// don't put players or game physics controlled objects to sleep
if ( ! pEntity0 - > IsPlayer ( ) & & ! pEntity1 - > IsPlayer ( ) & & ! pObj0 - > GetShadowController ( ) & & ! pObj1 - > GetShadowController ( ) )
{
// two objects have been stuck for more than 3 seconds, try disabling simulation
event . collisionState = COLLSTATE_TRYDISABLE ;
return false ;
}
}
return true ;
}
void CCollisionEvent : : FluidStartTouch ( IPhysicsObject * pObject , IPhysicsFluidController * pFluid )
{
CallbackContext check ( this ) ;
if ( ( pObject = = NULL ) | | ( pFluid = = NULL ) )
return ;
CBaseEntity * pEntity = static_cast < CBaseEntity * > ( pObject - > GetGameData ( ) ) ;
if ( ! pEntity )
return ;
pEntity - > AddEFlags ( EFL_TOUCHING_FLUID ) ;
pEntity - > OnEntityEvent ( ENTITY_EVENT_WATER_TOUCH , ( void * ) pFluid - > GetContents ( ) ) ;
float timeSinceLastCollision = DeltaTimeSinceLastFluid ( pEntity ) ;
if ( timeSinceLastCollision < 0.5f )
return ;
// UNDONE: Use this for splash logic instead?
// UNDONE: Use angular term too - push splashes in rotAxs cross normal direction?
Vector normal ;
float dist ;
pFluid - > GetSurfacePlane ( & normal , & dist ) ;
Vector vel ;
AngularImpulse angVel ;
pObject - > GetVelocity ( & vel , & angVel ) ;
Vector unitVel = vel ;
VectorNormalize ( unitVel ) ;
// normal points out of the surface, we want the direction that points in
float dragScale = pFluid - > GetDensity ( ) * physenv - > GetSimulationTimestep ( ) ;
normal = - normal ;
float linearScale = 0.5f * DotProduct ( unitVel , normal ) * pObject - > CalculateLinearDrag ( normal ) * dragScale ;
linearScale = clamp ( linearScale , 0.0f , 1.0f ) ;
vel * = - linearScale ;
// UNDONE: Figure out how much of the surface area has crossed the water surface and scale angScale by that
// For now assume 25%
Vector rotAxis = angVel ;
VectorNormalize ( rotAxis ) ;
float angScale = 0.25f * pObject - > CalculateAngularDrag ( angVel ) * dragScale ;
angScale = clamp ( angScale , 0.0f , 1.0f ) ;
angVel * = - angScale ;
// compute the splash before we modify the velocity
PhysicsSplash ( pFluid , pObject , pEntity ) ;
// now damp out some motion toward the surface
pObject - > AddVelocity ( & vel , & angVel ) ;
}
void CCollisionEvent : : FluidEndTouch ( IPhysicsObject * pObject , IPhysicsFluidController * pFluid )
{
CallbackContext check ( this ) ;
if ( ( pObject = = NULL ) | | ( pFluid = = NULL ) )
return ;
CBaseEntity * pEntity = static_cast < CBaseEntity * > ( pObject - > GetGameData ( ) ) ;
if ( ! pEntity )
return ;
float timeSinceLastCollision = DeltaTimeSinceLastFluid ( pEntity ) ;
if ( timeSinceLastCollision > = 0.5f )
{
PhysicsSplash ( pFluid , pObject , pEntity ) ;
}
pEntity - > RemoveEFlags ( EFL_TOUCHING_FLUID ) ;
pEntity - > OnEntityEvent ( ENTITY_EVENT_WATER_UNTOUCH , ( void * ) pFluid - > GetContents ( ) ) ;
}
class CSkipKeys : public IVPhysicsKeyHandler
{
public :
virtual void ParseKeyValue ( void * pData , const char * pKey , const char * pValue ) { }
virtual void SetDefaults ( void * pData ) { }
} ;
void PhysSolidOverride ( solid_t & solid , string_t overrideScript )
{
if ( overrideScript ! = NULL_STRING )
{
// parser destroys this data
bool collisions = solid . params . enableCollisions ;
char pTmpString [ 4096 ] ;
// write a header for a solid_t
Q_strncpy ( pTmpString , " solid { " , sizeof ( pTmpString ) ) ;
// suck out the comma delimited tokens and turn them into quoted key/values
char szToken [ 256 ] ;
const char * pStr = nexttoken ( szToken , STRING ( overrideScript ) , ' , ' ) ;
while ( szToken [ 0 ] ! = 0 )
{
Q_strncat ( pTmpString , " \" " , sizeof ( pTmpString ) , COPY_ALL_CHARACTERS ) ;
Q_strncat ( pTmpString , szToken , sizeof ( pTmpString ) , COPY_ALL_CHARACTERS ) ;
Q_strncat ( pTmpString , " \" " , sizeof ( pTmpString ) , COPY_ALL_CHARACTERS ) ;
pStr = nexttoken ( szToken , pStr , ' , ' ) ;
}
// terminate the script
Q_strncat ( pTmpString , " } " , sizeof ( pTmpString ) , COPY_ALL_CHARACTERS ) ;
// parse that sucker
IVPhysicsKeyParser * pParse = physcollision - > VPhysicsKeyParserCreate ( pTmpString ) ;
CSkipKeys tmp ;
pParse - > ParseSolid ( & solid , & tmp ) ;
physcollision - > VPhysicsKeyParserDestroy ( pParse ) ;
// parser destroys this data
solid . params . enableCollisions = collisions ;
}
}
void PhysSetMassCenterOverride ( masscenteroverride_t & override )
{
if ( override . entityName ! = NULL_STRING )
{
g_PhysicsHook . m_massCenterOverrides . AddToTail ( override ) ;
}
}
// NOTE: This will remove the entry from the list as well
int PhysGetMassCenterOverrideIndex ( string_t name )
{
if ( name ! = NULL_STRING & & g_PhysicsHook . m_massCenterOverrides . Count ( ) )
{
for ( int i = 0 ; i < g_PhysicsHook . m_massCenterOverrides . Count ( ) ; i + + )
{
if ( g_PhysicsHook . m_massCenterOverrides [ i ] . entityName = = name )
{
return i ;
}
}
}
return - 1 ;
}
void PhysGetMassCenterOverride ( CBaseEntity * pEntity , vcollide_t * pCollide , solid_t & solidOut )
{
int index = PhysGetMassCenterOverrideIndex ( pEntity - > GetEntityName ( ) ) ;
if ( index > = 0 )
{
masscenteroverride_t & override = g_PhysicsHook . m_massCenterOverrides [ index ] ;
Vector massCenterWS = override . center ;
switch ( override . alignType )
{
case masscenteroverride_t : : ALIGN_POINT :
VectorITransform ( massCenterWS , pEntity - > EntityToWorldTransform ( ) , solidOut . massCenterOverride ) ;
break ;
case masscenteroverride_t : : ALIGN_AXIS :
{
Vector massCenterLocal , defaultMassCenterWS ;
physcollision - > CollideGetMassCenter ( pCollide - > solids [ solidOut . index ] , & massCenterLocal ) ;
VectorTransform ( massCenterLocal , pEntity - > EntityToWorldTransform ( ) , defaultMassCenterWS ) ;
massCenterWS + = override . axis *
( DotProduct ( defaultMassCenterWS , override . axis ) - DotProduct ( override . axis , override . center ) ) ;
VectorITransform ( massCenterWS , pEntity - > EntityToWorldTransform ( ) , solidOut . massCenterOverride ) ;
}
break ;
}
g_PhysicsHook . m_massCenterOverrides . FastRemove ( index ) ;
if ( solidOut . massCenterOverride . Length ( ) > DIST_EPSILON )
{
solidOut . params . massCenterOverride = & solidOut . massCenterOverride ;
}
}
}
float PhysGetEntityMass ( CBaseEntity * pEntity )
{
IPhysicsObject * pList [ VPHYSICS_MAX_OBJECT_LIST_COUNT ] ;
int physCount = pEntity - > VPhysicsGetObjectList ( pList , ARRAYSIZE ( pList ) ) ;
float otherMass = 0 ;
for ( int i = 0 ; i < physCount ; i + + )
{
otherMass + = pList [ i ] - > GetMass ( ) ;
}
return otherMass ;
}
typedef void ( * EntityCallbackFunction ) ( CBaseEntity * pEntity ) ;
void IterateActivePhysicsEntities ( EntityCallbackFunction func )
{
int activeCount = physenv - > GetActiveObjectCount ( ) ;
IPhysicsObject * * pActiveList = NULL ;
if ( activeCount )
{
pActiveList = ( IPhysicsObject * * ) stackalloc ( sizeof ( IPhysicsObject * ) * activeCount ) ;
physenv - > GetActiveObjects ( pActiveList ) ;
for ( int i = 0 ; i < activeCount ; i + + )
{
CBaseEntity * pEntity = reinterpret_cast < CBaseEntity * > ( pActiveList [ i ] - > GetGameData ( ) ) ;
if ( pEntity )
{
func ( pEntity ) ;
}
}
}
}
static void CallbackHighlight ( CBaseEntity * pEntity )
{
pEntity - > m_debugOverlays | = OVERLAY_ABSBOX_BIT | OVERLAY_PIVOT_BIT ;
}
static void CallbackReport ( CBaseEntity * pEntity )
{
const char * pName = STRING ( pEntity - > GetEntityName ( ) ) ;
if ( ! Q_strlen ( pName ) )
{
pName = STRING ( pEntity - > GetModelName ( ) ) ;
}
Msg ( " %s - %s \n " , pEntity - > GetClassname ( ) , pName ) ;
}
CON_COMMAND ( physics_highlight_active , " Turns on the absbox for all active physics objects " )
{
if ( ! UTIL_IsCommandIssuedByServerAdmin ( ) )
return ;
IterateActivePhysicsEntities ( CallbackHighlight ) ;
}
CON_COMMAND ( physics_report_active , " Lists all active physics objects " )
{
if ( ! UTIL_IsCommandIssuedByServerAdmin ( ) )
return ;
IterateActivePhysicsEntities ( CallbackReport ) ;
}
CON_COMMAND_F ( surfaceprop , " Reports the surface properties at the cursor " , FCVAR_CHEAT )
{
if ( ! UTIL_IsCommandIssuedByServerAdmin ( ) )
return ;
CBasePlayer * pPlayer = UTIL_GetCommandClient ( ) ;
trace_t tr ;
Vector forward ;
pPlayer - > EyeVectors ( & forward ) ;
UTIL_TraceLine ( pPlayer - > EyePosition ( ) , pPlayer - > EyePosition ( ) + forward * MAX_COORD_RANGE ,
MASK_SHOT_HULL | CONTENTS_GRATE | CONTENTS_DEBRIS , pPlayer , COLLISION_GROUP_NONE , & tr ) ;
if ( tr . DidHit ( ) )
{
const model_t * pModel = modelinfo - > GetModel ( tr . m_pEnt - > GetModelIndex ( ) ) ;
const char * pModelName = STRING ( tr . m_pEnt - > GetModelName ( ) ) ;
if ( tr . DidHitWorld ( ) & & tr . hitbox > 0 )
{
ICollideable * pCollide = staticpropmgr - > GetStaticPropByIndex ( tr . hitbox - 1 ) ;
pModel = pCollide - > GetCollisionModel ( ) ;
pModelName = modelinfo - > GetModelName ( pModel ) ;
}
CFmtStr modelStuff ;
if ( pModel )
{
modelStuff . sprintf ( " %s.%s " , modelinfo - > IsTranslucent ( pModel ) ? " Translucent " : " Opaque " ,
modelinfo - > IsTranslucentTwoPass ( pModel ) ? " Two-pass. " : " " ) ;
}
// Calculate distance to surface that was hit
Vector vecVelocity = tr . startpos - tr . endpos ;
int length = vecVelocity . Length ( ) ;
Msg ( " Hit surface \" %s \" (entity %s, model \" %s \" %s), texture \" %s \" \n " , physprops - > GetPropName ( tr . surface . surfaceProps ) , tr . m_pEnt - > GetClassname ( ) , pModelName , modelStuff . Access ( ) , tr . surface . name ) ;
Msg ( " Distance to surface: %d \n " , length ) ;
}
}
static void OutputVPhysicsDebugInfo ( CBaseEntity * pEntity )
{
if ( pEntity )
{
Msg ( " Entity %s (%s) %s Collision Group %d \n " , pEntity - > GetClassname ( ) , pEntity - > GetDebugName ( ) , pEntity - > IsNavIgnored ( ) ? " NAV IGNORE " : " " , pEntity - > GetCollisionGroup ( ) ) ;
CUtlVector < CBaseEntity * > list ;
g_Collisions . GetListOfPenetratingEntities ( pEntity , list ) ;
for ( int i = 0 ; i < list . Count ( ) ; i + + )
{
Msg ( " penetration with entity %s (%s) \n " , list [ i ] - > GetDebugName ( ) , STRING ( list [ i ] - > GetModelName ( ) ) ) ;
}
IPhysicsObject * pList [ VPHYSICS_MAX_OBJECT_LIST_COUNT ] ;
int physCount = pEntity - > VPhysicsGetObjectList ( pList , ARRAYSIZE ( pList ) ) ;
if ( physCount )
{
if ( physCount > 1 )
{
for ( int i = 0 ; i < physCount ; i + + )
{
Msg ( " Object %d (of %d) ========================= \n " , i + 1 , physCount ) ;
pList [ i ] - > OutputDebugInfo ( ) ;
}
}
else
{
pList [ 0 ] - > OutputDebugInfo ( ) ;
}
}
}
}
class CConstraintFloodEntry
{
public :
CConstraintFloodEntry ( ) : isMarked ( false ) , isConstraint ( false ) { }
CUtlVector < CBaseEntity * > linkList ;
bool isMarked ;
bool isConstraint ;
} ;
class CConstraintFloodList
{
public :
CConstraintFloodList ( )
{
SetDefLessFunc ( m_list ) ;
m_list . EnsureCapacity ( 64 ) ;
m_entryList . EnsureCapacity ( 64 ) ;
}
bool IsWorldEntity ( CBaseEntity * pEnt )
{
if ( pEnt - > edict ( ) )
return pEnt - > IsWorld ( ) ;
return false ;
}
void AddLink ( CBaseEntity * pEntity , CBaseEntity * pLink , bool bIsConstraint )
{
if ( ! pEntity | | ! pLink | | IsWorldEntity ( pEntity ) | | IsWorldEntity ( pLink ) )
return ;
int listIndex = m_list . Find ( pEntity ) ;
if ( listIndex = = m_list . InvalidIndex ( ) )
{
int entryIndex = m_entryList . AddToTail ( ) ;
m_entryList [ entryIndex ] . isConstraint = bIsConstraint ;
listIndex = m_list . Insert ( pEntity , entryIndex ) ;
}
int entryIndex = m_list . Element ( listIndex ) ;
CConstraintFloodEntry & entry = m_entryList . Element ( entryIndex ) ;
Assert ( entry . isConstraint = = bIsConstraint ) ;
if ( entry . linkList . Find ( pLink ) < 0 )
{
entry . linkList . AddToTail ( pLink ) ;
}
}
void BuildGraphFromEntity ( CBaseEntity * pEntity , CUtlVector < CBaseEntity * > & constraintList )
{
int listIndex = m_list . Find ( pEntity ) ;
if ( listIndex ! = m_list . InvalidIndex ( ) )
{
int entryIndex = m_list . Element ( listIndex ) ;
CConstraintFloodEntry & entry = m_entryList . Element ( entryIndex ) ;
if ( ! entry . isMarked )
{
if ( entry . isConstraint )
{
Assert ( constraintList . Find ( pEntity ) < 0 ) ;
constraintList . AddToTail ( pEntity ) ;
}
entry . isMarked = true ;
for ( int i = 0 ; i < entry . linkList . Count ( ) ; i + + )
{
// now recursively traverse the graph from here
BuildGraphFromEntity ( entry . linkList [ i ] , constraintList ) ;
}
}
}
}
CUtlMap < CBaseEntity * , int > m_list ;
CUtlVector < CConstraintFloodEntry > m_entryList ;
} ;
// traverses the graph of attachments (currently supports springs & constraints) starting at an entity
// Then turns on debug info for each link in the graph (springs/constraints are links)
static void DebugConstraints ( CBaseEntity * pEntity )
{
extern bool GetSpringAttachments ( CBaseEntity * pEntity , CBaseEntity * pAttach [ 2 ] , IPhysicsObject * pAttachVPhysics [ 2 ] ) ;
extern bool GetConstraintAttachments ( CBaseEntity * pEntity , CBaseEntity * pAttach [ 2 ] , IPhysicsObject * pAttachVPhysics [ 2 ] ) ;
extern void DebugConstraint ( CBaseEntity * pEntity ) ;
if ( ! pEntity )
return ;
CBaseEntity * pAttach [ 2 ] ;
IPhysicsObject * pAttachVPhysics [ 2 ] ;
CConstraintFloodList list ;
for ( CBaseEntity * pList = gEntList . FirstEnt ( ) ; pList ! = NULL ; pList = gEntList . NextEnt ( pList ) )
{
if ( GetConstraintAttachments ( pList , pAttach , pAttachVPhysics ) | | GetSpringAttachments ( pList , pAttach , pAttachVPhysics ) )
{
list . AddLink ( pList , pAttach [ 0 ] , true ) ;
list . AddLink ( pList , pAttach [ 1 ] , true ) ;
list . AddLink ( pAttach [ 0 ] , pList , false ) ;
list . AddLink ( pAttach [ 1 ] , pList , false ) ;
}
}
CUtlVector < CBaseEntity * > constraints ;
list . BuildGraphFromEntity ( pEntity , constraints ) ;
for ( int i = 0 ; i < constraints . Count ( ) ; i + + )
{
if ( ! GetConstraintAttachments ( constraints [ i ] , pAttach , pAttachVPhysics ) )
{
GetSpringAttachments ( constraints [ i ] , pAttach , pAttachVPhysics ) ;
}
const char * pName0 = " world " ;
const char * pName1 = " world " ;
const char * pModel0 = " " ;
const char * pModel1 = " " ;
int index0 = 0 ;
int index1 = 0 ;
if ( pAttach [ 0 ] )
{
pName0 = pAttach [ 0 ] - > GetClassname ( ) ;
pModel0 = STRING ( pAttach [ 0 ] - > GetModelName ( ) ) ;
index0 = pAttachVPhysics [ 0 ] - > GetGameIndex ( ) ;
}
if ( pAttach [ 1 ] )
{
pName1 = pAttach [ 1 ] - > GetClassname ( ) ;
pModel1 = STRING ( pAttach [ 1 ] - > GetModelName ( ) ) ;
index1 = pAttachVPhysics [ 1 ] - > GetGameIndex ( ) ;
}
Msg ( " ********************** \n %s connects %s(%s:%d) to %s(%s:%d) \n " , constraints [ i ] - > GetClassname ( ) , pName0 , pModel0 , index0 , pName1 , pModel1 , index1 ) ;
DebugConstraint ( constraints [ i ] ) ;
constraints [ i ] - > m_debugOverlays | = OVERLAY_BBOX_BIT | OVERLAY_TEXT_BIT ;
}
}
static void MarkVPhysicsDebug ( CBaseEntity * pEntity )
{
if ( pEntity )
{
IPhysicsObject * pPhysics = pEntity - > VPhysicsGetObject ( ) ;
if ( pPhysics )
{
unsigned short callbacks = pPhysics - > GetCallbackFlags ( ) ;
callbacks ^ = CALLBACK_MARKED_FOR_TEST ;
pPhysics - > SetCallbackFlags ( callbacks ) ;
}
}
}
void PhysicsCommand ( const CCommand & args , void ( * func ) ( CBaseEntity * pEntity ) )
{
if ( args . ArgC ( ) < 2 )
{
CBasePlayer * pPlayer = UTIL_GetCommandClient ( ) ;
trace_t tr ;
Vector forward ;
pPlayer - > EyeVectors ( & forward ) ;
UTIL_TraceLine ( pPlayer - > EyePosition ( ) , pPlayer - > EyePosition ( ) + forward * MAX_COORD_RANGE ,
MASK_SHOT_HULL | CONTENTS_GRATE | CONTENTS_DEBRIS , pPlayer , COLLISION_GROUP_NONE , & tr ) ;
if ( tr . DidHit ( ) )
{
func ( tr . m_pEnt ) ;
}
}
else
{
CBaseEntity * pEnt = NULL ;
while ( ( pEnt = gEntList . FindEntityGeneric ( pEnt , args [ 1 ] ) ) ! = NULL )
{
func ( pEnt ) ;
}
}
}
CON_COMMAND ( physics_constraints , " Highlights constraint system graph for an entity " )
{
if ( ! UTIL_IsCommandIssuedByServerAdmin ( ) )
return ;
PhysicsCommand ( args , DebugConstraints ) ;
}
CON_COMMAND ( physics_debug_entity , " Dumps debug info for an entity " )
{
if ( ! UTIL_IsCommandIssuedByServerAdmin ( ) )
return ;
PhysicsCommand ( args , OutputVPhysicsDebugInfo ) ;
}
CON_COMMAND ( physics_select , " Dumps debug info for an entity " )
{
if ( ! UTIL_IsCommandIssuedByServerAdmin ( ) )
return ;
PhysicsCommand ( args , MarkVPhysicsDebug ) ;
}
CON_COMMAND ( physics_budget , " Times the cost of each active object " )
{
if ( ! UTIL_IsCommandIssuedByServerAdmin ( ) )
return ;
int activeCount = physenv - > GetActiveObjectCount ( ) ;
IPhysicsObject * * pActiveList = NULL ;
CUtlVector < CBaseEntity * > ents ;
if ( activeCount )
{
int i ;
pActiveList = ( IPhysicsObject * * ) stackalloc ( sizeof ( IPhysicsObject * ) * activeCount ) ;
physenv - > GetActiveObjects ( pActiveList ) ;
for ( i = 0 ; i < activeCount ; i + + )
{
CBaseEntity * pEntity = reinterpret_cast < CBaseEntity * > ( pActiveList [ i ] - > GetGameData ( ) ) ;
if ( pEntity )
{
int index = - 1 ;
for ( int j = 0 ; j < ents . Count ( ) ; j + + )
{
if ( pEntity = = ents [ j ] )
{
index = j ;
break ;
}
}
if ( index > = 0 )
continue ;
ents . AddToTail ( pEntity ) ;
}
}
stackfree ( pActiveList ) ;
if ( ! ents . Count ( ) )
return ;
CUtlVector < float > times ;
float totalTime = 0.f ;
g_Collisions . BufferTouchEvents ( true ) ;
float full = engine - > Time ( ) ;
physenv - > Simulate ( gpGlobals - > interval_per_tick ) ;
full = engine - > Time ( ) - full ;
float lastTime = full ;
times . SetSize ( ents . Count ( ) ) ;
// NOTE: This is just a heuristic. Attempt to estimate cost by putting each object to sleep in turn.
// note that simulation may wake the objects again and some costs scale with sets of objects/constraints/etc
// so these are only generally useful for broad questions, not real metrics!
for ( i = 0 ; i < ents . Count ( ) ; i + + )
{
for ( int j = 0 ; j < i ; j + + )
{
PhysForceEntityToSleep ( ents [ j ] , ents [ j ] - > VPhysicsGetObject ( ) ) ;
}
float start = engine - > Time ( ) ;
physenv - > Simulate ( gpGlobals - > interval_per_tick ) ;
float end = engine - > Time ( ) ;
float elapsed = end - start ;
float avgTime = lastTime - elapsed ;
times [ i ] = clamp ( avgTime , 0.00001f , 1.0f ) ;
totalTime + = times [ i ] ;
lastTime = elapsed ;
}
totalTime = MAX ( totalTime , 0.001 ) ;
for ( i = 0 ; i < ents . Count ( ) ; i + + )
{
float fraction = times [ i ] / totalTime ;
Msg ( " %s (%s): %.3fms (%.3f%%) @ %s \n " , ents [ i ] - > GetClassname ( ) , ents [ i ] - > GetDebugName ( ) , fraction * totalTime * 1000.0f , fraction * 100.0f , VecToString ( ents [ i ] - > GetAbsOrigin ( ) ) ) ;
}
g_Collisions . BufferTouchEvents ( false ) ;
}
}
# ifdef PORTAL
ConVar sv_fullsyncclones ( " sv_fullsyncclones " , " 1 " , FCVAR_CHEAT ) ;
void PortalPhysFrame ( float deltaTime ) //small wrapper for PhysFrame that simulates all environments at once
{
CPortalSimulator : : PrePhysFrame ( ) ;
if ( sv_fullsyncclones . GetBool ( ) )
CPhysicsShadowClone : : FullSyncAllClones ( ) ;
g_Collisions . BufferTouchEvents ( true ) ;
PhysFrame ( deltaTime ) ;
g_Collisions . PortalPostSimulationFrame ( ) ;
g_Collisions . BufferTouchEvents ( false ) ;
g_Collisions . FrameUpdate ( ) ;
CPortalSimulator : : PostPhysFrame ( ) ;
}
# endif
// Advance physics by time (in seconds)
void PhysFrame ( float deltaTime )
{
static int lastObjectCount = 0 ;
entitem_t * pItem ;
if ( ! g_PhysicsHook . ShouldSimulate ( ) )
return ;
// Trap interrupts and clock changes
if ( deltaTime > 1.0f | | deltaTime < 0.0f )
{
deltaTime = 0 ;
Msg ( " Reset physics clock \n " ) ;
}
else if ( deltaTime > 0.1f ) // limit incoming time to 100ms
{
deltaTime = 0.1f ;
}
float simRealTime = 0 ;
deltaTime * = phys_timescale . GetFloat ( ) ;
// !!!HACKHACK -- hard limit scaled time to avoid spending too much time in here
// Limit to 100 ms
if ( deltaTime > 0.100f )
deltaTime = 0.100f ;
bool bProfile = phys_speeds . GetBool ( ) ;
if ( bProfile )
{
simRealTime = engine - > Time ( ) ;
}
# ifdef _DEBUG
physenv - > DebugCheckContacts ( ) ;
# endif
# ifndef PORTAL //instead of wrapping 1 simulation with this, portal needs to wrap 3
g_Collisions . BufferTouchEvents ( true ) ;
# endif
int activeCount = physenv - > GetActiveObjectCount ( ) ;
IPhysicsObject * * pActiveList = NULL ;
#if 0
if ( activeCount )
{
pActiveList = ( IPhysicsObject * * ) stackalloc ( sizeof ( IPhysicsObject * ) * activeCount ) ;
physenv - > GetActiveObjects ( pActiveList ) ;
for ( int i = 0 ; i < activeCount ; i + + )
{
CBaseEntity * pEntity = reinterpret_cast < CBaseEntity * > ( pActiveList [ i ] - > GetGameData ( ) ) ;
OutputVPhysicsDebugInfo ( pEntity ) ;
}
stackfree ( pActiveList ) ;
}
# endif
physenv - > Simulate ( deltaTime ) ;
activeCount = physenv - > GetActiveObjectCount ( ) ;
pActiveList = NULL ;
if ( activeCount )
{
pActiveList = ( IPhysicsObject * * ) stackalloc ( sizeof ( IPhysicsObject * ) * activeCount ) ;
physenv - > GetActiveObjects ( pActiveList ) ;
for ( int i = 0 ; i < activeCount ; i + + )
{
CBaseEntity * pEntity = reinterpret_cast < CBaseEntity * > ( pActiveList [ i ] - > GetGameData ( ) ) ;
// OutputVPhysicsDebugInfo(pEntity);
if ( pEntity )
{
if ( pEntity - > CollisionProp ( ) - > DoesVPhysicsInvalidateSurroundingBox ( ) )
{
pEntity - > CollisionProp ( ) - > MarkSurroundingBoundsDirty ( ) ;
}
pEntity - > VPhysicsUpdate ( pActiveList [ i ] ) ;
}
}
stackfree ( pActiveList ) ;
}
for ( pItem = g_pShadowEntities - > m_pItemList ; pItem ; pItem = pItem - > pNext )
{
CBaseEntity * pEntity = pItem - > hEnt . Get ( ) ;
if ( ! pEntity )
{
Msg ( " Dangling pointer to physics entity!!! \n " ) ;
continue ;
}
IPhysicsObject * pPhysics = pEntity - > VPhysicsGetObject ( ) ;
// apply updates
if ( pPhysics & & ! pPhysics - > IsAsleep ( ) )
{
pEntity - > VPhysicsShadowUpdate ( pPhysics ) ;
}
}
if ( bProfile )
{
simRealTime = engine - > Time ( ) - simRealTime ;
if ( simRealTime < 0 )
simRealTime = 0 ;
g_PhysAverageSimTime * = 0.8 ;
g_PhysAverageSimTime + = ( simRealTime * 0.2 ) ;
if ( lastObjectCount ! = 0 | | activeCount ! = 0 )
{
Msg ( " Physics: %3d objects, %4.1fms / AVG: %4.1fms \n " , activeCount , simRealTime * 1000 , g_PhysAverageSimTime * 1000 ) ;
}
lastObjectCount = activeCount ;
}
# ifndef PORTAL //instead of wrapping 1 simulation with this, portal needs to wrap 3
g_Collisions . BufferTouchEvents ( false ) ;
g_Collisions . FrameUpdate ( ) ;
# endif
}
void PhysAddShadow ( CBaseEntity * pEntity )
{
g_pShadowEntities - > AddEntity ( pEntity ) ;
}
void PhysRemoveShadow ( CBaseEntity * pEntity )
{
g_pShadowEntities - > DeleteEntity ( pEntity ) ;
}
bool PhysHasShadow ( CBaseEntity * pEntity )
{
EHANDLE hTestEnt = pEntity ;
entitem_t * pCurrent = g_pShadowEntities - > m_pItemList ;
while ( pCurrent )
{
if ( pCurrent - > hEnt = = hTestEnt )
{
return true ;
}
pCurrent = pCurrent - > pNext ;
}
return false ;
}
void PhysEnableFloating ( IPhysicsObject * pObject , bool bEnable )
{
if ( pObject ! = NULL )
{
unsigned short flags = pObject - > GetCallbackFlags ( ) ;
if ( bEnable )
{
flags | = CALLBACK_DO_FLUID_SIMULATION ;
}
else
{
flags & = ~ CALLBACK_DO_FLUID_SIMULATION ;
}
pObject - > SetCallbackFlags ( flags ) ;
}
}
//-----------------------------------------------------------------------------
// CollisionEvent system
//-----------------------------------------------------------------------------
// NOTE: PreCollision/PostCollision ALWAYS come in matched pairs!!!
void CCollisionEvent : : PreCollision ( vcollisionevent_t * pEvent )
{
CallbackContext check ( this ) ;
m_gameEvent . Init ( pEvent ) ;
// gather the pre-collision data that the game needs to track
for ( int i = 0 ; i < 2 ; i + + )
{
IPhysicsObject * pObject = pEvent - > pObjects [ i ] ;
if ( pObject )
{
if ( pObject - > GetGameFlags ( ) & FVPHYSICS_PLAYER_HELD )
{
CBaseEntity * pOtherEntity = reinterpret_cast < CBaseEntity * > ( pEvent - > pObjects [ ! i ] - > GetGameData ( ) ) ;
if ( pOtherEntity & & ! pOtherEntity - > IsPlayer ( ) )
{
Vector velocity ;
AngularImpulse angVel ;
// HACKHACK: If we totally clear this out, then Havok will think the objects
// are penetrating and generate forces to separate them
// so make it fairly small and have a tiny collision instead.
pObject - > GetVelocity ( & velocity , & angVel ) ;
float len = VectorNormalize ( velocity ) ;
len = MAX ( len , 10 ) ;
velocity * = len ;
len = VectorNormalize ( angVel ) ;
len = MAX ( len , 1 ) ;
angVel * = len ;
pObject - > SetVelocity ( & velocity , & angVel ) ;
}
}
pObject - > GetVelocity ( & m_gameEvent . preVelocity [ i ] , & m_gameEvent . preAngularVelocity [ i ] ) ;
}
}
}
void CCollisionEvent : : PostCollision ( vcollisionevent_t * pEvent )
{
CallbackContext check ( this ) ;
bool isShadow [ 2 ] = { false , false } ;
int i ;
for ( i = 0 ; i < 2 ; i + + )
{
IPhysicsObject * pObject = pEvent - > pObjects [ i ] ;
if ( pObject )
{
CBaseEntity * pEntity = reinterpret_cast < CBaseEntity * > ( pObject - > GetGameData ( ) ) ;
if ( ! pEntity )
return ;
// UNDONE: This is here to trap crashes due to NULLing out the game data on delete
m_gameEvent . pEntities [ i ] = pEntity ;
unsigned int flags = pObject - > GetCallbackFlags ( ) ;
pObject - > GetVelocity ( & m_gameEvent . postVelocity [ i ] , NULL ) ;
if ( flags & CALLBACK_SHADOW_COLLISION )
{
isShadow [ i ] = true ;
}
// Shouldn't get impacts with triggers
Assert ( ! pObject - > IsTrigger ( ) ) ;
}
}
// copy off the post-collision variable data
m_gameEvent . collisionSpeed = pEvent - > collisionSpeed ;
m_gameEvent . pInternalData = pEvent - > pInternalData ;
// special case for hitting self, only make one non-shadow call
if ( m_gameEvent . pEntities [ 0 ] = = m_gameEvent . pEntities [ 1 ] )
{
if ( pEvent - > isCollision & & m_gameEvent . pEntities [ 0 ] )
{
m_gameEvent . pEntities [ 0 ] - > VPhysicsCollision ( 0 , & m_gameEvent ) ;
}
return ;
}
if ( isShadow [ 0 ] & & isShadow [ 1 ] )
{
pEvent - > isCollision = false ;
}
for ( i = 0 ; i < 2 ; i + + )
{
if ( pEvent - > isCollision )
{
m_gameEvent . pEntities [ i ] - > VPhysicsCollision ( i , & m_gameEvent ) ;
}
if ( pEvent - > isShadowCollision & & isShadow [ i ] )
{
m_gameEvent . pEntities [ i ] - > VPhysicsShadowCollision ( i , & m_gameEvent ) ;
}
}
}
void PhysForceEntityToSleep ( CBaseEntity * pEntity , IPhysicsObject * pObject )
{
// UNDONE: Check to see if the object is touching the player first?
// Might get the player stuck?
if ( ! pObject | | ! pObject - > IsMoveable ( ) )
return ;
DevMsg ( 2 , " Putting entity to sleep: %s \n " , pEntity - > GetClassname ( ) ) ;
MEM_ALLOC_CREDIT ( ) ;
IPhysicsObject * pList [ VPHYSICS_MAX_OBJECT_LIST_COUNT ] ;
int physCount = pEntity - > VPhysicsGetObjectList ( pList , ARRAYSIZE ( pList ) ) ;
for ( int i = 0 ; i < physCount ; i + + )
{
PhysForceClearVelocity ( pList [ i ] ) ;
pList [ i ] - > Sleep ( ) ;
}
}
void CCollisionEvent : : Friction ( IPhysicsObject * pObject , float energy , int surfaceProps , int surfacePropsHit , IPhysicsCollisionData * pData )
{
CallbackContext check ( this ) ;
//Get our friction information
Vector vecPos , vecVel ;
pData - > GetContactPoint ( vecPos ) ;
pObject - > GetVelocityAtPoint ( vecPos , & vecVel ) ;
CBaseEntity * pEntity = reinterpret_cast < CBaseEntity * > ( pObject - > GetGameData ( ) ) ;
if ( pEntity )
{
friction_t * pFriction = g_Collisions . FindFriction ( pEntity ) ;
if ( pFriction & & pFriction - > pObject )
{
// in MP mode play sound and effects once every 500 msecs,
// no ongoing updates, takes too much bandwidth
if ( ( pFriction - > flLastEffectTime + 0.5f ) > gpGlobals - > curtime )
{
pFriction - > flLastUpdateTime = gpGlobals - > curtime ;
return ;
}
}
pEntity - > VPhysicsFriction ( pObject , energy , surfaceProps , surfacePropsHit ) ;
}
PhysFrictionEffect ( vecPos , vecVel , energy , surfaceProps , surfacePropsHit ) ;
}
friction_t * CCollisionEvent : : FindFriction ( CBaseEntity * pObject )
{
friction_t * pFree = NULL ;
for ( int i = 0 ; i < ARRAYSIZE ( m_current ) ; i + + )
{
if ( ! m_current [ i ] . pObject & & ! pFree )
pFree = & m_current [ i ] ;
if ( m_current [ i ] . pObject = = pObject )
return & m_current [ i ] ;
}
return pFree ;
}
void CCollisionEvent : : ShutdownFriction ( friction_t & friction )
{
// Msg( "Scrape Stop %s \n", STRING(friction.pObject->m_iClassname) );
CSoundEnvelopeController : : GetController ( ) . SoundDestroy ( friction . patch ) ;
friction . patch = NULL ;
friction . pObject = NULL ;
}
void CCollisionEvent : : UpdateRemoveObjects ( )
{
Assert ( ! PhysIsInCallback ( ) ) ;
for ( int i = 0 ; i < m_removeObjects . Count ( ) ; i + + )
{
UTIL_Remove ( m_removeObjects [ i ] ) ;
}
m_removeObjects . RemoveAll ( ) ;
}
void CCollisionEvent : : PostSimulationFrame ( )
{
UpdateDamageEvents ( ) ;
g_PostSimulationQueue . CallQueued ( ) ;
UpdateRemoveObjects ( ) ;
}
void CCollisionEvent : : FlushQueuedOperations ( )
{
int loopCount = 0 ;
while ( loopCount < 20 )
{
int count = m_triggerEvents . Count ( ) + m_touchEvents . Count ( ) + m_damageEvents . Count ( ) + m_removeObjects . Count ( ) + g_PostSimulationQueue . Count ( ) ;
if ( ! count )
break ;
// testing, if this assert fires it proves we've fixed the crash
// after that the assert + warning can safely be removed
Assert ( 0 ) ;
Warning ( " Physics queue not empty, error! \n " ) ;
loopCount + + ;
UpdateTouchEvents ( ) ;
UpdateDamageEvents ( ) ;
g_PostSimulationQueue . CallQueued ( ) ;
UpdateRemoveObjects ( ) ;
}
}
void CCollisionEvent : : FrameUpdate ( void )
{
UpdateFrictionSounds ( ) ;
UpdateTouchEvents ( ) ;
UpdatePenetrateEvents ( ) ;
UpdateFluidEvents ( ) ;
UpdateDamageEvents ( ) ; // if there was no PSI in physics, we'll still need to do some of these because collisions are solved in between PSIs
g_PostSimulationQueue . CallQueued ( ) ;
UpdateRemoveObjects ( ) ;
// There are some queued operations that must complete each frame, iterate until these are done
FlushQueuedOperations ( ) ;
}
// the delete list is getting flushed, clean up ours
void PhysOnCleanupDeleteList ( )
{
g_Collisions . FlushQueuedOperations ( ) ;
if ( physenv )
{
physenv - > CleanupDeleteList ( ) ;
}
}
void CCollisionEvent : : UpdateFluidEvents ( void )
{
for ( int i = m_fluidEvents . Count ( ) - 1 ; i > = 0 ; - - i )
{
if ( ( gpGlobals - > curtime - m_fluidEvents [ i ] . impactTime ) > FLUID_TIME_MAX )
{
m_fluidEvents . FastRemove ( i ) ;
}
}
}
float CCollisionEvent : : DeltaTimeSinceLastFluid ( CBaseEntity * pEntity )
{
for ( int i = m_fluidEvents . Count ( ) - 1 ; i > = 0 ; - - i )
{
if ( m_fluidEvents [ i ] . hEntity . Get ( ) = = pEntity )
{
return gpGlobals - > curtime - m_fluidEvents [ i ] . impactTime ;
}
}
int index = m_fluidEvents . AddToTail ( ) ;
m_fluidEvents [ index ] . hEntity = pEntity ;
m_fluidEvents [ index ] . impactTime = gpGlobals - > curtime ;
return FLUID_TIME_MAX ;
}
void CCollisionEvent : : UpdateFrictionSounds ( void )
{
for ( int i = 0 ; i < ARRAYSIZE ( m_current ) ; i + + )
{
if ( m_current [ i ] . patch )
{
if ( m_current [ i ] . flLastUpdateTime < ( gpGlobals - > curtime - 0.1f ) )
{
// friction wasn't updated the last 100msec, assume fiction finished
ShutdownFriction ( m_current [ i ] ) ;
}
}
}
}
void CCollisionEvent : : DispatchStartTouch ( CBaseEntity * pEntity0 , CBaseEntity * pEntity1 , const Vector & point , const Vector & normal )
{
trace_t trace ;
memset ( & trace , 0 , sizeof ( trace ) ) ;
trace . endpos = point ;
trace . plane . dist = DotProduct ( point , normal ) ;
trace . plane . normal = normal ;
// NOTE: This sets up the touch list for both entities, no call to pEntity1 is needed
pEntity0 - > PhysicsMarkEntitiesAsTouchingEventDriven ( pEntity1 , trace ) ;
}
void CCollisionEvent : : DispatchEndTouch ( CBaseEntity * pEntity0 , CBaseEntity * pEntity1 )
{
// frees the event-driven touchlinks
pEntity0 - > PhysicsNotifyOtherOfUntouch ( pEntity0 , pEntity1 ) ;
pEntity1 - > PhysicsNotifyOtherOfUntouch ( pEntity1 , pEntity0 ) ;
}
void CCollisionEvent : : UpdateTouchEvents ( void )
{
int i ;
// Turn on buffering in case new touch events occur during processing
bool bOldTouchEvents = m_bBufferTouchEvents ;
m_bBufferTouchEvents = true ;
for ( i = 0 ; i < m_touchEvents . Count ( ) ; i + + )
{
const touchevent_t & event = m_touchEvents [ i ] ;
if ( event . touchType = = TOUCH_START )
{
DispatchStartTouch ( event . pEntity0 , event . pEntity1 , event . endPoint , event . normal ) ;
}
else
{
// TOUCH_END
DispatchEndTouch ( event . pEntity0 , event . pEntity1 ) ;
}
}
m_touchEvents . RemoveAll ( ) ;
for ( i = 0 ; i < m_triggerEvents . Count ( ) ; i + + )
{
m_currentTriggerEvent = m_triggerEvents [ i ] ;
if ( m_currentTriggerEvent . bStart )
{
m_currentTriggerEvent . pTriggerEntity - > StartTouch ( m_currentTriggerEvent . pEntity ) ;
}
else
{
m_currentTriggerEvent . pTriggerEntity - > EndTouch ( m_currentTriggerEvent . pEntity ) ;
}
}
m_triggerEvents . RemoveAll ( ) ;
m_currentTriggerEvent . Clear ( ) ;
m_bBufferTouchEvents = bOldTouchEvents ;
}
void CCollisionEvent : : UpdateDamageEvents ( void )
{
for ( int i = 0 ; i < m_damageEvents . Count ( ) ; i + + )
{
damageevent_t & event = m_damageEvents [ i ] ;
// Track changes in the entity's life state
int iEntBits = event . pEntity - > IsAlive ( ) ? 0x0001 : 0 ;
iEntBits | = event . pEntity - > IsMarkedForDeletion ( ) ? 0x0002 : 0 ;
iEntBits | = ( event . pEntity - > GetSolidFlags ( ) & FSOLID_NOT_SOLID ) ? 0x0004 : 0 ;
#if 0
// Go ahead and compute the current static stress when hit by a large object (with a force high enough to do damage).
// That way you die from the impact rather than the stress of the object resting on you whenever possible.
// This makes the damage effects cleaner.
if ( event . pInflictorPhysics & & event . pInflictorPhysics - > GetMass ( ) > VPHYSICS_LARGE_OBJECT_MASS )
{
CBaseCombatCharacter * pCombat = event . pEntity - > MyCombatCharacterPointer ( ) ;
if ( pCombat )
{
vphysics_objectstress_t stressOut ;
event . info . AddDamage ( pCombat - > CalculatePhysicsStressDamage ( & stressOut , pCombat - > VPhysicsGetObject ( ) ) ) ;
}
}
# endif
event . pEntity - > TakeDamage ( event . info ) ;
int iEntBits2 = event . pEntity - > IsAlive ( ) ? 0x0001 : 0 ;
iEntBits2 | = event . pEntity - > IsMarkedForDeletion ( ) ? 0x0002 : 0 ;
iEntBits2 | = ( event . pEntity - > GetSolidFlags ( ) & FSOLID_NOT_SOLID ) ? 0x0004 : 0 ;
if ( event . bRestoreVelocity & & iEntBits ! = iEntBits2 )
{
// UNDONE: Use ratio of masses to blend in a little of the collision response?
// UNDONE: Damage for future events is already computed - it would be nice to
// go back and recompute it now that the values have
// been adjusted
RestoreDamageInflictorState ( event . pInflictorPhysics ) ;
}
}
m_damageEvents . RemoveAll ( ) ;
m_damageInflictors . RemoveAll ( ) ;
}
void CCollisionEvent : : RestoreDamageInflictorState ( int inflictorStateIndex , float velocityBlend )
{
inflictorstate_t & state = m_damageInflictors [ inflictorStateIndex ] ;
if ( state . restored )
return ;
// so we only restore this guy once
state . restored = true ;
if ( velocityBlend > 0 )
{
Vector velocity ;
AngularImpulse angVel ;
state . pInflictorPhysics - > GetVelocity ( & velocity , & angVel ) ;
state . savedVelocity = state . savedVelocity * velocityBlend + velocity * ( 1 - velocityBlend ) ;
state . savedAngularVelocity = state . savedAngularVelocity * velocityBlend + angVel * ( 1 - velocityBlend ) ;
state . pInflictorPhysics - > SetVelocity ( & state . savedVelocity , & state . savedAngularVelocity ) ;
}
if ( state . nextIndex > = 0 )
{
RestoreDamageInflictorState ( state . nextIndex , velocityBlend ) ;
}
}
void CCollisionEvent : : RestoreDamageInflictorState ( IPhysicsObject * pInflictor )
{
if ( ! pInflictor )
return ;
int index = FindDamageInflictor ( pInflictor ) ;
if ( index > = 0 )
{
inflictorstate_t & state = m_damageInflictors [ index ] ;
if ( ! state . restored )
{
float velocityBlend = 1.0 ;
float inflictorMass = state . pInflictorPhysics - > GetMass ( ) ;
if ( inflictorMass < VPHYSICS_LARGE_OBJECT_MASS & & ! ( state . pInflictorPhysics - > GetGameFlags ( ) & FVPHYSICS_DMG_SLICE ) )
{
float otherMass = state . otherMassMax > 0 ? state . otherMassMax : 1 ;
float massRatio = inflictorMass / otherMass ;
massRatio = clamp ( massRatio , 0.1f , 10.0f ) ;
if ( massRatio < 1 )
{
velocityBlend = RemapVal ( massRatio , 0.1 , 1 , 0 , 0.5 ) ;
}
else
{
velocityBlend = RemapVal ( massRatio , 1.0 , 10 , 0.5 , 1 ) ;
}
}
RestoreDamageInflictorState ( index , velocityBlend ) ;
}
}
}
bool CCollisionEvent : : GetInflictorVelocity ( IPhysicsObject * pInflictor , Vector & velocity , AngularImpulse & angVelocity )
{
int index = FindDamageInflictor ( pInflictor ) ;
if ( index > = 0 )
{
inflictorstate_t & state = m_damageInflictors [ index ] ;
velocity = state . savedVelocity ;
angVelocity = state . savedAngularVelocity ;
return true ;
}
return false ;
}
bool PhysGetDamageInflictorVelocityStartOfFrame ( IPhysicsObject * pInflictor , Vector & velocity , AngularImpulse & angVelocity )
{
return g_Collisions . GetInflictorVelocity ( pInflictor , velocity , angVelocity ) ;
}
void CCollisionEvent : : AddTouchEvent ( CBaseEntity * pEntity0 , CBaseEntity * pEntity1 , int touchType , const Vector & point , const Vector & normal )
{
if ( ! pEntity0 | | ! pEntity1 )
return ;
int index = m_touchEvents . AddToTail ( ) ;
touchevent_t & event = m_touchEvents [ index ] ;
event . pEntity0 = pEntity0 ;
event . pEntity1 = pEntity1 ;
event . touchType = touchType ;
event . endPoint = point ;
event . normal = normal ;
}
void CCollisionEvent : : AddDamageEvent ( CBaseEntity * pEntity , const CTakeDamageInfo & info , IPhysicsObject * pInflictorPhysics , bool bRestoreVelocity , const Vector & savedVel , const AngularImpulse & savedAngVel )
{
if ( pEntity - > IsMarkedForDeletion ( ) )
return ;
int iTimeBasedDamage = g_pGameRules - > Damage_GetTimeBased ( ) ;
if ( ! ( info . GetDamageType ( ) & ( DMG_BURN | DMG_DROWN | iTimeBasedDamage | DMG_PREVENT_PHYSICS_FORCE ) ) )
{
Assert ( info . GetDamageForce ( ) ! = vec3_origin & & info . GetDamagePosition ( ) ! = vec3_origin ) ;
}
int index = m_damageEvents . AddToTail ( ) ;
damageevent_t & event = m_damageEvents [ index ] ;
event . pEntity = pEntity ;
event . info = info ;
event . pInflictorPhysics = pInflictorPhysics ;
event . bRestoreVelocity = bRestoreVelocity ;
if ( ! pInflictorPhysics | | ! pInflictorPhysics - > IsMoveable ( ) )
{
event . bRestoreVelocity = false ;
}
if ( event . bRestoreVelocity )
{
float otherMass = pEntity - > VPhysicsGetObject ( ) - > GetMass ( ) ;
int inflictorIndex = FindDamageInflictor ( pInflictorPhysics ) ;
if ( inflictorIndex > = 0 )
{
// if this is a bigger mass, save that info
inflictorstate_t & state = m_damageInflictors [ inflictorIndex ] ;
if ( otherMass > state . otherMassMax )
{
state . otherMassMax = otherMass ;
}
}
else
{
AddDamageInflictor ( pInflictorPhysics , otherMass , savedVel , savedAngVel , true ) ;
}
}
}
//-----------------------------------------------------------------------------
// Impulse events
//-----------------------------------------------------------------------------
static void PostSimulation_ImpulseEvent ( IPhysicsObject * pObject , const Vector & centerForce , const AngularImpulse & centerTorque )
{
pObject - > ApplyForceCenter ( centerForce ) ;
pObject - > ApplyTorqueCenter ( centerTorque ) ;
}
void PostSimulation_SetVelocityEvent ( IPhysicsObject * pPhysicsObject , const Vector & vecVelocity )
{
pPhysicsObject - > SetVelocity ( & vecVelocity , NULL ) ;
}
void CCollisionEvent : : AddRemoveObject ( IServerNetworkable * pRemove )
{
if ( pRemove & & m_removeObjects . Find ( pRemove ) = = - 1 )
{
m_removeObjects . AddToTail ( pRemove ) ;
}
}
int CCollisionEvent : : FindDamageInflictor ( IPhysicsObject * pInflictorPhysics )
{
// UNDONE: Linear search? Probably ok with a low count here
for ( int i = m_damageInflictors . Count ( ) - 1 ; i > = 0 ; - - i )
{
const inflictorstate_t & state = m_damageInflictors [ i ] ;
if ( state . pInflictorPhysics = = pInflictorPhysics )
return i ;
}
return - 1 ;
}
int CCollisionEvent : : AddDamageInflictor ( IPhysicsObject * pInflictorPhysics , float otherMass , const Vector & savedVel , const AngularImpulse & savedAngVel , bool addList )
{
// NOTE: Save off the state of the object before collision
// restore if the impact is a kill
// UNDONE: Should we absorb some energy here?
// NOTE: we can't save a delta because there could be subsequent post-fatal collisions
int addIndex = m_damageInflictors . AddToTail ( ) ;
{
inflictorstate_t & state = m_damageInflictors [ addIndex ] ;
state . pInflictorPhysics = pInflictorPhysics ;
state . savedVelocity = savedVel ;
state . savedAngularVelocity = savedAngVel ;
state . otherMassMax = otherMass ;
state . restored = false ;
state . nextIndex = - 1 ;
}
if ( addList )
{
CBaseEntity * pEntity = static_cast < CBaseEntity * > ( pInflictorPhysics - > GetGameData ( ) ) ;
if ( pEntity )
{
IPhysicsObject * pList [ VPHYSICS_MAX_OBJECT_LIST_COUNT ] ;
int physCount = pEntity - > VPhysicsGetObjectList ( pList , ARRAYSIZE ( pList ) ) ;
if ( physCount > 1 )
{
int currentIndex = addIndex ;
for ( int i = 0 ; i < physCount ; i + + )
{
if ( pList [ i ] ! = pInflictorPhysics )
{
Vector vel ;
AngularImpulse angVel ;
pList [ i ] - > GetVelocity ( & vel , & angVel ) ;
int next = AddDamageInflictor ( pList [ i ] , otherMass , vel , angVel , false ) ;
m_damageInflictors [ currentIndex ] . nextIndex = next ;
currentIndex = next ;
}
}
}
}
}
return addIndex ;
}
void CCollisionEvent : : LevelShutdown ( void )
{
for ( int i = 0 ; i < ARRAYSIZE ( m_current ) ; i + + )
{
if ( m_current [ i ] . patch )
{
ShutdownFriction ( m_current [ i ] ) ;
}
}
}
void CCollisionEvent : : StartTouch ( IPhysicsObject * pObject1 , IPhysicsObject * pObject2 , IPhysicsCollisionData * pTouchData )
{
CallbackContext check ( this ) ;
CBaseEntity * pEntity1 = static_cast < CBaseEntity * > ( pObject1 - > GetGameData ( ) ) ;
CBaseEntity * pEntity2 = static_cast < CBaseEntity * > ( pObject2 - > GetGameData ( ) ) ;
if ( ! pEntity1 | | ! pEntity2 )
return ;
Vector endPoint , normal ;
pTouchData - > GetContactPoint ( endPoint ) ;
pTouchData - > GetSurfaceNormal ( normal ) ;
if ( ! m_bBufferTouchEvents )
{
DispatchStartTouch ( pEntity1 , pEntity2 , endPoint , normal ) ;
}
else
{
AddTouchEvent ( pEntity1 , pEntity2 , TOUCH_START , endPoint , normal ) ;
}
}
static int CountPhysicsObjectEntityContacts ( IPhysicsObject * pObject , CBaseEntity * pEntity )
{
IPhysicsFrictionSnapshot * pSnapshot = pObject - > CreateFrictionSnapshot ( ) ;
int count = 0 ;
while ( pSnapshot - > IsValid ( ) )
{
IPhysicsObject * pOther = pSnapshot - > GetObject ( 1 ) ;
CBaseEntity * pOtherEntity = static_cast < CBaseEntity * > ( pOther - > GetGameData ( ) ) ;
if ( pOtherEntity = = pEntity )
count + + ;
pSnapshot - > NextFrictionData ( ) ;
}
pObject - > DestroyFrictionSnapshot ( pSnapshot ) ;
return count ;
}
void CCollisionEvent : : EndTouch ( IPhysicsObject * pObject1 , IPhysicsObject * pObject2 , IPhysicsCollisionData * pTouchData )
{
CallbackContext check ( this ) ;
CBaseEntity * pEntity1 = static_cast < CBaseEntity * > ( pObject1 - > GetGameData ( ) ) ;
CBaseEntity * pEntity2 = static_cast < CBaseEntity * > ( pObject2 - > GetGameData ( ) ) ;
if ( ! pEntity1 | | ! pEntity2 )
return ;
// contact point deleted, but entities are still touching?
IPhysicsObject * list [ VPHYSICS_MAX_OBJECT_LIST_COUNT ] ;
int count = pEntity1 - > VPhysicsGetObjectList ( list , ARRAYSIZE ( list ) ) ;
int contactCount = 0 ;
for ( int i = 0 ; i < count ; i + + )
{
contactCount + = CountPhysicsObjectEntityContacts ( list [ i ] , pEntity2 ) ;
// still touching
if ( contactCount > 1 )
return ;
}
// should have exactly one contact point (the one getting deleted here)
//Assert( contactCount == 1 );
Vector endPoint , normal ;
pTouchData - > GetContactPoint ( endPoint ) ;
pTouchData - > GetSurfaceNormal ( normal ) ;
if ( ! m_bBufferTouchEvents )
{
DispatchEndTouch ( pEntity1 , pEntity2 ) ;
}
else
{
AddTouchEvent ( pEntity1 , pEntity2 , TOUCH_END , vec3_origin , vec3_origin ) ;
}
}
// UNDONE: This is functional, but minimally.
void CCollisionEvent : : ObjectEnterTrigger ( IPhysicsObject * pTrigger , IPhysicsObject * pObject )
{
CBaseEntity * pTriggerEntity = static_cast < CBaseEntity * > ( pTrigger - > GetGameData ( ) ) ;
CBaseEntity * pEntity = static_cast < CBaseEntity * > ( pObject - > GetGameData ( ) ) ;
if ( pTriggerEntity & & pEntity )
{
// UNDONE: Don't buffer these until we can solve generating touches at object creation time
if ( 0 & & m_bBufferTouchEvents )
{
int index = m_triggerEvents . AddToTail ( ) ;
m_triggerEvents [ index ] . Init ( pTriggerEntity , pTrigger , pEntity , pObject , true ) ;
}
else
{
CallbackContext check ( this ) ;
m_currentTriggerEvent . Init ( pTriggerEntity , pTrigger , pEntity , pObject , true ) ;
pTriggerEntity - > StartTouch ( pEntity ) ;
m_currentTriggerEvent . Clear ( ) ;
}
}
}
void CCollisionEvent : : ObjectLeaveTrigger ( IPhysicsObject * pTrigger , IPhysicsObject * pObject )
{
CBaseEntity * pTriggerEntity = static_cast < CBaseEntity * > ( pTrigger - > GetGameData ( ) ) ;
CBaseEntity * pEntity = static_cast < CBaseEntity * > ( pObject - > GetGameData ( ) ) ;
if ( pTriggerEntity & & pEntity )
{
// UNDONE: Don't buffer these until we can solve generating touches at object creation time
if ( 0 & & m_bBufferTouchEvents )
{
int index = m_triggerEvents . AddToTail ( ) ;
m_triggerEvents [ index ] . Init ( pTriggerEntity , pTrigger , pEntity , pObject , false ) ;
}
else
{
CallbackContext check ( this ) ;
m_currentTriggerEvent . Init ( pTriggerEntity , pTrigger , pEntity , pObject , false ) ;
pTriggerEntity - > EndTouch ( pEntity ) ;
m_currentTriggerEvent . Clear ( ) ;
}
}
}
bool CCollisionEvent : : GetTriggerEvent ( triggerevent_t * pEvent , CBaseEntity * pTriggerEntity )
{
if ( pEvent & & pTriggerEntity = = m_currentTriggerEvent . pTriggerEntity )
{
* pEvent = m_currentTriggerEvent ;
return true ;
}
return false ;
}
void PhysGetListOfPenetratingEntities ( CBaseEntity * pSearch , CUtlVector < CBaseEntity * > & list )
{
g_Collisions . GetListOfPenetratingEntities ( pSearch , list ) ;
}
bool PhysGetTriggerEvent ( triggerevent_t * pEvent , CBaseEntity * pTriggerEntity )
{
return g_Collisions . GetTriggerEvent ( pEvent , pTriggerEntity ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// External interface to collision sounds
//-----------------------------------------------------------------------------
void PhysicsImpactSound ( CBaseEntity * pEntity , IPhysicsObject * pPhysObject , int channel , int surfaceProps , int surfacePropsHit , float volume , float impactSpeed )
{
physicssound : : AddImpactSound ( g_PhysicsHook . m_impactSounds , pEntity , pEntity - > entindex ( ) , channel , pPhysObject , surfaceProps , surfacePropsHit , volume , impactSpeed ) ;
}
void PhysCollisionSound ( CBaseEntity * pEntity , IPhysicsObject * pPhysObject , int channel , int surfaceProps , int surfacePropsHit , float deltaTime , float speed )
{
if ( deltaTime < 0.05f | | speed < 70.0f )
return ;
float volume = speed * speed * ( 1.0f / ( 320.0f * 320.0f ) ) ; // max volume at 320 in/s
if ( volume > 1.0f )
volume = 1.0f ;
PhysicsImpactSound ( pEntity , pPhysObject , channel , surfaceProps , surfacePropsHit , volume , speed ) ;
}
void PhysBreakSound ( CBaseEntity * pEntity , IPhysicsObject * pPhysObject , Vector vecOrigin )
{
if ( ! pPhysObject )
return ;
physicssound : : AddBreakSound ( g_PhysicsHook . m_breakSounds , vecOrigin , pPhysObject - > GetMaterialIndex ( ) ) ;
}
ConVar collision_shake_amp ( " collision_shake_amp " , " 0.2 " ) ;
ConVar collision_shake_freq ( " collision_shake_freq " , " 0.5 " ) ;
ConVar collision_shake_time ( " collision_shake_time " , " 0.5 " ) ;
void PhysCollisionScreenShake ( gamevcollisionevent_t * pEvent , int index )
{
int otherIndex = ! index ;
float mass = pEvent - > pObjects [ index ] - > GetMass ( ) ;
if ( mass > = VPHYSICS_LARGE_OBJECT_MASS & & pEvent - > pObjects [ otherIndex ] - > IsStatic ( ) & &
! ( pEvent - > pObjects [ index ] - > GetGameFlags ( ) & FVPHYSICS_PENETRATING ) )
{
mass = clamp ( mass , VPHYSICS_LARGE_OBJECT_MASS , 2000.f ) ;
if ( pEvent - > collisionSpeed > 30 & & pEvent - > deltaCollisionTime > 0.25f )
{
Vector vecPos ;
pEvent - > pInternalData - > GetContactPoint ( vecPos ) ;
float impulse = pEvent - > collisionSpeed * mass ;
float amplitude = impulse * ( collision_shake_amp . GetFloat ( ) / ( 30.0f * VPHYSICS_LARGE_OBJECT_MASS ) ) ;
UTIL_ScreenShake ( vecPos , amplitude , collision_shake_freq . GetFloat ( ) , collision_shake_time . GetFloat ( ) , amplitude * 60 , SHAKE_START ) ;
}
}
}
# if HL2_EPISODIC
// Uses DispatchParticleEffect because, so far as I know, that is the new means of kicking
// off flinders for this kind of collision. Should this be in g_pEffects instead?
void PhysCollisionWarpEffect ( gamevcollisionevent_t * pEvent , surfacedata_t * phit )
{
Vector vecPos ;
QAngle vecAngles ;
pEvent - > pInternalData - > GetContactPoint ( vecPos ) ;
{
Vector vecNormal ;
pEvent - > pInternalData - > GetSurfaceNormal ( vecNormal ) ;
VectorAngles ( vecNormal , vecAngles ) ;
}
DispatchParticleEffect ( " warp_shield_impact " , vecPos , vecAngles ) ;
}
# endif
void PhysCollisionDust ( gamevcollisionevent_t * pEvent , surfacedata_t * phit )
{
switch ( phit - > game . material )
{
case CHAR_TEX_SAND :
case CHAR_TEX_DIRT :
if ( pEvent - > collisionSpeed < 200.0f )
return ;
break ;
case CHAR_TEX_CONCRETE :
if ( pEvent - > collisionSpeed < 340.0f )
return ;
break ;
# if HL2_EPISODIC
// this is probably redundant because BaseEntity::VHandleCollision should have already dispatched us elsewhere
case CHAR_TEX_WARPSHIELD :
PhysCollisionWarpEffect ( pEvent , phit ) ;
return ;
break ;
# endif
default :
return ;
}
//Kick up dust
Vector vecPos , vecVel ;
pEvent - > pInternalData - > GetContactPoint ( vecPos ) ;
vecVel . Random ( - 1.0f , 1.0f ) ;
vecVel . z = random - > RandomFloat ( 0.3f , 1.0f ) ;
VectorNormalize ( vecVel ) ;
g_pEffects - > Dust ( vecPos , vecVel , 8.0f , pEvent - > collisionSpeed ) ;
}
void PhysFrictionSound ( CBaseEntity * pEntity , IPhysicsObject * pObject , const char * pSoundName , HSOUNDSCRIPTHANDLE & handle , float flVolume )
{
if ( ! pEntity )
return ;
// cut out the quiet sounds
// UNDONE: Separate threshold for starting a sound vs. continuing?
flVolume = clamp ( flVolume , 0.0f , 1.0f ) ;
if ( flVolume > ( 1.0f / 128.0f ) )
{
friction_t * pFriction = g_Collisions . FindFriction ( pEntity ) ;
if ( ! pFriction )
return ;
CSoundParameters params ;
if ( ! CBaseEntity : : GetParametersForSound ( pSoundName , handle , params , NULL ) )
return ;
if ( ! pFriction - > pObject )
{
// don't create really quiet scrapes
if ( params . volume * flVolume < = 0.1f )
return ;
pFriction - > pObject = pEntity ;
CPASAttenuationFilter filter ( pEntity , params . soundlevel ) ;
pFriction - > patch = CSoundEnvelopeController : : GetController ( ) . SoundCreate (
filter , pEntity - > entindex ( ) , CHAN_BODY , pSoundName , params . soundlevel ) ;
CSoundEnvelopeController : : GetController ( ) . Play ( pFriction - > patch , params . volume * flVolume , params . pitch ) ;
}
else
{
float pitch = ( flVolume * ( params . pitchhigh - params . pitchlow ) ) + params . pitchlow ;
CSoundEnvelopeController : : GetController ( ) . SoundChangeVolume ( pFriction - > patch , params . volume * flVolume , 0.1f ) ;
CSoundEnvelopeController : : GetController ( ) . SoundChangePitch ( pFriction - > patch , pitch , 0.1f ) ;
}
pFriction - > flLastUpdateTime = gpGlobals - > curtime ;
pFriction - > flLastEffectTime = gpGlobals - > curtime ;
}
}
void PhysCleanupFrictionSounds ( CBaseEntity * pEntity )
{
friction_t * pFriction = g_Collisions . FindFriction ( pEntity ) ;
if ( pFriction & & pFriction - > patch )
{
g_Collisions . ShutdownFriction ( * pFriction ) ;
}
}
//-----------------------------------------------------------------------------
// Applies force impulses at a later time
//-----------------------------------------------------------------------------
void PhysCallbackImpulse ( IPhysicsObject * pPhysicsObject , const Vector & vecCenterForce , const AngularImpulse & vecCenterTorque )
{
Assert ( physenv - > IsInSimulation ( ) ) ;
g_PostSimulationQueue . QueueCall ( PostSimulation_ImpulseEvent , pPhysicsObject , RefToVal ( vecCenterForce ) , RefToVal ( vecCenterTorque ) ) ;
}
void PhysCallbackSetVelocity ( IPhysicsObject * pPhysicsObject , const Vector & vecVelocity )
{
Assert ( physenv - > IsInSimulation ( ) ) ;
g_PostSimulationQueue . QueueCall ( PostSimulation_SetVelocityEvent , pPhysicsObject , RefToVal ( vecVelocity ) ) ;
}
void PhysCallbackDamage ( CBaseEntity * pEntity , const CTakeDamageInfo & info , gamevcollisionevent_t & event , int hurtIndex )
{
Assert ( physenv - > IsInSimulation ( ) ) ;
int otherIndex = ! hurtIndex ;
g_Collisions . AddDamageEvent ( pEntity , info , event . pObjects [ otherIndex ] , true , event . preVelocity [ otherIndex ] , event . preAngularVelocity [ otherIndex ] ) ;
}
void PhysCallbackDamage ( CBaseEntity * pEntity , const CTakeDamageInfo & info )
{
if ( PhysIsInCallback ( ) )
{
CBaseEntity * pInflictor = info . GetInflictor ( ) ;
IPhysicsObject * pInflictorPhysics = ( pInflictor ) ? pInflictor - > VPhysicsGetObject ( ) : NULL ;
g_Collisions . AddDamageEvent ( pEntity , info , pInflictorPhysics , false , vec3_origin , vec3_origin ) ;
if ( pEntity & & info . GetInflictor ( ) )
{
DevMsg ( 2 , " Warning: Physics damage event with no recovery info! \n Objects: %s, %s \n " , pEntity - > GetClassname ( ) , info . GetInflictor ( ) - > GetClassname ( ) ) ;
}
}
else
{
pEntity - > TakeDamage ( info ) ;
}
}
void PhysCallbackRemove ( IServerNetworkable * pRemove )
{
if ( PhysIsInCallback ( ) )
{
g_Collisions . AddRemoveObject ( pRemove ) ;
}
else
{
UTIL_Remove ( pRemove ) ;
}
}
void PhysSetEntityGameFlags ( CBaseEntity * pEntity , unsigned short flags )
{
IPhysicsObject * pList [ VPHYSICS_MAX_OBJECT_LIST_COUNT ] ;
int count = pEntity - > VPhysicsGetObjectList ( pList , ARRAYSIZE ( pList ) ) ;
for ( int i = 0 ; i < count ; i + + )
{
PhysSetGameFlags ( pList [ i ] , flags ) ;
}
}
bool PhysFindOrAddVehicleScript ( const char * pScriptName , vehicleparams_t * pParams , vehiclesounds_t * pSounds )
{
return g_PhysicsHook . FindOrAddVehicleScript ( pScriptName , pParams , pSounds ) ;
}
void PhysFlushVehicleScripts ( )
{
g_PhysicsHook . FlushVehicleScripts ( ) ;
}
IPhysicsObject * FindPhysicsObjectByName ( const char * pName , CBaseEntity * pErrorEntity )
{
if ( ! pName | | ! strlen ( pName ) )
return NULL ;
CBaseEntity * pEntity = NULL ;
IPhysicsObject * pBestObject = NULL ;
while ( 1 )
{
pEntity = gEntList . FindEntityByName ( pEntity , pName ) ;
if ( ! pEntity )
break ;
if ( pEntity - > VPhysicsGetObject ( ) )
{
if ( pBestObject )
{
const char * pErrorName = pErrorEntity ? pErrorEntity - > GetClassname ( ) : " Unknown " ;
Vector origin = pErrorEntity ? pErrorEntity - > GetAbsOrigin ( ) : vec3_origin ;
DevWarning ( " entity %s at %s has physics attachment to more than one entity with the name %s!!! \n " , pErrorName , VecToString ( origin ) , pName ) ;
while ( ( pEntity = gEntList . FindEntityByName ( pEntity , pName ) ) ! = NULL )
{
DevWarning ( " Found %s \n " , pEntity - > GetClassname ( ) ) ;
}
break ;
}
pBestObject = pEntity - > VPhysicsGetObject ( ) ;
}
}
return pBestObject ;
}
void CC_AirDensity ( const CCommand & args )
{
if ( ! physenv )
return ;
if ( args . ArgC ( ) < 2 )
{
Msg ( " air_density <value> \n Current air density is %.2f \n " , physenv - > GetAirDensity ( ) ) ;
}
else
{
float density = atof ( args [ 1 ] ) ;
physenv - > SetAirDensity ( density ) ;
}
}
static ConCommand air_density ( " air_density " , CC_AirDensity , " Changes the density of air for drag computations. " , FCVAR_CHEAT ) ;
void DebugDrawContactPoints ( IPhysicsObject * pPhysics )
{
IPhysicsFrictionSnapshot * pSnapshot = pPhysics - > CreateFrictionSnapshot ( ) ;
while ( pSnapshot - > IsValid ( ) )
{
Vector pt , normal ;
pSnapshot - > GetContactPoint ( pt ) ;
pSnapshot - > GetSurfaceNormal ( normal ) ;
NDebugOverlay : : Box ( pt , - Vector ( 1 , 1 , 1 ) , Vector ( 1 , 1 , 1 ) , 0 , 255 , 0 , 32 , 0 ) ;
NDebugOverlay : : Line ( pt , pt - normal * 20 , 0 , 255 , 0 , false , 0 ) ;
IPhysicsObject * pOther = pSnapshot - > GetObject ( 1 ) ;
CBaseEntity * pEntity0 = static_cast < CBaseEntity * > ( pOther - > GetGameData ( ) ) ;
CFmtStr str ( " %s (%s) : % s [ % 0.2f ] " , pEntity0->GetClassname(), STRING(pEntity0->GetModelName()), pEntity0->GetDebugName(), pSnapshot->GetFrictionCoefficient() ) ;
NDebugOverlay : : Text ( pt , str . Access ( ) , false , 0 ) ;
pSnapshot - > NextFrictionData ( ) ;
}
pSnapshot - > DeleteAllMarkedContacts ( true ) ;
pPhysics - > DestroyFrictionSnapshot ( pSnapshot ) ;
}
#if 0
# include "filesystem.h"
//-----------------------------------------------------------------------------
// Purpose: This will append a collide to a glview file. Then you can view the
// collisionmodels with glview.
// Input : *pCollide - collision model
// &origin - position of the instance of this model
// &angles - orientation of instance
// *pFilename - output text file
//-----------------------------------------------------------------------------
// examples:
// world:
// DumpCollideToGlView( pWorldCollide->solids[0], vec3_origin, vec3_origin, "jaycollide.txt" );
// static_prop:
// DumpCollideToGlView( info.m_pCollide->solids[0], info.m_Origin, info.m_Angles, "jaycollide.txt" );
//
//-----------------------------------------------------------------------------
void DumpCollideToGlView ( CPhysCollide * pCollide , const Vector & origin , const QAngle & angles , const char * pFilename )
{
if ( ! pCollide )
return ;
printf ( " Writing %s... \n " , pFilename ) ;
Vector * outVerts ;
int vertCount = physcollision - > CreateDebugMesh ( pCollide , & outVerts ) ;
FileHandle_t fp = filesystem - > Open ( pFilename , " ab " ) ;
int triCount = vertCount / 3 ;
int vert = 0 ;
VMatrix tmp = SetupMatrixOrgAngles ( origin , angles ) ;
int i ;
for ( i = 0 ; i < vertCount ; i + + )
{
outVerts [ i ] = tmp . VMul4x3 ( outVerts [ i ] ) ;
}
for ( i = 0 ; i < triCount ; i + + )
{
filesystem - > FPrintf ( fp , " 3 \n " ) ;
filesystem - > FPrintf ( fp , " %6.3f %6.3f %6.3f 1 0 0 \n " , outVerts [ vert ] . x , outVerts [ vert ] . y , outVerts [ vert ] . z ) ;
vert + + ;
filesystem - > FPrintf ( fp , " %6.3f %6.3f %6.3f 0 1 0 \n " , outVerts [ vert ] . x , outVerts [ vert ] . y , outVerts [ vert ] . z ) ;
vert + + ;
filesystem - > FPrintf ( fp , " %6.3f %6.3f %6.3f 0 0 1 \n " , outVerts [ vert ] . x , outVerts [ vert ] . y , outVerts [ vert ] . z ) ;
vert + + ;
}
filesystem - > Close ( fp ) ;
physcollision - > DestroyDebugMesh ( vertCount , outVerts ) ;
}
# endif