source-engine/game/client/physics.cpp

1046 lines
30 KiB
C++

//========= 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 ) = default;
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<C_BaseEntity *>(pObject->GetGameData());
if (pEntity && pEntity->HasDataObjectType(VPHYSICSWATCHER))
{
ReportVPhysicsStateChanged( pObject, pEntity, true );
}
}
virtual void ObjectSleep( IPhysicsObject *pObject )
{
C_BaseEntity *pEntity = static_cast<C_BaseEntity *>(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<fluidevent_t> m_fluidEvents;
CUtlVector<touchevent_t> 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<C_BaseEntity *>(pGameData0);
C_BaseEntity *pEntity1 = static_cast<C_BaseEntity *>(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<C_BaseEntity *>(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<C_BaseEntity *>(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();
}
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<C_BaseEntity *>(pObject1->GetGameData());
C_BaseEntity *pEntity2 = static_cast<C_BaseEntity *>(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<C_BaseEntity *>(pObject1->GetGameData());
C_BaseEntity *pEntity2 = static_cast<C_BaseEntity *>(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<CBaseEntity *>(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<CBaseEntity *>(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;
}