diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/physics.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/physics.cpp')
| -rw-r--r-- | mp/src/game/server/physics.cpp | 5866 |
1 files changed, 2933 insertions, 2933 deletions
diff --git a/mp/src/game/server/physics.cpp b/mp/src/game/server/physics.cpp index fa856a6d..bec86f5f 100644 --- a/mp/src/game/server/physics.cpp +++ b/mp/src/game/server/physics.cpp @@ -1,2933 +1,2933 @@ -//========= 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 routines
-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( ¶ms );
-
-#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( DEFAULT_TICK_INTERVAL ); // 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( DEFAULT_TICK_INTERVAL );
- 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( DEFAULT_TICK_INTERVAL );
- 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
-
- physenv->Simulate( deltaTime );
-
- 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 )
- {
- 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 ¢erForce, const AngularImpulse ¢erTorque )
-{
- 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!\nObjects: %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>\nCurrent 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
-
+//========= 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 routines +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( ¶ms ); + +#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( DEFAULT_TICK_INTERVAL ); // 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( DEFAULT_TICK_INTERVAL ); + 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( DEFAULT_TICK_INTERVAL ); + 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 + + physenv->Simulate( deltaTime ); + + 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 ) + { + 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 ¢erForce, const AngularImpulse ¢erTorque ) +{ + 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!\nObjects: %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>\nCurrent 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 + |