From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/client/physics.cpp | 2102 ++++++++++++++++++++-------------------- 1 file changed, 1051 insertions(+), 1051 deletions(-) (limited to 'mp/src/game/client/physics.cpp') diff --git a/mp/src/game/client/physics.cpp b/mp/src/game/client/physics.cpp index a3a32b59..fb182ab0 100644 --- a/mp/src/game/client/physics.cpp +++ b/mp/src/game/client/physics.cpp @@ -1,1051 +1,1051 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include "cbase.h" -#include "vcollide_parse.h" -#include "filesystem.h" -#include "engine/IStaticPropMgr.h" -#include "solidsetdefaults.h" -#include "engine/IEngineSound.h" -#include "vphysics_sound.h" -#include "movevars_shared.h" -#include "engine/ivmodelinfo.h" -#include "fx.h" -#include "tier0/vprof.h" -#include "c_world.h" -#include "vphysics/object_hash.h" -#include "vphysics/collision_set.h" -#include "soundenvelope.h" -#include "fx_water.h" -#include "positionwatcher.h" -#include "vphysics/constraints.h" -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -// file system interface -extern IFileSystem *filesystem; - -ConVar cl_phys_timescale( "cl_phys_timescale", "1.0", FCVAR_CHEAT, "Sets the scale of time for client-side physics (ragdolls)" ); - -void PrecachePhysicsSounds( void ); - -//FIXME: Replicated from server end, consolidate? - - -extern IVEngineClient *engine; - -class CCollisionEvent : public IPhysicsCollisionEvent, public IPhysicsCollisionSolver, public IPhysicsObjectEvent -{ -public: - CCollisionEvent( void ); - - void ObjectSound( int index, vcollisionevent_t *pEvent ); - void PreCollision( vcollisionevent_t *pEvent ) {} - void PostCollision( vcollisionevent_t *pEvent ); - void Friction( IPhysicsObject *pObject, float energy, int surfaceProps, int surfacePropsHit, IPhysicsCollisionData *pData ); - - void BufferTouchEvents( bool enable ) { m_bBufferTouchEvents = enable; } - - void StartTouch( IPhysicsObject *pObject1, IPhysicsObject *pObject2, IPhysicsCollisionData *pTouchData ); - void EndTouch( IPhysicsObject *pObject1, IPhysicsObject *pObject2, IPhysicsCollisionData *pTouchData ); - - void FluidStartTouch( IPhysicsObject *pObject, IPhysicsFluidController *pFluid ); - void FluidEndTouch( IPhysicsObject *pObject, IPhysicsFluidController *pFluid ); - void PostSimulationFrame() {} - - virtual void ObjectEnterTrigger( IPhysicsObject *pTrigger, IPhysicsObject *pObject ) {} - virtual void ObjectLeaveTrigger( IPhysicsObject *pTrigger, IPhysicsObject *pObject ) {} - - float DeltaTimeSinceLastFluid( CBaseEntity *pEntity ); - void FrameUpdate( void ); - - void UpdateFluidEvents( void ); - void UpdateTouchEvents( void ); - - // IPhysicsCollisionSolver - int ShouldCollide( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1 ); -#if _DEBUG - int ShouldCollide_2( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1 ); -#endif - // debugging collision problem in TF2 - int ShouldSolvePenetration( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1, float dt ); - bool ShouldFreezeObject( IPhysicsObject *pObject ) { return true; } - int AdditionalCollisionChecksThisTick( int currentChecksDone ) { return 0; } - bool ShouldFreezeContacts( IPhysicsObject **pObjectList, int objectCount ) { return true; } - - // IPhysicsObjectEvent - virtual void ObjectWake( IPhysicsObject *pObject ) - { - C_BaseEntity *pEntity = static_cast(pObject->GetGameData()); - if (pEntity && pEntity->HasDataObjectType(VPHYSICSWATCHER)) - { - ReportVPhysicsStateChanged( pObject, pEntity, true ); - } - } - - virtual void ObjectSleep( IPhysicsObject *pObject ) - { - C_BaseEntity *pEntity = static_cast(pObject->GetGameData()); - if ( pEntity && pEntity->HasDataObjectType( VPHYSICSWATCHER ) ) - { - ReportVPhysicsStateChanged( pObject, pEntity, false ); - } - } - - - friction_t *FindFriction( CBaseEntity *pObject ); - void ShutdownFriction( friction_t &friction ); - void UpdateFrictionSounds(); - bool IsInCallback() { return m_inCallback > 0 ? true : false; } - -private: - class CallbackContext - { - public: - CallbackContext(CCollisionEvent *pOuter) - { - m_pOuter = pOuter; - m_pOuter->m_inCallback++; - } - ~CallbackContext() - { - m_pOuter->m_inCallback--; - } - private: - CCollisionEvent *m_pOuter; - }; - friend class CallbackContext; - - void AddTouchEvent( C_BaseEntity *pEntity0, C_BaseEntity *pEntity1, int touchType, const Vector &point, const Vector &normal ); - void DispatchStartTouch( C_BaseEntity *pEntity0, C_BaseEntity *pEntity1, const Vector &point, const Vector &normal ); - void DispatchEndTouch( C_BaseEntity *pEntity0, C_BaseEntity *pEntity1 ); - - friction_t m_current[8]; - CUtlVector m_fluidEvents; - CUtlVector m_touchEvents; - int m_inCallback; - bool m_bBufferTouchEvents; -}; - -CCollisionEvent g_Collisions; - -bool PhysIsInCallback() -{ - if ( (physenv && physenv->IsInSimulation()) || g_Collisions.IsInCallback() ) - return true; - - return false; -} - -bool PhysicsDLLInit( CreateInterfaceFn physicsFactory ) -{ - if ((physics = (IPhysics *)physicsFactory( VPHYSICS_INTERFACE_VERSION, NULL )) == NULL || - (physprops = (IPhysicsSurfaceProps *)physicsFactory( VPHYSICS_SURFACEPROPS_INTERFACE_VERSION, NULL )) == NULL || - (physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL )) == NULL ) - { - return false; - } - - if ( IsX360() ) - { - // Reduce timescale to save perf on 360 - cl_phys_timescale.SetValue(0.9f); - } - PhysParseSurfaceData( physprops, filesystem ); - return true; -} - -#define DEFAULT_XBOX_CLIENT_VPHYSICS_TICK 0.025 // 25ms ticks on xbox ragdolls -void PhysicsLevelInit( void ) -{ - physenv = physics->CreateEnvironment(); - assert( physenv ); -#ifdef PORTAL - physenv_main = physenv; -#endif - { - MEM_ALLOC_CREDIT(); - g_EntityCollisionHash = physics->CreateObjectPairHash(); - } - - // TODO: need to get the right factory function here - //physenv->SetDebugOverlay( appSystemFactory ); - physenv->SetGravity( Vector(0, 0, -GetCurrentGravity() ) ); - // 15 ms per tick - // NOTE: Always run client physics at this rate - helps keep ragdolls stable - physenv->SetSimulationTimestep( IsXbox() ? DEFAULT_XBOX_CLIENT_VPHYSICS_TICK : DEFAULT_TICK_INTERVAL ); - physenv->SetCollisionEventHandler( &g_Collisions ); - physenv->SetCollisionSolver( &g_Collisions ); - - g_PhysWorldObject = PhysCreateWorld_Shared( GetClientWorldEntity(), modelinfo->GetVCollide(1), g_PhysDefaultObjectParams ); - - staticpropmgr->CreateVPhysicsRepresentations( physenv, &g_SolidSetup, NULL ); -} - -void PhysicsReset() -{ - if ( !physenv ) - return; - - physenv->ResetSimulationClock(); -} - - -ConVar cl_ragdoll_collide( "cl_ragdoll_collide", "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 callback(this); - - C_BaseEntity *pEntity0 = static_cast(pGameData0); - C_BaseEntity *pEntity1 = static_cast(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; - } - // Obey collision group rules - Assert(GameRules()); - if ( GameRules() ) - { - if (!GameRules()->ShouldCollide( pEntity0->GetCollisionGroup(), pEntity1->GetCollisionGroup() )) - return 0; - } - - if ( (pObj0->GetGameFlags() & FVPHYSICS_PART_OF_RAGDOLL) && (pObj1->GetGameFlags() & FVPHYSICS_PART_OF_RAGDOLL) ) - { - if ( !cl_ragdoll_collide.GetBool() ) - 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; - -#if 0 - int solid0 = pEntity0->GetSolid(); - int solid1 = pEntity1->GetSolid(); - int nSolidFlags0 = pEntity0->GetSolidFlags(); - int nSolidFlags1 = pEntity1->GetSolidFlags(); -#endif - - 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; - return 1; -} - -int CCollisionEvent::ShouldSolvePenetration( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1, float dt ) -{ - CallbackContext callback(this); - // solve it yourself here and return 0, or have the default implementation do it - if ( pGameData0 == pGameData1 ) - { - if ( pObj0->GetGameFlags() & FVPHYSICS_PART_OF_RAGDOLL ) - { - // this is a ragdoll, self penetrating - C_BaseEntity *pEnt = reinterpret_cast(pGameData0); - C_BaseAnimating *pAnim = pEnt->GetBaseAnimating(); - - if ( pAnim && pAnim->m_pRagdoll ) - { - IPhysicsConstraintGroup *pGroup = pAnim->m_pRagdoll->GetConstraintGroup(); - if ( pGroup ) - { - pGroup->SolvePenetration( pObj0, pObj1 ); - return false; - } - } - } - } - - return true; -} - - -// A class that implements an IClientSystem for physics -class CPhysicsSystem : public CAutoGameSystemPerFrame -{ -public: - CPhysicsSystem( char const *name ) : CAutoGameSystemPerFrame( name ) - { - } - - // HACKHACK: PhysicsDLLInit() is called explicitly because it requires a parameter - virtual bool Init(); - virtual void Shutdown(); - - // Level init, shutdown - virtual void LevelInitPreEntity(); - virtual void LevelInitPostEntity(); - - // The level is shutdown in two parts - virtual void LevelShutdownPreEntity(); - - virtual void LevelShutdownPostEntity(); - - void AddImpactSound( void *pGameData, IPhysicsObject *pObject, int surfaceProps, int surfacePropsHit, float volume, float speed ); - - virtual void Update( float frametime ); - - void PhysicsSimulate(); - -private: - physicssound::soundlist_t m_impactSounds; -}; - -static CPhysicsSystem g_PhysicsSystem( "CPhysicsSystem" ); -// singleton to hook into the client system -IGameSystem *PhysicsGameSystem( void ) -{ - return &g_PhysicsSystem; -} - - -// HACKHACK: PhysicsDLLInit() is called explicitly because it requires a parameter -bool CPhysicsSystem::Init() -{ - return true; -} - -void CPhysicsSystem::Shutdown() -{ -} - -// Level init, shutdown -void CPhysicsSystem::LevelInitPreEntity( void ) -{ - m_impactSounds.RemoveAll(); - PrecachePhysicsSounds(); -} - -void CPhysicsSystem::LevelInitPostEntity( void ) -{ - PhysicsLevelInit(); -} - -// The level is shutdown in two parts -void CPhysicsSystem::LevelShutdownPreEntity() -{ - if ( physenv ) - { - // we may have deleted multiple objects including the world by now, so - // don't try to wake them up - physenv->SetQuickDelete( true ); - } -} - -void CPhysicsSystem::LevelShutdownPostEntity() -{ - if ( physenv ) - { - // environment destroys all objects - // entities are gone, so this is safe now - physics->DestroyEnvironment( physenv ); - } - physics->DestroyObjectPairHash( g_EntityCollisionHash ); - g_EntityCollisionHash = NULL; - - physics->DestroyAllCollisionSets(); - - physenv = NULL; - g_PhysWorldObject = NULL; -} - -void CPhysicsSystem::AddImpactSound( void *pGameData, IPhysicsObject *pObject, int surfaceProps, int surfacePropsHit, float volume, float speed ) -{ - physicssound::AddImpactSound( m_impactSounds, pGameData, SOUND_FROM_WORLD, CHAN_STATIC, pObject, surfaceProps, surfacePropsHit, volume, speed ); -} - - -void CPhysicsSystem::Update( float frametime ) -{ - // THIS WAS MOVED TO POST-ENTITY SIM - //PhysicsSimulate(); -} - - -void CPhysicsSystem::PhysicsSimulate() -{ - VPROF_BUDGET( "CPhysicsSystem::PhysicsSimulate", VPROF_BUDGETGROUP_PHYSICS ); - float frametime = gpGlobals->frametime; - - if ( physenv ) - { - tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %d", __FUNCTION__, physenv->GetActiveObjectCount() ); - - g_Collisions.BufferTouchEvents( true ); -#ifdef _DEBUG - physenv->DebugCheckContacts(); -#endif - physenv->Simulate( frametime * cl_phys_timescale.GetFloat() ); - - 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++ ) - { - C_BaseEntity *pEntity = reinterpret_cast(pActiveList[i]->GetGameData()); - if ( pEntity ) - { - if ( pEntity->CollisionProp()->DoesVPhysicsInvalidateSurroundingBox() ) - { - pEntity->CollisionProp()->MarkSurroundingBoundsDirty(); - } - pEntity->VPhysicsUpdate( pActiveList[i] ); - } - } - } - - g_Collisions.BufferTouchEvents( false ); - g_Collisions.FrameUpdate(); - } - physicssound::PlayImpactSounds( m_impactSounds ); -} - - -void PhysicsSimulate() -{ - g_PhysicsSystem.PhysicsSimulate(); -} - - - -CCollisionEvent::CCollisionEvent( void ) -{ -} - -void CCollisionEvent::ObjectSound( int index, vcollisionevent_t *pEvent ) -{ - IPhysicsObject *pObject = pEvent->pObjects[index]; - if ( !pObject || pObject->IsStatic() ) - return; - - float speed = pEvent->collisionSpeed * pEvent->collisionSpeed; - int surfaceProps = pEvent->surfaceProps[index]; - - void *pGameData = pObject->GetGameData(); - - if ( pGameData ) - { - float volume = speed * (1.0f/(320.0f*320.0f)); // max volume at 320 in/s - - if ( volume > 1.0f ) - volume = 1.0f; - - if ( surfaceProps >= 0 ) - { - g_PhysicsSystem.AddImpactSound( pGameData, pObject, surfaceProps, pEvent->surfaceProps[!index], volume, speed ); - } - } -} - -void CCollisionEvent::PostCollision( vcollisionevent_t *pEvent ) -{ - CallbackContext callback(this); - if ( pEvent->deltaCollisionTime > 0.1f && pEvent->collisionSpeed > 70 ) - { - ObjectSound( 0, pEvent ); - ObjectSound( 1, pEvent ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCollisionEvent::FrameUpdate( void ) -{ - UpdateFrictionSounds(); - UpdateTouchEvents(); - UpdateFluidEvents(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCollisionEvent::UpdateTouchEvents( void ) -{ - // Turn on buffering in case new touch events occur during processing - bool bOldTouchEvents = m_bBufferTouchEvents; - m_bBufferTouchEvents = true; - for ( int 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(); - m_bBufferTouchEvents = bOldTouchEvents; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pEntity0 - -// *pEntity1 - -// touchType - -//----------------------------------------------------------------------------- -void CCollisionEvent::AddTouchEvent( C_BaseEntity *pEntity0, C_BaseEntity *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; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pObject1 - -// *pObject2 - -// *pTouchData - -//----------------------------------------------------------------------------- -void CCollisionEvent::StartTouch( IPhysicsObject *pObject1, IPhysicsObject *pObject2, IPhysicsCollisionData *pTouchData ) -{ - CallbackContext callback(this); - C_BaseEntity *pEntity1 = static_cast(pObject1->GetGameData()); - C_BaseEntity *pEntity2 = static_cast(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 ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pEntity0 - -// *pEntity1 - -//----------------------------------------------------------------------------- -void CCollisionEvent::DispatchStartTouch( C_BaseEntity *pEntity0, C_BaseEntity *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 ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pObject1 - -// *pObject2 - -// *pTouchData - -//----------------------------------------------------------------------------- -void CCollisionEvent::EndTouch( IPhysicsObject *pObject1, IPhysicsObject *pObject2, IPhysicsCollisionData *pTouchData ) -{ - CallbackContext callback(this); - C_BaseEntity *pEntity1 = static_cast(pObject1->GetGameData()); - C_BaseEntity *pEntity2 = static_cast(pObject2->GetGameData()); - - if ( !pEntity1 || !pEntity2 ) - return; - - if ( !m_bBufferTouchEvents ) - { - DispatchEndTouch( pEntity1, pEntity2 ); - } - else - { - AddTouchEvent( pEntity1, pEntity2, TOUCH_END, vec3_origin, vec3_origin ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pEntity0 - -// *pEntity1 - -//----------------------------------------------------------------------------- -void CCollisionEvent::DispatchEndTouch( C_BaseEntity *pEntity0, C_BaseEntity *pEntity1 ) -{ - // frees the event-driven touchlinks - pEntity0->PhysicsNotifyOtherOfUntouch( pEntity0, pEntity1 ); - pEntity1->PhysicsNotifyOtherOfUntouch( pEntity1, pEntity0 ); -} - -void CCollisionEvent::Friction( IPhysicsObject *pObject, float energy, int surfaceProps, int surfacePropsHit, IPhysicsCollisionData *pData ) -{ - CallbackContext callback(this); - if ( energy < 0.05f || surfaceProps < 0 ) - return; - - //Get our friction information - Vector vecPos, vecVel; - pData->GetContactPoint( vecPos ); - pObject->GetVelocityAtPoint( vecPos, &vecVel ); - - CBaseEntity *pEntity = reinterpret_cast(pObject->GetGameData()); - - if ( pEntity ) - { - friction_t *pFriction = g_Collisions.FindFriction( pEntity ); - - if ( (gpGlobals->maxClients > 1) && 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; - } - } - - PhysFrictionSound( pEntity, 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::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] ); - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &matrix - -// &normal - -// Output : static int -//----------------------------------------------------------------------------- -static int BestAxisMatchingNormal( matrix3x4_t &matrix, const Vector &normal ) -{ - float bestDot = -1; - int best = 0; - for ( int i = 0; i < 3; i++ ) - { - Vector tmp; - MatrixGetColumn( matrix, i, tmp ); - float dot = fabs(DotProduct( tmp, normal )); - if ( dot > bestDot ) - { - bestDot = dot; - best = i; - } - } - - return best; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pFluid - -// *pObject - -// *pEntity - -//----------------------------------------------------------------------------- -void PhysicsSplash( IPhysicsFluidController *pFluid, IPhysicsObject *pObject, CBaseEntity *pEntity ) -{ - //FIXME: For now just allow ragdolls for E3 - jdw - if ( ( pObject->GetGameFlags() & FVPHYSICS_PART_OF_RAGDOLL ) == false ) - return; - - Vector velocity; - pObject->GetVelocity( &velocity, NULL ); - - float impactSpeed = velocity.Length(); - - if ( impactSpeed < 25.0f ) - return; - - Vector normal; - float dist; - pFluid->GetSurfacePlane( &normal, &dist ); - - matrix3x4_t &matrix = pEntity->EntityToWorldTransform(); - - // Find the local axis that best matches the water surface normal - int bestAxis = BestAxisMatchingNormal( matrix, normal ); - - Vector tangent, binormal; - MatrixGetColumn( matrix, (bestAxis+1)%3, tangent ); - binormal = CrossProduct( normal, tangent ); - VectorNormalize( binormal ); - tangent = CrossProduct( binormal, normal ); - VectorNormalize( tangent ); - - // Now we have a basis tangent to the surface that matches the object's local orientation as well as possible - // compute an OBB using this basis - - // Get object extents in basis - Vector tanPts[2], binPts[2]; - tanPts[0] = physcollision->CollideGetExtent( pObject->GetCollide(), pEntity->GetAbsOrigin(), pEntity->GetAbsAngles(), -tangent ); - tanPts[1] = physcollision->CollideGetExtent( pObject->GetCollide(), pEntity->GetAbsOrigin(), pEntity->GetAbsAngles(), tangent ); - binPts[0] = physcollision->CollideGetExtent( pObject->GetCollide(), pEntity->GetAbsOrigin(), pEntity->GetAbsAngles(), -binormal ); - binPts[1] = physcollision->CollideGetExtent( pObject->GetCollide(), pEntity->GetAbsOrigin(), pEntity->GetAbsAngles(), binormal ); - - // now compute the centered bbox - float mins[2], maxs[2], center[2], extents[2]; - mins[0] = DotProduct( tanPts[0], tangent ); - maxs[0] = DotProduct( tanPts[1], tangent ); - - mins[1] = DotProduct( binPts[0], binormal ); - maxs[1] = DotProduct( binPts[1], binormal ); - - center[0] = 0.5 * (mins[0] + maxs[0]); - center[1] = 0.5 * (mins[1] + maxs[1]); - - extents[0] = maxs[0] - center[0]; - extents[1] = maxs[1] - center[1]; - - Vector centerPoint = center[0] * tangent + center[1] * binormal + dist * normal; - - Vector axes[2]; - axes[0] = (maxs[0] - center[0]) * tangent; - axes[1] = (maxs[1] - center[1]) * binormal; - - // visualize OBB hit - /* - Vector corner1 = centerPoint - axes[0] - axes[1]; - Vector corner2 = centerPoint + axes[0] - axes[1]; - Vector corner3 = centerPoint + axes[0] + axes[1]; - Vector corner4 = centerPoint - axes[0] + axes[1]; - NDebugOverlay::Line( corner1, corner2, 0, 0, 255, false, 10 ); - NDebugOverlay::Line( corner2, corner3, 0, 0, 255, false, 10 ); - NDebugOverlay::Line( corner3, corner4, 0, 0, 255, false, 10 ); - NDebugOverlay::Line( corner4, corner1, 0, 0, 255, false, 10 ); - */ - - Vector corner[4]; - - corner[0] = centerPoint - axes[0] - axes[1]; - corner[1] = centerPoint + axes[0] - axes[1]; - corner[2] = centerPoint + axes[0] + axes[1]; - corner[3] = centerPoint - axes[0] + axes[1]; - - int contents = enginetrace->GetPointContents( centerPoint-Vector(0,0,2) ); - - bool bInSlime = ( contents & CONTENTS_SLIME ) ? true : false; - - Vector color = vec3_origin; - float luminosity = 1.0f; - - if ( !bInSlime ) - { - // Get our lighting information - FX_GetSplashLighting( centerPoint + ( normal * 8.0f ), &color, &luminosity ); - } - - if ( impactSpeed > 150 ) - { - if ( bInSlime ) - { - FX_GunshotSlimeSplash( centerPoint, normal, random->RandomFloat( 8, 10 ) ); - } - else - { - FX_GunshotSplash( centerPoint, normal, random->RandomFloat( 8, 10 ) ); - } - } - else if ( !bInSlime ) - { - FX_WaterRipple( centerPoint, 1.5f, &color, 1.5f, luminosity ); - } - - int splashes = 4; - Vector point; - - for ( int i = 0; i < splashes; i++ ) - { - point = RandomVector( -32.0f, 32.0f ); - point[2] = 0.0f; - - point += corner[i]; - - if ( impactSpeed > 150 ) - { - if ( bInSlime ) - { - FX_GunshotSlimeSplash( centerPoint, normal, random->RandomFloat( 4, 6 ) ); - } - else - { - FX_GunshotSplash( centerPoint, normal, random->RandomFloat( 4, 6 ) ); - } - } - else if ( !bInSlime ) - { - FX_WaterRipple( point, random->RandomFloat( 0.25f, 0.5f ), &color, luminosity, random->RandomFloat( 0.5f, 1.0f ) ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -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); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pEntity - -// Output : float -//----------------------------------------------------------------------------- -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; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pObject - -// *pFluid - -//----------------------------------------------------------------------------- -void CCollisionEvent::FluidStartTouch( IPhysicsObject *pObject, IPhysicsFluidController *pFluid ) -{ - CallbackContext callback(this); - if ( ( pObject == NULL ) || ( pFluid == NULL ) ) - return; - - CBaseEntity *pEntity = static_cast(pObject->GetGameData()); - - if ( pEntity ) - { - float timeSinceLastCollision = DeltaTimeSinceLastFluid( pEntity ); - - if ( timeSinceLastCollision < 0.5f ) - return; - - PhysicsSplash( pFluid, pObject, pEntity ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pObject - -// *pFluid - -//----------------------------------------------------------------------------- -void CCollisionEvent::FluidEndTouch( IPhysicsObject *pObject, IPhysicsFluidController *pFluid ) -{ - CallbackContext callback(this); - //FIXME: Do nothing for now -} - -IPhysicsObject *GetWorldPhysObject ( void ) -{ - return g_PhysWorldObject; -} - -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 ); - int entindex = pEntity->entindex(); - - // clientside created entites doesn't have a valid entindex, let 'world' play the sound for them - if ( entindex < 0 ) - entindex = 0; - - pFriction->patch = CSoundEnvelopeController::GetController().SoundCreate( - filter, 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 ); - } -} - -float PhysGetNextSimTime() -{ - return physenv->GetSimulationTime() + gpGlobals->frametime * cl_phys_timescale.GetFloat(); -} - -float PhysGetSyncCreateTime() -{ - float nextTime = physenv->GetNextFrameTime(); - float simTime = PhysGetNextSimTime(); - if ( nextTime < simTime ) - { - // The next simulation frame begins before the end of this frame - // so create physics objects at that time so that they will reach the current - // position at curtime. Otherwise the physics object will simulate forward from curtime - // and pop into the future a bit at this point of transition - return gpGlobals->curtime + nextTime - simTime; - } - return gpGlobals->curtime; -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "vcollide_parse.h" +#include "filesystem.h" +#include "engine/IStaticPropMgr.h" +#include "solidsetdefaults.h" +#include "engine/IEngineSound.h" +#include "vphysics_sound.h" +#include "movevars_shared.h" +#include "engine/ivmodelinfo.h" +#include "fx.h" +#include "tier0/vprof.h" +#include "c_world.h" +#include "vphysics/object_hash.h" +#include "vphysics/collision_set.h" +#include "soundenvelope.h" +#include "fx_water.h" +#include "positionwatcher.h" +#include "vphysics/constraints.h" +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// file system interface +extern IFileSystem *filesystem; + +ConVar cl_phys_timescale( "cl_phys_timescale", "1.0", FCVAR_CHEAT, "Sets the scale of time for client-side physics (ragdolls)" ); + +void PrecachePhysicsSounds( void ); + +//FIXME: Replicated from server end, consolidate? + + +extern IVEngineClient *engine; + +class CCollisionEvent : public IPhysicsCollisionEvent, public IPhysicsCollisionSolver, public IPhysicsObjectEvent +{ +public: + CCollisionEvent( void ); + + void ObjectSound( int index, vcollisionevent_t *pEvent ); + void PreCollision( vcollisionevent_t *pEvent ) {} + void PostCollision( vcollisionevent_t *pEvent ); + void Friction( IPhysicsObject *pObject, float energy, int surfaceProps, int surfacePropsHit, IPhysicsCollisionData *pData ); + + void BufferTouchEvents( bool enable ) { m_bBufferTouchEvents = enable; } + + void StartTouch( IPhysicsObject *pObject1, IPhysicsObject *pObject2, IPhysicsCollisionData *pTouchData ); + void EndTouch( IPhysicsObject *pObject1, IPhysicsObject *pObject2, IPhysicsCollisionData *pTouchData ); + + void FluidStartTouch( IPhysicsObject *pObject, IPhysicsFluidController *pFluid ); + void FluidEndTouch( IPhysicsObject *pObject, IPhysicsFluidController *pFluid ); + void PostSimulationFrame() {} + + virtual void ObjectEnterTrigger( IPhysicsObject *pTrigger, IPhysicsObject *pObject ) {} + virtual void ObjectLeaveTrigger( IPhysicsObject *pTrigger, IPhysicsObject *pObject ) {} + + float DeltaTimeSinceLastFluid( CBaseEntity *pEntity ); + void FrameUpdate( void ); + + void UpdateFluidEvents( void ); + void UpdateTouchEvents( void ); + + // IPhysicsCollisionSolver + int ShouldCollide( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1 ); +#if _DEBUG + int ShouldCollide_2( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1 ); +#endif + // debugging collision problem in TF2 + int ShouldSolvePenetration( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1, float dt ); + bool ShouldFreezeObject( IPhysicsObject *pObject ) { return true; } + int AdditionalCollisionChecksThisTick( int currentChecksDone ) { return 0; } + bool ShouldFreezeContacts( IPhysicsObject **pObjectList, int objectCount ) { return true; } + + // IPhysicsObjectEvent + virtual void ObjectWake( IPhysicsObject *pObject ) + { + C_BaseEntity *pEntity = static_cast(pObject->GetGameData()); + if (pEntity && pEntity->HasDataObjectType(VPHYSICSWATCHER)) + { + ReportVPhysicsStateChanged( pObject, pEntity, true ); + } + } + + virtual void ObjectSleep( IPhysicsObject *pObject ) + { + C_BaseEntity *pEntity = static_cast(pObject->GetGameData()); + if ( pEntity && pEntity->HasDataObjectType( VPHYSICSWATCHER ) ) + { + ReportVPhysicsStateChanged( pObject, pEntity, false ); + } + } + + + friction_t *FindFriction( CBaseEntity *pObject ); + void ShutdownFriction( friction_t &friction ); + void UpdateFrictionSounds(); + bool IsInCallback() { return m_inCallback > 0 ? true : false; } + +private: + class CallbackContext + { + public: + CallbackContext(CCollisionEvent *pOuter) + { + m_pOuter = pOuter; + m_pOuter->m_inCallback++; + } + ~CallbackContext() + { + m_pOuter->m_inCallback--; + } + private: + CCollisionEvent *m_pOuter; + }; + friend class CallbackContext; + + void AddTouchEvent( C_BaseEntity *pEntity0, C_BaseEntity *pEntity1, int touchType, const Vector &point, const Vector &normal ); + void DispatchStartTouch( C_BaseEntity *pEntity0, C_BaseEntity *pEntity1, const Vector &point, const Vector &normal ); + void DispatchEndTouch( C_BaseEntity *pEntity0, C_BaseEntity *pEntity1 ); + + friction_t m_current[8]; + CUtlVector m_fluidEvents; + CUtlVector m_touchEvents; + int m_inCallback; + bool m_bBufferTouchEvents; +}; + +CCollisionEvent g_Collisions; + +bool PhysIsInCallback() +{ + if ( (physenv && physenv->IsInSimulation()) || g_Collisions.IsInCallback() ) + return true; + + return false; +} + +bool PhysicsDLLInit( CreateInterfaceFn physicsFactory ) +{ + if ((physics = (IPhysics *)physicsFactory( VPHYSICS_INTERFACE_VERSION, NULL )) == NULL || + (physprops = (IPhysicsSurfaceProps *)physicsFactory( VPHYSICS_SURFACEPROPS_INTERFACE_VERSION, NULL )) == NULL || + (physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL )) == NULL ) + { + return false; + } + + if ( IsX360() ) + { + // Reduce timescale to save perf on 360 + cl_phys_timescale.SetValue(0.9f); + } + PhysParseSurfaceData( physprops, filesystem ); + return true; +} + +#define DEFAULT_XBOX_CLIENT_VPHYSICS_TICK 0.025 // 25ms ticks on xbox ragdolls +void PhysicsLevelInit( void ) +{ + physenv = physics->CreateEnvironment(); + assert( physenv ); +#ifdef PORTAL + physenv_main = physenv; +#endif + { + MEM_ALLOC_CREDIT(); + g_EntityCollisionHash = physics->CreateObjectPairHash(); + } + + // TODO: need to get the right factory function here + //physenv->SetDebugOverlay( appSystemFactory ); + physenv->SetGravity( Vector(0, 0, -GetCurrentGravity() ) ); + // 15 ms per tick + // NOTE: Always run client physics at this rate - helps keep ragdolls stable + physenv->SetSimulationTimestep( IsXbox() ? DEFAULT_XBOX_CLIENT_VPHYSICS_TICK : DEFAULT_TICK_INTERVAL ); + physenv->SetCollisionEventHandler( &g_Collisions ); + physenv->SetCollisionSolver( &g_Collisions ); + + g_PhysWorldObject = PhysCreateWorld_Shared( GetClientWorldEntity(), modelinfo->GetVCollide(1), g_PhysDefaultObjectParams ); + + staticpropmgr->CreateVPhysicsRepresentations( physenv, &g_SolidSetup, NULL ); +} + +void PhysicsReset() +{ + if ( !physenv ) + return; + + physenv->ResetSimulationClock(); +} + + +ConVar cl_ragdoll_collide( "cl_ragdoll_collide", "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 callback(this); + + C_BaseEntity *pEntity0 = static_cast(pGameData0); + C_BaseEntity *pEntity1 = static_cast(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; + } + // Obey collision group rules + Assert(GameRules()); + if ( GameRules() ) + { + if (!GameRules()->ShouldCollide( pEntity0->GetCollisionGroup(), pEntity1->GetCollisionGroup() )) + return 0; + } + + if ( (pObj0->GetGameFlags() & FVPHYSICS_PART_OF_RAGDOLL) && (pObj1->GetGameFlags() & FVPHYSICS_PART_OF_RAGDOLL) ) + { + if ( !cl_ragdoll_collide.GetBool() ) + 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; + +#if 0 + int solid0 = pEntity0->GetSolid(); + int solid1 = pEntity1->GetSolid(); + int nSolidFlags0 = pEntity0->GetSolidFlags(); + int nSolidFlags1 = pEntity1->GetSolidFlags(); +#endif + + 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; + return 1; +} + +int CCollisionEvent::ShouldSolvePenetration( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1, float dt ) +{ + CallbackContext callback(this); + // solve it yourself here and return 0, or have the default implementation do it + if ( pGameData0 == pGameData1 ) + { + if ( pObj0->GetGameFlags() & FVPHYSICS_PART_OF_RAGDOLL ) + { + // this is a ragdoll, self penetrating + C_BaseEntity *pEnt = reinterpret_cast(pGameData0); + C_BaseAnimating *pAnim = pEnt->GetBaseAnimating(); + + if ( pAnim && pAnim->m_pRagdoll ) + { + IPhysicsConstraintGroup *pGroup = pAnim->m_pRagdoll->GetConstraintGroup(); + if ( pGroup ) + { + pGroup->SolvePenetration( pObj0, pObj1 ); + return false; + } + } + } + } + + return true; +} + + +// A class that implements an IClientSystem for physics +class CPhysicsSystem : public CAutoGameSystemPerFrame +{ +public: + CPhysicsSystem( char const *name ) : CAutoGameSystemPerFrame( name ) + { + } + + // HACKHACK: PhysicsDLLInit() is called explicitly because it requires a parameter + virtual bool Init(); + virtual void Shutdown(); + + // Level init, shutdown + virtual void LevelInitPreEntity(); + virtual void LevelInitPostEntity(); + + // The level is shutdown in two parts + virtual void LevelShutdownPreEntity(); + + virtual void LevelShutdownPostEntity(); + + void AddImpactSound( void *pGameData, IPhysicsObject *pObject, int surfaceProps, int surfacePropsHit, float volume, float speed ); + + virtual void Update( float frametime ); + + void PhysicsSimulate(); + +private: + physicssound::soundlist_t m_impactSounds; +}; + +static CPhysicsSystem g_PhysicsSystem( "CPhysicsSystem" ); +// singleton to hook into the client system +IGameSystem *PhysicsGameSystem( void ) +{ + return &g_PhysicsSystem; +} + + +// HACKHACK: PhysicsDLLInit() is called explicitly because it requires a parameter +bool CPhysicsSystem::Init() +{ + return true; +} + +void CPhysicsSystem::Shutdown() +{ +} + +// Level init, shutdown +void CPhysicsSystem::LevelInitPreEntity( void ) +{ + m_impactSounds.RemoveAll(); + PrecachePhysicsSounds(); +} + +void CPhysicsSystem::LevelInitPostEntity( void ) +{ + PhysicsLevelInit(); +} + +// The level is shutdown in two parts +void CPhysicsSystem::LevelShutdownPreEntity() +{ + if ( physenv ) + { + // we may have deleted multiple objects including the world by now, so + // don't try to wake them up + physenv->SetQuickDelete( true ); + } +} + +void CPhysicsSystem::LevelShutdownPostEntity() +{ + if ( physenv ) + { + // environment destroys all objects + // entities are gone, so this is safe now + physics->DestroyEnvironment( physenv ); + } + physics->DestroyObjectPairHash( g_EntityCollisionHash ); + g_EntityCollisionHash = NULL; + + physics->DestroyAllCollisionSets(); + + physenv = NULL; + g_PhysWorldObject = NULL; +} + +void CPhysicsSystem::AddImpactSound( void *pGameData, IPhysicsObject *pObject, int surfaceProps, int surfacePropsHit, float volume, float speed ) +{ + physicssound::AddImpactSound( m_impactSounds, pGameData, SOUND_FROM_WORLD, CHAN_STATIC, pObject, surfaceProps, surfacePropsHit, volume, speed ); +} + + +void CPhysicsSystem::Update( float frametime ) +{ + // THIS WAS MOVED TO POST-ENTITY SIM + //PhysicsSimulate(); +} + + +void CPhysicsSystem::PhysicsSimulate() +{ + VPROF_BUDGET( "CPhysicsSystem::PhysicsSimulate", VPROF_BUDGETGROUP_PHYSICS ); + float frametime = gpGlobals->frametime; + + if ( physenv ) + { + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %d", __FUNCTION__, physenv->GetActiveObjectCount() ); + + g_Collisions.BufferTouchEvents( true ); +#ifdef _DEBUG + physenv->DebugCheckContacts(); +#endif + physenv->Simulate( frametime * cl_phys_timescale.GetFloat() ); + + 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++ ) + { + C_BaseEntity *pEntity = reinterpret_cast(pActiveList[i]->GetGameData()); + if ( pEntity ) + { + if ( pEntity->CollisionProp()->DoesVPhysicsInvalidateSurroundingBox() ) + { + pEntity->CollisionProp()->MarkSurroundingBoundsDirty(); + } + pEntity->VPhysicsUpdate( pActiveList[i] ); + } + } + } + + g_Collisions.BufferTouchEvents( false ); + g_Collisions.FrameUpdate(); + } + physicssound::PlayImpactSounds( m_impactSounds ); +} + + +void PhysicsSimulate() +{ + g_PhysicsSystem.PhysicsSimulate(); +} + + + +CCollisionEvent::CCollisionEvent( void ) +{ +} + +void CCollisionEvent::ObjectSound( int index, vcollisionevent_t *pEvent ) +{ + IPhysicsObject *pObject = pEvent->pObjects[index]; + if ( !pObject || pObject->IsStatic() ) + return; + + float speed = pEvent->collisionSpeed * pEvent->collisionSpeed; + int surfaceProps = pEvent->surfaceProps[index]; + + void *pGameData = pObject->GetGameData(); + + if ( pGameData ) + { + float volume = speed * (1.0f/(320.0f*320.0f)); // max volume at 320 in/s + + if ( volume > 1.0f ) + volume = 1.0f; + + if ( surfaceProps >= 0 ) + { + g_PhysicsSystem.AddImpactSound( pGameData, pObject, surfaceProps, pEvent->surfaceProps[!index], volume, speed ); + } + } +} + +void CCollisionEvent::PostCollision( vcollisionevent_t *pEvent ) +{ + CallbackContext callback(this); + if ( pEvent->deltaCollisionTime > 0.1f && pEvent->collisionSpeed > 70 ) + { + ObjectSound( 0, pEvent ); + ObjectSound( 1, pEvent ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCollisionEvent::FrameUpdate( void ) +{ + UpdateFrictionSounds(); + UpdateTouchEvents(); + UpdateFluidEvents(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCollisionEvent::UpdateTouchEvents( void ) +{ + // Turn on buffering in case new touch events occur during processing + bool bOldTouchEvents = m_bBufferTouchEvents; + m_bBufferTouchEvents = true; + for ( int 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(); + m_bBufferTouchEvents = bOldTouchEvents; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEntity0 - +// *pEntity1 - +// touchType - +//----------------------------------------------------------------------------- +void CCollisionEvent::AddTouchEvent( C_BaseEntity *pEntity0, C_BaseEntity *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; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pObject1 - +// *pObject2 - +// *pTouchData - +//----------------------------------------------------------------------------- +void CCollisionEvent::StartTouch( IPhysicsObject *pObject1, IPhysicsObject *pObject2, IPhysicsCollisionData *pTouchData ) +{ + CallbackContext callback(this); + C_BaseEntity *pEntity1 = static_cast(pObject1->GetGameData()); + C_BaseEntity *pEntity2 = static_cast(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 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEntity0 - +// *pEntity1 - +//----------------------------------------------------------------------------- +void CCollisionEvent::DispatchStartTouch( C_BaseEntity *pEntity0, C_BaseEntity *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 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pObject1 - +// *pObject2 - +// *pTouchData - +//----------------------------------------------------------------------------- +void CCollisionEvent::EndTouch( IPhysicsObject *pObject1, IPhysicsObject *pObject2, IPhysicsCollisionData *pTouchData ) +{ + CallbackContext callback(this); + C_BaseEntity *pEntity1 = static_cast(pObject1->GetGameData()); + C_BaseEntity *pEntity2 = static_cast(pObject2->GetGameData()); + + if ( !pEntity1 || !pEntity2 ) + return; + + if ( !m_bBufferTouchEvents ) + { + DispatchEndTouch( pEntity1, pEntity2 ); + } + else + { + AddTouchEvent( pEntity1, pEntity2, TOUCH_END, vec3_origin, vec3_origin ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEntity0 - +// *pEntity1 - +//----------------------------------------------------------------------------- +void CCollisionEvent::DispatchEndTouch( C_BaseEntity *pEntity0, C_BaseEntity *pEntity1 ) +{ + // frees the event-driven touchlinks + pEntity0->PhysicsNotifyOtherOfUntouch( pEntity0, pEntity1 ); + pEntity1->PhysicsNotifyOtherOfUntouch( pEntity1, pEntity0 ); +} + +void CCollisionEvent::Friction( IPhysicsObject *pObject, float energy, int surfaceProps, int surfacePropsHit, IPhysicsCollisionData *pData ) +{ + CallbackContext callback(this); + if ( energy < 0.05f || surfaceProps < 0 ) + return; + + //Get our friction information + Vector vecPos, vecVel; + pData->GetContactPoint( vecPos ); + pObject->GetVelocityAtPoint( vecPos, &vecVel ); + + CBaseEntity *pEntity = reinterpret_cast(pObject->GetGameData()); + + if ( pEntity ) + { + friction_t *pFriction = g_Collisions.FindFriction( pEntity ); + + if ( (gpGlobals->maxClients > 1) && 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; + } + } + + PhysFrictionSound( pEntity, 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::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] ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &matrix - +// &normal - +// Output : static int +//----------------------------------------------------------------------------- +static int BestAxisMatchingNormal( matrix3x4_t &matrix, const Vector &normal ) +{ + float bestDot = -1; + int best = 0; + for ( int i = 0; i < 3; i++ ) + { + Vector tmp; + MatrixGetColumn( matrix, i, tmp ); + float dot = fabs(DotProduct( tmp, normal )); + if ( dot > bestDot ) + { + bestDot = dot; + best = i; + } + } + + return best; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pFluid - +// *pObject - +// *pEntity - +//----------------------------------------------------------------------------- +void PhysicsSplash( IPhysicsFluidController *pFluid, IPhysicsObject *pObject, CBaseEntity *pEntity ) +{ + //FIXME: For now just allow ragdolls for E3 - jdw + if ( ( pObject->GetGameFlags() & FVPHYSICS_PART_OF_RAGDOLL ) == false ) + return; + + Vector velocity; + pObject->GetVelocity( &velocity, NULL ); + + float impactSpeed = velocity.Length(); + + if ( impactSpeed < 25.0f ) + return; + + Vector normal; + float dist; + pFluid->GetSurfacePlane( &normal, &dist ); + + matrix3x4_t &matrix = pEntity->EntityToWorldTransform(); + + // Find the local axis that best matches the water surface normal + int bestAxis = BestAxisMatchingNormal( matrix, normal ); + + Vector tangent, binormal; + MatrixGetColumn( matrix, (bestAxis+1)%3, tangent ); + binormal = CrossProduct( normal, tangent ); + VectorNormalize( binormal ); + tangent = CrossProduct( binormal, normal ); + VectorNormalize( tangent ); + + // Now we have a basis tangent to the surface that matches the object's local orientation as well as possible + // compute an OBB using this basis + + // Get object extents in basis + Vector tanPts[2], binPts[2]; + tanPts[0] = physcollision->CollideGetExtent( pObject->GetCollide(), pEntity->GetAbsOrigin(), pEntity->GetAbsAngles(), -tangent ); + tanPts[1] = physcollision->CollideGetExtent( pObject->GetCollide(), pEntity->GetAbsOrigin(), pEntity->GetAbsAngles(), tangent ); + binPts[0] = physcollision->CollideGetExtent( pObject->GetCollide(), pEntity->GetAbsOrigin(), pEntity->GetAbsAngles(), -binormal ); + binPts[1] = physcollision->CollideGetExtent( pObject->GetCollide(), pEntity->GetAbsOrigin(), pEntity->GetAbsAngles(), binormal ); + + // now compute the centered bbox + float mins[2], maxs[2], center[2], extents[2]; + mins[0] = DotProduct( tanPts[0], tangent ); + maxs[0] = DotProduct( tanPts[1], tangent ); + + mins[1] = DotProduct( binPts[0], binormal ); + maxs[1] = DotProduct( binPts[1], binormal ); + + center[0] = 0.5 * (mins[0] + maxs[0]); + center[1] = 0.5 * (mins[1] + maxs[1]); + + extents[0] = maxs[0] - center[0]; + extents[1] = maxs[1] - center[1]; + + Vector centerPoint = center[0] * tangent + center[1] * binormal + dist * normal; + + Vector axes[2]; + axes[0] = (maxs[0] - center[0]) * tangent; + axes[1] = (maxs[1] - center[1]) * binormal; + + // visualize OBB hit + /* + Vector corner1 = centerPoint - axes[0] - axes[1]; + Vector corner2 = centerPoint + axes[0] - axes[1]; + Vector corner3 = centerPoint + axes[0] + axes[1]; + Vector corner4 = centerPoint - axes[0] + axes[1]; + NDebugOverlay::Line( corner1, corner2, 0, 0, 255, false, 10 ); + NDebugOverlay::Line( corner2, corner3, 0, 0, 255, false, 10 ); + NDebugOverlay::Line( corner3, corner4, 0, 0, 255, false, 10 ); + NDebugOverlay::Line( corner4, corner1, 0, 0, 255, false, 10 ); + */ + + Vector corner[4]; + + corner[0] = centerPoint - axes[0] - axes[1]; + corner[1] = centerPoint + axes[0] - axes[1]; + corner[2] = centerPoint + axes[0] + axes[1]; + corner[3] = centerPoint - axes[0] + axes[1]; + + int contents = enginetrace->GetPointContents( centerPoint-Vector(0,0,2) ); + + bool bInSlime = ( contents & CONTENTS_SLIME ) ? true : false; + + Vector color = vec3_origin; + float luminosity = 1.0f; + + if ( !bInSlime ) + { + // Get our lighting information + FX_GetSplashLighting( centerPoint + ( normal * 8.0f ), &color, &luminosity ); + } + + if ( impactSpeed > 150 ) + { + if ( bInSlime ) + { + FX_GunshotSlimeSplash( centerPoint, normal, random->RandomFloat( 8, 10 ) ); + } + else + { + FX_GunshotSplash( centerPoint, normal, random->RandomFloat( 8, 10 ) ); + } + } + else if ( !bInSlime ) + { + FX_WaterRipple( centerPoint, 1.5f, &color, 1.5f, luminosity ); + } + + int splashes = 4; + Vector point; + + for ( int i = 0; i < splashes; i++ ) + { + point = RandomVector( -32.0f, 32.0f ); + point[2] = 0.0f; + + point += corner[i]; + + if ( impactSpeed > 150 ) + { + if ( bInSlime ) + { + FX_GunshotSlimeSplash( centerPoint, normal, random->RandomFloat( 4, 6 ) ); + } + else + { + FX_GunshotSplash( centerPoint, normal, random->RandomFloat( 4, 6 ) ); + } + } + else if ( !bInSlime ) + { + FX_WaterRipple( point, random->RandomFloat( 0.25f, 0.5f ), &color, luminosity, random->RandomFloat( 0.5f, 1.0f ) ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +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); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEntity - +// Output : float +//----------------------------------------------------------------------------- +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; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pObject - +// *pFluid - +//----------------------------------------------------------------------------- +void CCollisionEvent::FluidStartTouch( IPhysicsObject *pObject, IPhysicsFluidController *pFluid ) +{ + CallbackContext callback(this); + if ( ( pObject == NULL ) || ( pFluid == NULL ) ) + return; + + CBaseEntity *pEntity = static_cast(pObject->GetGameData()); + + if ( pEntity ) + { + float timeSinceLastCollision = DeltaTimeSinceLastFluid( pEntity ); + + if ( timeSinceLastCollision < 0.5f ) + return; + + PhysicsSplash( pFluid, pObject, pEntity ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pObject - +// *pFluid - +//----------------------------------------------------------------------------- +void CCollisionEvent::FluidEndTouch( IPhysicsObject *pObject, IPhysicsFluidController *pFluid ) +{ + CallbackContext callback(this); + //FIXME: Do nothing for now +} + +IPhysicsObject *GetWorldPhysObject ( void ) +{ + return g_PhysWorldObject; +} + +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 ); + int entindex = pEntity->entindex(); + + // clientside created entites doesn't have a valid entindex, let 'world' play the sound for them + if ( entindex < 0 ) + entindex = 0; + + pFriction->patch = CSoundEnvelopeController::GetController().SoundCreate( + filter, 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 ); + } +} + +float PhysGetNextSimTime() +{ + return physenv->GetSimulationTime() + gpGlobals->frametime * cl_phys_timescale.GetFloat(); +} + +float PhysGetSyncCreateTime() +{ + float nextTime = physenv->GetNextFrameTime(); + float simTime = PhysGetNextSimTime(); + if ( nextTime < simTime ) + { + // The next simulation frame begins before the end of this frame + // so create physics objects at that time so that they will reach the current + // position at curtime. Otherwise the physics object will simulate forward from curtime + // and pop into the future a bit at this point of transition + return gpGlobals->curtime + nextTime - simTime; + } + return gpGlobals->curtime; +} -- cgit v1.2.3