You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2228 lines
66 KiB
2228 lines
66 KiB
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
#include "cbase.h"
|
|
#include "tier0/threadtools.h"
|
|
#include "physics_constraint.h"
|
|
#include "physics_spring.h"
|
|
#include "physics_fluid.h"
|
|
#include "physics_shadow.h"
|
|
#include "physics_motioncontroller.h"
|
|
#include "physics_vehicle.h"
|
|
#include "physics_virtualmesh.h"
|
|
#include "utlmultilist.h"
|
|
#include "vphysics/constraints.h"
|
|
#include "vphysics/vehicles.h"
|
|
#include "vphysics/object_hash.h"
|
|
#include "vphysics/performance.h"
|
|
#include "vphysics/stats.h"
|
|
#include "vphysics/player_controller.h"
|
|
#include "vphysics_saverestore.h"
|
|
#include "vphysics_internal.h"
|
|
|
|
#include "ivu_linear_macros.hxx"
|
|
#include "ivp_collision_filter.hxx"
|
|
#include "ivp_listener_collision.hxx"
|
|
#include "ivp_listener_object.hxx"
|
|
#include "ivp_mindist.hxx"
|
|
#include "ivp_friction.hxx"
|
|
#include "ivp_anomaly_manager.hxx"
|
|
#include "ivp_time.hxx"
|
|
#include "ivp_listener_psi.hxx"
|
|
#include "ivp_phantom.hxx"
|
|
#include "ivp_range_manager.hxx"
|
|
#include "ivp_clustering_visualizer.hxx"
|
|
#include "ivp_mindist_intern.hxx"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
IPhysicsObjectPairHash *CreateObjectPairHash();
|
|
|
|
IVP_Synapse_Friction *GetOppositeSynapse( IVP_Synapse_Friction *pfriction )
|
|
{
|
|
IVP_Contact_Point *contact = pfriction->get_contact_point();
|
|
IVP_Synapse_Friction *ptest = contact->get_synapse(0);
|
|
if ( ptest == pfriction )
|
|
{
|
|
ptest = contact->get_synapse(1);
|
|
}
|
|
|
|
return ptest;
|
|
}
|
|
|
|
IVP_Real_Object *GetOppositeSynapseObject( IVP_Synapse_Friction *pfriction )
|
|
{
|
|
IVP_Synapse_Friction *opposite = GetOppositeSynapse( pfriction );
|
|
return opposite->get_object();
|
|
}
|
|
|
|
// simple delete queue
|
|
class IDeleteQueueItem
|
|
{
|
|
public:
|
|
// Add a virtual destructor to silence the clang warning.
|
|
// Note that this destructor doesn't actually do anything -- you
|
|
// still have to use the Delete() then delete pattern.
|
|
virtual ~IDeleteQueueItem() {}
|
|
virtual void Delete() = 0;
|
|
};
|
|
|
|
template <typename T>
|
|
class CDeleteProxy : public IDeleteQueueItem
|
|
{
|
|
public:
|
|
CDeleteProxy(T *pItem) : m_pItem(pItem) {}
|
|
virtual void Delete() { delete m_pItem; }
|
|
private:
|
|
T *m_pItem;
|
|
};
|
|
|
|
class CDeleteQueue
|
|
{
|
|
public:
|
|
void Add( IDeleteQueueItem *pItem )
|
|
{
|
|
m_list.AddToTail( pItem );
|
|
}
|
|
|
|
template <typename T>
|
|
void QueueForDelete( T *pItem )
|
|
{
|
|
Add( new CDeleteProxy<T>(pItem) );
|
|
}
|
|
void DeleteAll()
|
|
{
|
|
for ( int i = m_list.Count()-1; i >= 0; --i)
|
|
{
|
|
m_list[i]->Delete();
|
|
delete m_list[i];
|
|
}
|
|
m_list.RemoveAll();
|
|
}
|
|
|
|
private:
|
|
CUtlVector< IDeleteQueueItem * > m_list;
|
|
};
|
|
|
|
class CPhysicsCollisionData : public IPhysicsCollisionData
|
|
{
|
|
public:
|
|
CPhysicsCollisionData( IVP_Contact_Situation *contact ) : m_pContact(contact) {}
|
|
|
|
virtual void GetSurfaceNormal( Vector &out ) { ConvertDirectionToHL( m_pContact->surf_normal, out ); }
|
|
virtual void GetContactPoint( Vector &out ) { ConvertPositionToHL( m_pContact->contact_point_ws, out ); }
|
|
virtual void GetContactSpeed( Vector &out ) { ConvertPositionToHL( m_pContact->speed, out ); }
|
|
|
|
const IVP_Contact_Situation *m_pContact;
|
|
};
|
|
|
|
class CPhysicsFrictionData : public IPhysicsCollisionData
|
|
{
|
|
public:
|
|
CPhysicsFrictionData( IVP_Synapse_Friction *synapse, float sign ) : m_sign(sign)
|
|
{
|
|
m_pPoint = synapse->get_contact_point();
|
|
m_pContact = NULL;
|
|
}
|
|
|
|
CPhysicsFrictionData( IVP_Event_Friction *pEvent ) : m_sign(1.0f)
|
|
{
|
|
m_pPoint = pEvent->friction_handle;
|
|
m_pContact = pEvent->contact_situation;
|
|
}
|
|
|
|
virtual void GetSurfaceNormal( Vector &out )
|
|
{
|
|
if ( m_pContact )
|
|
{
|
|
ConvertDirectionToHL( m_pContact->surf_normal, out );
|
|
}
|
|
else
|
|
{
|
|
IVP_U_Float_Point normal;
|
|
IVP_Contact_Point_API::get_surface_normal_ws(const_cast<IVP_Contact_Point *>(m_pPoint), &normal);
|
|
ConvertDirectionToHL( normal, out );
|
|
out *= m_sign;
|
|
}
|
|
}
|
|
virtual void GetContactPoint( Vector &out )
|
|
{
|
|
if ( m_pContact )
|
|
{
|
|
ConvertPositionToHL( m_pContact->contact_point_ws, out );
|
|
}
|
|
else
|
|
{
|
|
ConvertPositionToHL( *m_pPoint->get_contact_point_ws(), out );
|
|
}
|
|
}
|
|
virtual void GetContactSpeed( Vector &out )
|
|
{
|
|
if ( m_pContact )
|
|
{
|
|
ConvertPositionToHL( m_pContact->speed, out );
|
|
}
|
|
else
|
|
{
|
|
out.Init();
|
|
}
|
|
}
|
|
|
|
private:
|
|
const IVP_Contact_Point *m_pPoint;
|
|
float m_sign;
|
|
const IVP_Contact_Situation *m_pContact;
|
|
};
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Routes object event callbacks to game code
|
|
//-----------------------------------------------------------------------------
|
|
class CSleepObjects : public IVP_Listener_Object
|
|
{
|
|
public:
|
|
CSleepObjects( void ) : IVP_Listener_Object()
|
|
{
|
|
m_pCallback = NULL;
|
|
m_lastScrapeTime = 0.0f;
|
|
}
|
|
|
|
void SetHandler( IPhysicsObjectEvent *pListener )
|
|
{
|
|
m_pCallback = pListener;
|
|
}
|
|
|
|
void Remove( int index )
|
|
{
|
|
// fast remove preserves indices except for the last element (moved into the empty spot)
|
|
m_activeObjects.FastRemove(index);
|
|
// If this isn't the last element, shift its index over
|
|
if ( index < m_activeObjects.Count() )
|
|
{
|
|
m_activeObjects[index]->SetActiveIndex( index );
|
|
}
|
|
}
|
|
|
|
void DeleteObject( CPhysicsObject *pObject )
|
|
{
|
|
int index = pObject->GetActiveIndex();
|
|
if ( index < m_activeObjects.Count() )
|
|
{
|
|
Assert( m_activeObjects[index] == pObject );
|
|
Remove( index );
|
|
pObject->SetActiveIndex( 0xFFFF );
|
|
}
|
|
else
|
|
{
|
|
Assert(index==0xFFFF);
|
|
}
|
|
|
|
}
|
|
|
|
void event_object_deleted( IVP_Event_Object *pEvent )
|
|
{
|
|
CPhysicsObject *pObject = static_cast<CPhysicsObject *>(pEvent->real_object->client_data);
|
|
if ( !pObject )
|
|
return;
|
|
|
|
DeleteObject(pObject);
|
|
}
|
|
|
|
void event_object_created( IVP_Event_Object *pEvent )
|
|
{
|
|
}
|
|
|
|
void event_object_revived( IVP_Event_Object *pEvent )
|
|
{
|
|
CPhysicsObject *pObject = static_cast<CPhysicsObject *>(pEvent->real_object->client_data);
|
|
if ( !pObject )
|
|
return;
|
|
|
|
int sleepState = pObject->GetSleepState();
|
|
|
|
pObject->NotifyWake();
|
|
|
|
// asleep, but already in active list
|
|
if ( sleepState == OBJ_STARTSLEEP )
|
|
return;
|
|
|
|
// don't track static objects (like the world). That way we only track objects that will move
|
|
if ( pObject->GetObject()->get_movement_state() != IVP_MT_STATIC )
|
|
{
|
|
Assert(pObject->GetActiveIndex()==0xFFFF);
|
|
if ( pObject->GetActiveIndex()!=0xFFFF)
|
|
return;
|
|
|
|
int index = m_activeObjects.AddToTail( pObject );
|
|
pObject->SetActiveIndex( index );
|
|
}
|
|
if ( m_pCallback )
|
|
{
|
|
m_pCallback->ObjectWake( pObject );
|
|
}
|
|
}
|
|
|
|
void event_object_frozen( IVP_Event_Object *pEvent )
|
|
{
|
|
CPhysicsObject *pObject = static_cast<CPhysicsObject *>(pEvent->real_object->client_data);
|
|
if ( !pObject )
|
|
return;
|
|
|
|
pObject->NotifySleep();
|
|
if ( m_pCallback )
|
|
{
|
|
m_pCallback->ObjectSleep( pObject );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: This walks the objects in the environment and generates friction events
|
|
// for any scraping that is occurring.
|
|
//-----------------------------------------------------------------------------
|
|
void ProcessActiveObjects( IVP_Environment *pEnvironment, IPhysicsCollisionEvent *pEvent )
|
|
{
|
|
// FIXME: Is this correct? Shouldn't it do next PSI - lastScrape?
|
|
float nextTime = pEnvironment->get_old_time_of_last_PSI().get_time();
|
|
float delta = nextTime - m_lastScrapeTime;
|
|
|
|
// only process if we have done a PSI
|
|
if ( delta < pEnvironment->get_delta_PSI_time() )
|
|
return;
|
|
|
|
float t = 0.0f;
|
|
if ( delta != 0.0f )
|
|
{
|
|
t = 1.0f / delta;
|
|
}
|
|
|
|
m_lastScrapeTime = nextTime;
|
|
|
|
// UNDONE: This only calls friciton for one object in each pair.
|
|
// UNDONE: Split energy in half and call for both objects?
|
|
// UNDONE: Don't split/call if one object is static (like the world)?
|
|
for ( int i = 0; i < m_activeObjects.Count(); i++ )
|
|
{
|
|
CPhysicsObject *pObject = m_activeObjects[i];
|
|
IVP_Real_Object *ivpObject = pObject->GetObject();
|
|
|
|
// no friction callbacks for this object
|
|
if ( ! (pObject->CallbackFlags() & CALLBACK_GLOBAL_FRICTION) )
|
|
continue;
|
|
|
|
// UNDONE: IVP_Synapse_Friction is supposed to be opaque. Is there a better way
|
|
// to implement this? Using the friction listener is much more work for the CPU
|
|
// and considers sleeping objects.
|
|
IVP_Synapse_Friction *pfriction = ivpObject->get_first_friction_synapse();
|
|
while ( pfriction )
|
|
{
|
|
IVP_Contact_Point *contact = pfriction->get_contact_point();
|
|
IVP_Synapse_Friction *pOpposite = GetOppositeSynapse( pfriction );
|
|
IVP_Real_Object *pobj = pOpposite->get_object();
|
|
CPhysicsObject *pScrape = (CPhysicsObject *)pobj->client_data;
|
|
|
|
// friction callbacks for this object?
|
|
if ( pScrape->CallbackFlags() & CALLBACK_GLOBAL_FRICTION )
|
|
{
|
|
float energy = IVP_Contact_Point_API::get_eliminated_energy( contact );
|
|
if ( energy )
|
|
{
|
|
// scrape with an estimate for the energy per unit mass
|
|
// This assumes that the game is interested in some measure of vibration
|
|
// for sound effects. This also assumes that more massive objects require
|
|
// more energy to vibrate.
|
|
energy = energy * t * ivpObject->get_core()->get_inv_mass();
|
|
|
|
if ( energy > 0.05f )
|
|
{
|
|
int hitSurface = pScrape->GetMaterialIndexInternal();
|
|
|
|
int materialIndex = pOpposite->get_material_index();
|
|
if ( materialIndex )
|
|
{
|
|
// use the per-triangle material if it has one
|
|
hitSurface = physprops->RemapIVPMaterialIndex( materialIndex );
|
|
}
|
|
|
|
float sign = (pfriction == contact->get_synapse(0)) ? 1 : -1;
|
|
|
|
CPhysicsFrictionData data(pfriction, sign);
|
|
|
|
pEvent->Friction( pObject, ConvertEnergyToHL(energy), pObject->GetMaterialIndexInternal(), hitSurface, &data );
|
|
}
|
|
IVP_Contact_Point_API::reset_eliminated_energy( contact );
|
|
}
|
|
}
|
|
pfriction = pfriction->get_next();
|
|
}
|
|
}
|
|
}
|
|
void DebugCheckContacts( IVP_Environment *pEnvironment )
|
|
{
|
|
IVP_Mindist_Manager *pManager = pEnvironment->get_mindist_manager();
|
|
|
|
for( IVP_Mindist *mdist = pManager->exact_mindists; mdist != NULL; mdist = mdist->next )
|
|
{
|
|
IVP_Real_Object *obj[2];
|
|
mdist->get_objects( obj );
|
|
IVP_BOOL check = pEnvironment->get_collision_filter()->check_objects_for_collision_detection( obj[0], obj[1] );
|
|
Assert(check);
|
|
if ( !check )
|
|
{
|
|
Msg("Changed collision rules for %s vs. %s without calling recheck!\n", obj[0]->get_name(), obj[1]->get_name() );
|
|
}
|
|
}
|
|
}
|
|
int GetActiveObjectCount( void ) const
|
|
{
|
|
return m_activeObjects.Count();
|
|
}
|
|
void GetActiveObjects( IPhysicsObject **pOutputObjectList ) const
|
|
{
|
|
for ( int i = 0; i < m_activeObjects.Count(); i++ )
|
|
{
|
|
pOutputObjectList[i] = m_activeObjects[i];
|
|
}
|
|
}
|
|
void UpdateSleepObjects( void )
|
|
{
|
|
int i;
|
|
|
|
CUtlVector<CPhysicsObject *> sleepObjects;
|
|
|
|
for ( i = 0; i < m_activeObjects.Count(); i++ )
|
|
{
|
|
CPhysicsObject *pObject = m_activeObjects[i];
|
|
|
|
if ( pObject->GetSleepState() != OBJ_AWAKE )
|
|
{
|
|
sleepObjects.AddToTail( pObject );
|
|
}
|
|
}
|
|
|
|
for ( i = sleepObjects.Count()-1; i >= 0; --i )
|
|
{
|
|
// put fully to sleep
|
|
sleepObjects[i]->NotifySleep();
|
|
|
|
// remove from the active list
|
|
DeleteObject( sleepObjects[i] );
|
|
}
|
|
}
|
|
|
|
private:
|
|
CUtlVector<CPhysicsObject *> m_activeObjects;
|
|
float m_lastScrapeTime;
|
|
IPhysicsObjectEvent *m_pCallback;
|
|
};
|
|
|
|
class CEmptyCollisionListener : public IPhysicsCollisionEvent
|
|
{
|
|
public:
|
|
virtual void PreCollision( vcollisionevent_t *pEvent ) {}
|
|
virtual void PostCollision( vcollisionevent_t *pEvent ) {}
|
|
|
|
// This is a scrape event. The object has scraped across another object consuming the indicated energy
|
|
virtual void Friction( IPhysicsObject *pObject, float energy, int surfaceProps, int surfacePropsHit, IPhysicsCollisionData *pData ) {}
|
|
|
|
virtual void StartTouch( IPhysicsObject *pObject1, IPhysicsObject *pObject2, IPhysicsCollisionData *pTouchData ) {}
|
|
virtual void EndTouch( IPhysicsObject *pObject1, IPhysicsObject *pObject2, IPhysicsCollisionData *pTouchData ) {}
|
|
|
|
virtual void FluidStartTouch( IPhysicsObject *pObject, IPhysicsFluidController *pFluid ) {}
|
|
virtual void FluidEndTouch( IPhysicsObject *pObject, IPhysicsFluidController *pFluid ) {}
|
|
|
|
virtual void ObjectEnterTrigger( IPhysicsObject *pTrigger, IPhysicsObject *pObject ) {}
|
|
virtual void ObjectLeaveTrigger( IPhysicsObject *pTrigger, IPhysicsObject *pObject ) {}
|
|
|
|
virtual void PostSimulationFrame() {}
|
|
};
|
|
|
|
CEmptyCollisionListener g_EmptyCollisionListener;
|
|
|
|
#define ALL_COLLISION_FLAGS (IVP_LISTENER_COLLISION_CALLBACK_PRE_COLLISION|IVP_LISTENER_COLLISION_CALLBACK_POST_COLLISION|IVP_LISTENER_COLLISION_CALLBACK_FRICTION)
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Routes collision event callbacks to game code
|
|
//-----------------------------------------------------------------------------
|
|
class CPhysicsListenerCollision : public IVP_Listener_Collision, public IVP_Listener_Phantom
|
|
{
|
|
public:
|
|
CPhysicsListenerCollision();
|
|
|
|
void SetHandler( IPhysicsCollisionEvent *pCallback )
|
|
{
|
|
m_pCallback = pCallback;
|
|
}
|
|
IPhysicsCollisionEvent *GetHandler() { return m_pCallback; }
|
|
|
|
virtual void event_pre_collision( IVP_Event_Collision *pEvent )
|
|
{
|
|
m_event.isCollision = false;
|
|
m_event.isShadowCollision = false;
|
|
IVP_Contact_Situation *contact = pEvent->contact_situation;
|
|
CPhysicsObject *pObject1 = static_cast<CPhysicsObject *>(contact->objects[0]->client_data);
|
|
CPhysicsObject *pObject2 = static_cast<CPhysicsObject *>(contact->objects[1]->client_data);
|
|
if ( !pObject1 || !pObject2 )
|
|
return;
|
|
|
|
unsigned int flags1 = pObject1->CallbackFlags();
|
|
unsigned int flags2 = pObject2->CallbackFlags();
|
|
|
|
m_event.isCollision = (flags1 & flags2 & CALLBACK_GLOBAL_COLLISION) ? true : false;
|
|
|
|
// only call shadow collisions if one is shadow and the other isn't (hence the xor)
|
|
// (if both are shadow, the collisions happen in AI - if neither, then no callback)
|
|
m_event.isShadowCollision = ((flags1^flags2) & CALLBACK_SHADOW_COLLISION) ? true : false;
|
|
|
|
m_event.pObjects[0] = pObject1;
|
|
m_event.pObjects[1] = pObject2;
|
|
m_event.deltaCollisionTime = pEvent->d_time_since_last_collision;
|
|
// This timer must have been reset or something (constructor initializes time to -1000)
|
|
// Fake the time to 50ms (resets happen often in rolling collisions for some reason)
|
|
if ( m_event.deltaCollisionTime > 999 )
|
|
{
|
|
m_event.deltaCollisionTime = 1.0;
|
|
}
|
|
|
|
|
|
CPhysicsCollisionData data(contact);
|
|
m_event.pInternalData = &data;
|
|
|
|
// clear out any static object collisions unless flagged to keep them
|
|
if ( contact->objects[0]->get_movement_state() == IVP_MT_STATIC )
|
|
{
|
|
// don't call global if disabled
|
|
if ( !(flags2 & CALLBACK_GLOBAL_COLLIDE_STATIC) )
|
|
{
|
|
m_event.isCollision = false;
|
|
}
|
|
}
|
|
if ( contact->objects[1]->get_movement_state() == IVP_MT_STATIC )
|
|
{
|
|
// don't call global if disabled
|
|
if ( !(flags1 & CALLBACK_GLOBAL_COLLIDE_STATIC) )
|
|
{
|
|
m_event.isCollision = false;
|
|
}
|
|
}
|
|
|
|
if ( !m_event.isCollision && !m_event.isShadowCollision )
|
|
return;
|
|
|
|
// look up surface props
|
|
for ( int i = 0; i < 2; i++ )
|
|
{
|
|
m_event.surfaceProps[i] = physprops->GetIVPMaterialIndex( contact->materials[i] );
|
|
if ( m_event.surfaceProps[i] < 0 )
|
|
{
|
|
m_event.surfaceProps[i] = m_event.pObjects[i]->GetMaterialIndex();
|
|
}
|
|
}
|
|
|
|
m_pCallback->PreCollision( &m_event );
|
|
}
|
|
|
|
virtual void event_post_collision( IVP_Event_Collision *pEvent )
|
|
{
|
|
// didn't call preCollision, so don't call postCollision
|
|
if ( !m_event.isCollision && !m_event.isShadowCollision )
|
|
return;
|
|
|
|
IVP_Contact_Situation *contact = pEvent->contact_situation;
|
|
|
|
float collisionSpeed = contact->speed.dot_product(&contact->surf_normal);
|
|
m_event.collisionSpeed = ConvertDistanceToHL( fabs(collisionSpeed) );
|
|
CPhysicsCollisionData data(contact);
|
|
m_event.pInternalData = &data;
|
|
|
|
m_pCallback->PostCollision( &m_event );
|
|
}
|
|
|
|
virtual void event_collision_object_deleted( class IVP_Real_Object *)
|
|
{
|
|
// enable this in constructor
|
|
}
|
|
|
|
virtual void event_friction_created( IVP_Event_Friction *pEvent )
|
|
{
|
|
IVP_Contact_Situation *contact = pEvent->contact_situation;
|
|
CPhysicsObject *pObject1 = static_cast<CPhysicsObject *>(contact->objects[0]->client_data);
|
|
CPhysicsObject *pObject2 = static_cast<CPhysicsObject *>(contact->objects[1]->client_data);
|
|
|
|
if ( !pObject1 || !pObject2 )
|
|
return;
|
|
|
|
unsigned int flags1 = pObject1->CallbackFlags();
|
|
unsigned int flags2 = pObject2->CallbackFlags();
|
|
unsigned int allflags = flags1|flags2;
|
|
|
|
if ( !pObject1->IsStatic() || !pObject2->IsStatic() )
|
|
{
|
|
if ( !pObject1->HasTouchedDynamic() && pObject2->IsMoveable() )
|
|
{
|
|
pObject1->SetTouchedDynamic();
|
|
}
|
|
if ( !pObject2->HasTouchedDynamic() && pObject1->IsMoveable() )
|
|
{
|
|
pObject2->SetTouchedDynamic();
|
|
}
|
|
}
|
|
|
|
bool calltouch = ( allflags & CALLBACK_GLOBAL_TOUCH ) ? true : false;
|
|
if ( !calltouch )
|
|
return;
|
|
|
|
if ( pObject1->IsStatic() || pObject2->IsStatic() )
|
|
{
|
|
if ( !( allflags & CALLBACK_GLOBAL_TOUCH_STATIC ) )
|
|
return;
|
|
}
|
|
|
|
CPhysicsFrictionData data(pEvent);
|
|
m_pCallback->StartTouch( pObject1, pObject2, &data );
|
|
}
|
|
|
|
|
|
virtual void event_friction_deleted( IVP_Event_Friction *pEvent )
|
|
{
|
|
IVP_Contact_Situation *contact = pEvent->contact_situation;
|
|
CPhysicsObject *pObject1 = static_cast<CPhysicsObject *>(contact->objects[0]->client_data);
|
|
CPhysicsObject *pObject2 = static_cast<CPhysicsObject *>(contact->objects[1]->client_data);
|
|
if ( !pObject1 || !pObject2 )
|
|
return;
|
|
|
|
unsigned int flags1 = pObject1->CallbackFlags();
|
|
unsigned int flags2 = pObject2->CallbackFlags();
|
|
|
|
unsigned int allflags = flags1|flags2;
|
|
|
|
bool calltouch = ( allflags & CALLBACK_GLOBAL_TOUCH ) ? true : false;
|
|
if ( !calltouch )
|
|
return;
|
|
|
|
if ( pObject1->IsStatic() || pObject2->IsStatic() )
|
|
{
|
|
if ( !( allflags & CALLBACK_GLOBAL_TOUCH_STATIC ) )
|
|
return;
|
|
}
|
|
|
|
CPhysicsFrictionData data(pEvent);
|
|
m_pCallback->EndTouch( pObject1, pObject2, &data );
|
|
}
|
|
|
|
virtual void event_friction_pair_created( class IVP_Friction_Core_Pair *pair );
|
|
virtual void event_friction_pair_deleted( class IVP_Friction_Core_Pair *pair );
|
|
virtual void mindist_entered_volume( class IVP_Controller_Phantom *controller,class IVP_Mindist_Base *mindist ) {}
|
|
virtual void mindist_left_volume(class IVP_Controller_Phantom *controller, class IVP_Mindist_Base *mindist) {}
|
|
|
|
virtual void core_entered_volume( IVP_Controller_Phantom *controller, IVP_Core *pCore )
|
|
{
|
|
CPhysicsFluidController *pFluid = static_cast<CPhysicsFluidController *>( controller->client_data );
|
|
IVP_Real_Object *pivp = pCore->objects.element_at(0);
|
|
CPhysicsObject *pObject = static_cast<CPhysicsObject *>(pivp->client_data);
|
|
if ( !pObject )
|
|
return;
|
|
|
|
if ( pFluid )
|
|
{
|
|
if ( pObject && (pObject->CallbackFlags() & CALLBACK_FLUID_TOUCH) )
|
|
{
|
|
m_pCallback->FluidStartTouch( pObject, pFluid );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// must be a trigger
|
|
IVP_Real_Object *pTriggerIVP = controller->get_object();
|
|
CPhysicsObject *pTrigger = static_cast<CPhysicsObject *>(pTriggerIVP->client_data);
|
|
|
|
if ( pTrigger )
|
|
{
|
|
m_pCallback->ObjectEnterTrigger( pTrigger, pObject );
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual void core_left_volume( IVP_Controller_Phantom *controller, IVP_Core *pCore )
|
|
{
|
|
CPhysicsFluidController *pFluid = static_cast<CPhysicsFluidController *>( controller->client_data );
|
|
IVP_Real_Object *pivp = pCore->objects.element_at(0);
|
|
CPhysicsObject *pObject = static_cast<CPhysicsObject *>(pivp->client_data);
|
|
if ( !pObject )
|
|
return;
|
|
|
|
if ( pFluid )
|
|
{
|
|
if ( pObject && (pObject->CallbackFlags() & CALLBACK_FLUID_TOUCH) )
|
|
{
|
|
m_pCallback->FluidEndTouch( pObject, pFluid );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// must be a trigger
|
|
IVP_Real_Object *pTriggerIVP = controller->get_object();
|
|
CPhysicsObject *pTrigger = static_cast<CPhysicsObject *>(pTriggerIVP->client_data);
|
|
|
|
if ( pTrigger )
|
|
{
|
|
m_pCallback->ObjectLeaveTrigger( pTrigger, pObject );
|
|
}
|
|
}
|
|
}
|
|
void phantom_is_going_to_be_deleted_event(class IVP_Controller_Phantom *controller) {}
|
|
|
|
void EventPSI( CPhysicsEnvironment *pEnvironment )
|
|
{
|
|
m_pCallback->PostSimulationFrame();
|
|
UpdatePairListPSI( pEnvironment );
|
|
}
|
|
private:
|
|
|
|
struct corepair_t
|
|
{
|
|
corepair_t() {}
|
|
corepair_t( IVP_Friction_Core_Pair *pair )
|
|
{
|
|
int index = ( pair->objs[0] < pair->objs[1] ) ? 0 : 1;
|
|
core0 = pair->objs[index];
|
|
core1 = pair->objs[!index];
|
|
lastImpactTime= pair->last_impact_time_pair;
|
|
}
|
|
|
|
IVP_Core *core0;
|
|
IVP_Core *core1;
|
|
IVP_Time lastImpactTime;
|
|
};
|
|
|
|
static bool CorePairLessFunc( const corepair_t &lhs, const corepair_t &rhs )
|
|
{
|
|
if ( lhs.core0 != rhs.core0 )
|
|
return ( lhs.core0 < rhs.core0 );
|
|
else
|
|
return ( lhs.core1 < rhs.core1 );
|
|
}
|
|
void UpdatePairListPSI( CPhysicsEnvironment *pEnvironment )
|
|
{
|
|
unsigned short index = m_pairList.FirstInorder();
|
|
IVP_Time currentTime = pEnvironment->GetIVPEnvironment()->get_current_time();
|
|
|
|
while ( m_pairList.IsValidIndex(index) )
|
|
{
|
|
unsigned short next = m_pairList.NextInorder( index );
|
|
corepair_t &test = m_pairList.Element(index);
|
|
|
|
// only keep 1 seconds worth of data
|
|
if ( (currentTime - test.lastImpactTime) > 1.0 )
|
|
{
|
|
m_pairList.RemoveAt( index );
|
|
}
|
|
index = next;
|
|
}
|
|
}
|
|
|
|
CUtlRBTree<corepair_t> m_pairList;
|
|
float m_pairListOldestTime;
|
|
|
|
|
|
IPhysicsCollisionEvent *m_pCallback;
|
|
vcollisionevent_t m_event;
|
|
|
|
};
|
|
|
|
|
|
CPhysicsListenerCollision::CPhysicsListenerCollision() : IVP_Listener_Collision( ALL_COLLISION_FLAGS ), m_pCallback(&g_EmptyCollisionListener)
|
|
{
|
|
m_pairList.SetLessFunc( CorePairLessFunc );
|
|
}
|
|
|
|
|
|
void CPhysicsListenerCollision::event_friction_pair_created( IVP_Friction_Core_Pair *pair )
|
|
{
|
|
corepair_t test(pair);
|
|
unsigned short index = m_pairList.Find( test );
|
|
if ( m_pairList.IsValidIndex( index ) )
|
|
{
|
|
corepair_t &save = m_pairList.Element(index);
|
|
// found this one already, update the time
|
|
if ( save.lastImpactTime.get_seconds() > pair->last_impact_time_pair.get_seconds() )
|
|
{
|
|
pair->last_impact_time_pair = save.lastImpactTime;
|
|
}
|
|
else
|
|
{
|
|
save.lastImpactTime = pair->last_impact_time_pair;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( m_pairList.Count() < 16 )
|
|
{
|
|
m_pairList.Insert( test );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CPhysicsListenerCollision::event_friction_pair_deleted( IVP_Friction_Core_Pair *pair )
|
|
{
|
|
corepair_t test(pair);
|
|
unsigned short index = m_pairList.Find( test );
|
|
if ( m_pairList.IsValidIndex( index ) )
|
|
{
|
|
corepair_t &save = m_pairList.Element(index);
|
|
// found this one already, update the time
|
|
if ( save.lastImpactTime.get_seconds() < pair->last_impact_time_pair.get_seconds() )
|
|
{
|
|
save.lastImpactTime = pair->last_impact_time_pair;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( m_pairList.Count() < 16 )
|
|
{
|
|
m_pairList.Insert( test );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#if IVP_ENABLE_VISUALIZER
|
|
|
|
class CCollisionVisualizer : public IVP_Clustering_Visualizer_Shortrange_Callback, public IVP_Clustering_Visualizer_Longrange_Callback
|
|
{
|
|
IVPhysicsDebugOverlay *m_pDebug;
|
|
public:
|
|
CCollisionVisualizer(IVPhysicsDebugOverlay *pDebug) { m_pDebug = pDebug;}
|
|
|
|
void visualize_request()
|
|
{
|
|
Vector origin, extents;
|
|
ConvertPositionToHL( center, origin );
|
|
float hlradius = ConvertDistanceToHL( radius);
|
|
extents.Init( hlradius, hlradius, hlradius );
|
|
m_pDebug->AddBoxOverlay( origin, -extents, extents, vec3_angle, 0, 255, 0, 32, 0.5f);
|
|
}
|
|
|
|
virtual void devisualize_request() {}
|
|
virtual void enable() {}
|
|
virtual void disable() {}
|
|
|
|
void visualize_request_for_node()
|
|
{
|
|
Vector origin, extents;
|
|
ConvertPositionToHL( position, origin );
|
|
ConvertPositionToHL( box_extents, extents );
|
|
|
|
Vector boxOrigin, boxExtents;
|
|
CPhysicsObject *pObject0 = static_cast<CPhysicsObject *>(node_object->client_data);
|
|
pObject0->LocalToWorld( boxOrigin, origin );
|
|
QAngle angles;
|
|
pObject0->GetPosition( NULL, &angles );
|
|
|
|
m_pDebug->AddBoxOverlay( boxOrigin, -extents, extents, angles, 255, 255, 0, 0, 0.5f);
|
|
}
|
|
|
|
void visualize_request_for_intruder_radius()
|
|
{
|
|
Vector origin, extents;
|
|
ConvertPositionToHL( position, origin );
|
|
float hlradius = ConvertDistanceToHL( sphere_radius );
|
|
|
|
extents.Init( hlradius, hlradius, hlradius );
|
|
m_pDebug->AddBoxOverlay( origin, -extents, extents, vec3_angle, 0, 0, 255, 32, 0.25f);
|
|
}
|
|
};
|
|
#endif
|
|
|
|
class CCollisionSolver : public IVP_Collision_Filter, public IVP_Anomaly_Manager
|
|
{
|
|
public:
|
|
CCollisionSolver( void ) : IVP_Anomaly_Manager(IVP_FALSE) { m_pSolver = NULL; }
|
|
void SetHandler( IPhysicsCollisionSolver *pSolver ) { m_pSolver = pSolver; }
|
|
|
|
// IVP_Collision_Filter
|
|
IVP_BOOL check_objects_for_collision_detection(IVP_Real_Object *ivp0, IVP_Real_Object *ivp1)
|
|
{
|
|
if ( m_pSolver )
|
|
{
|
|
CPhysicsObject *pObject0 = static_cast<CPhysicsObject *>(ivp0->client_data);
|
|
CPhysicsObject *pObject1 = static_cast<CPhysicsObject *>(ivp1->client_data);
|
|
if ( pObject0 && pObject1 )
|
|
{
|
|
if ( (pObject0->CallbackFlags() & CALLBACK_ENABLING_COLLISION) && (pObject1->CallbackFlags() & CALLBACK_MARKED_FOR_DELETE) )
|
|
return IVP_FALSE;
|
|
|
|
if ( (pObject1->CallbackFlags() & CALLBACK_ENABLING_COLLISION) && (pObject0->CallbackFlags() & CALLBACK_MARKED_FOR_DELETE) )
|
|
return IVP_FALSE;
|
|
|
|
if ( !m_pSolver->ShouldCollide( pObject0, pObject1, pObject0->GetGameData(), pObject1->GetGameData() ) )
|
|
return IVP_FALSE;
|
|
}
|
|
}
|
|
return IVP_TRUE;
|
|
}
|
|
void environment_will_be_deleted(IVP_Environment *) {}
|
|
|
|
// IVP_Anomaly_Manager
|
|
virtual void inter_penetration( IVP_Mindist *mindist,IVP_Real_Object *ivp0, IVP_Real_Object *ivp1, IVP_DOUBLE speedChange)
|
|
{
|
|
if ( m_pSolver )
|
|
{
|
|
// UNDONE: project current velocity onto rescue velocity instead
|
|
// This will cause escapes to be slow - which is probably a good
|
|
// thing. That's probably a better heuristic than only rescuing once
|
|
// per PSI!
|
|
CPhysicsObject *pObject0 = static_cast<CPhysicsObject *>(ivp0->client_data);
|
|
CPhysicsObject *pObject1 = static_cast<CPhysicsObject *>(ivp1->client_data);
|
|
if ( pObject0 && pObject1 )
|
|
{
|
|
if ( (pObject0->CallbackFlags() & CALLBACK_MARKED_FOR_DELETE) ||
|
|
(pObject1->CallbackFlags() & CALLBACK_MARKED_FOR_DELETE) )
|
|
return;
|
|
|
|
// moveable object pair?
|
|
if ( pObject0->IsMoveable() && pObject1->IsMoveable() )
|
|
{
|
|
// only push each pair apart once per PSI
|
|
if ( CheckObjPair( ivp0, ivp1 ) )
|
|
return;
|
|
}
|
|
IVP_Environment *env = ivp0->get_environment();
|
|
float deltaTime = env->get_delta_PSI_time();
|
|
|
|
if ( !m_pSolver->ShouldSolvePenetration( pObject0, pObject1, pObject0->GetGameData(), pObject1->GetGameData(), deltaTime ) )
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
IVP_Anomaly_Manager::inter_penetration( mindist, ivp0, ivp1, speedChange );
|
|
}
|
|
|
|
// return true if object should be temp. freezed
|
|
virtual IVP_BOOL max_collisions_exceeded_check_freezing(IVP_Anomaly_Limits *, IVP_Core *pCore)
|
|
{
|
|
if ( m_pSolver )
|
|
{
|
|
CPhysicsObject *pObject = static_cast<CPhysicsObject *>(pCore->objects.element_at(0)->client_data);
|
|
return m_pSolver->ShouldFreezeObject( pObject ) ? IVP_TRUE : IVP_FALSE;
|
|
}
|
|
return IVP_TRUE;
|
|
}
|
|
// return number of additional checks to do this psi
|
|
virtual int max_collision_checks_exceeded( int totalChecks )
|
|
{
|
|
if ( m_pSolver )
|
|
{
|
|
return m_pSolver->AdditionalCollisionChecksThisTick( totalChecks );
|
|
}
|
|
return 0;
|
|
}
|
|
void max_velocity_exceeded(IVP_Anomaly_Limits *al, IVP_Core *pCore, IVP_U_Float_Point *velocity_in_out)
|
|
{
|
|
CPhysicsObject *pObject = static_cast<CPhysicsObject *>(pCore->objects.element_at(0)->client_data);
|
|
if ( pObject->GetShadowController() != NULL )
|
|
return;
|
|
IVP_Anomaly_Manager::max_velocity_exceeded(al, pCore, velocity_in_out);
|
|
}
|
|
IVP_BOOL max_contacts_exceeded_check_freezing( IVP_Core **pCoreList, int coreCount )
|
|
{
|
|
CUtlVector<IPhysicsObject *> list;
|
|
list.EnsureCapacity(coreCount);
|
|
for ( int i = 0; i < coreCount; i++ )
|
|
{
|
|
IVP_Core *pCore = pCoreList[i];
|
|
CPhysicsObject *pObject = static_cast<CPhysicsObject *>(pCore->objects.element_at(0)->client_data);
|
|
list.AddToTail(pObject);
|
|
}
|
|
|
|
return m_pSolver->ShouldFreezeContacts( list.Base(), list.Count() ) ? IVP_TRUE : IVP_FALSE;
|
|
}
|
|
|
|
|
|
public:
|
|
void EventPSI( CPhysicsEnvironment * )
|
|
{
|
|
m_rescue.RemoveAll();
|
|
}
|
|
|
|
|
|
private:
|
|
struct realobjectpair_t
|
|
{
|
|
IVP_Real_Object *pObj0;
|
|
IVP_Real_Object *pObj1;
|
|
inline bool operator==( const realobjectpair_t &src ) const
|
|
{
|
|
return (pObj0 == src.pObj0) && (pObj1 == src.pObj1);
|
|
}
|
|
};
|
|
// basically each moveable object pair gets 1 rescue per PSI
|
|
// UNDONE: Add a counter to do more?
|
|
bool CheckObjPair( IVP_Real_Object *pObj0, IVP_Real_Object *pObj1 )
|
|
{
|
|
realobjectpair_t tmp;
|
|
tmp.pObj0 = pObj0 < pObj1 ? pObj0 : pObj1;
|
|
tmp.pObj1 = pObj0 > pObj1 ? pObj0 : pObj1;
|
|
|
|
if ( m_rescue.Find( tmp ) != m_rescue.InvalidIndex() )
|
|
return true;
|
|
m_rescue.AddToTail( tmp );
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
IPhysicsCollisionSolver *m_pSolver;
|
|
// UNDONE: Linear search? should be small, but switch to rb tree if this ever gets large
|
|
CUtlVector<realobjectpair_t> m_rescue;
|
|
#if IVP_ENABLE_VISUALIZER
|
|
public:
|
|
CCollisionVisualizer *pVisualizer;
|
|
#endif
|
|
};
|
|
|
|
|
|
|
|
class CPhysicsListenerConstraint : public IVP_Listener_Constraint
|
|
{
|
|
public:
|
|
CPhysicsListenerConstraint()
|
|
{
|
|
m_pCallback = NULL;
|
|
}
|
|
|
|
void SetHandler( IPhysicsConstraintEvent *pHandler )
|
|
{
|
|
m_pCallback = pHandler;
|
|
}
|
|
|
|
void event_constraint_broken( IVP_Constraint *pConstraint )
|
|
{
|
|
// IVP_Constraint is not allowed, something is broken
|
|
Assert(0);
|
|
}
|
|
|
|
void event_constraint_broken( hk_Breakable_Constraint *pConstraint )
|
|
{
|
|
if ( m_pCallback )
|
|
{
|
|
IPhysicsConstraint *pObj = GetClientDataForHkConstraint( pConstraint );
|
|
m_pCallback->ConstraintBroken( pObj );
|
|
}
|
|
}
|
|
void event_constraint_broken( IPhysicsConstraint *pConstraint )
|
|
{
|
|
if ( m_pCallback )
|
|
{
|
|
m_pCallback->ConstraintBroken(pConstraint);
|
|
}
|
|
}
|
|
private:
|
|
IPhysicsConstraintEvent *m_pCallback;
|
|
};
|
|
|
|
|
|
#define AIR_DENSITY 2
|
|
|
|
class CDragController : public IVP_Controller_Independent
|
|
{
|
|
public:
|
|
|
|
CDragController( void )
|
|
{
|
|
m_airDensity = AIR_DENSITY;
|
|
}
|
|
virtual ~CDragController( void ) {}
|
|
|
|
virtual void do_simulation_controller(IVP_Event_Sim *event,IVP_U_Vector<IVP_Core> *core_list)
|
|
{
|
|
int i;
|
|
for( i = core_list->len()-1; i >=0; i--)
|
|
{
|
|
IVP_Core *pCore = core_list->element_at(i);
|
|
|
|
IVP_Real_Object *pivp = pCore->objects.element_at(0);
|
|
CPhysicsObject *pPhys = static_cast<CPhysicsObject *>(pivp->client_data);
|
|
|
|
float dragForce = -0.5 * pPhys->GetDragInDirection( pCore->speed ) * m_airDensity * event->delta_time;
|
|
if ( dragForce < -1.0f )
|
|
dragForce = -1.0f;
|
|
if ( dragForce < 0 )
|
|
{
|
|
IVP_U_Float_Point dragVelocity;
|
|
dragVelocity.set_multiple( &pCore->speed, dragForce );
|
|
pCore->speed.add( &dragVelocity );
|
|
}
|
|
float angDragForce = -pPhys->GetAngularDragInDirection( pCore->rot_speed ) * m_airDensity * event->delta_time;
|
|
if ( angDragForce < -1.0f )
|
|
angDragForce = -1.0f;
|
|
if ( angDragForce < 0 )
|
|
{
|
|
IVP_U_Float_Point angDragVelocity;
|
|
angDragVelocity.set_multiple( &pCore->rot_speed, angDragForce );
|
|
pCore->rot_speed.add( &angDragVelocity );
|
|
}
|
|
}
|
|
}
|
|
virtual const char *get_controller_name() { return "vphysics:drag"; }
|
|
|
|
virtual IVP_CONTROLLER_PRIORITY get_controller_priority()
|
|
{
|
|
return IVP_CP_MOTION;
|
|
}
|
|
float GetAirDensity() const { return m_airDensity; }
|
|
void SetAirDensity( float density ) { m_airDensity = density; }
|
|
|
|
private:
|
|
float m_airDensity;
|
|
};
|
|
|
|
//
|
|
// Default implementation of the debug overlay interface so that we never return NULL from GetDebugOverlay.
|
|
//
|
|
class CVPhysicsDebugOverlay : public IVPhysicsDebugOverlay
|
|
{
|
|
public:
|
|
virtual void AddEntityTextOverlay(int ent_index, int line_offset, float duration, int r, int g, int b, int a, const char *format, ...) {}
|
|
virtual void AddBoxOverlay(const Vector& origin, const Vector& mins, const Vector& max, QAngle const& orientation, int r, int g, int b, int a, float duration) {}
|
|
virtual void AddTriangleOverlay(const Vector& p1, const Vector& p2, const Vector& p3, int r, int g, int b, int a, bool noDepthTest, float duration) {}
|
|
virtual void AddLineOverlay(const Vector& origin, const Vector& dest, int r, int g, int b,bool noDepthTest, float duration) {}
|
|
virtual void AddTextOverlay(const Vector& origin, float duration, const char *format, ...) {}
|
|
virtual void AddTextOverlay(const Vector& origin, int line_offset, float duration, const char *format, ...) {}
|
|
virtual void AddScreenTextOverlay(float flXPos, float flYPos,float flDuration, int r, int g, int b, int a, const char *text) {}
|
|
virtual void AddSweptBoxOverlay(const Vector& start, const Vector& end, const Vector& mins, const Vector& max, const QAngle & angles, int r, int g, int b, int a, float flDuration) {}
|
|
virtual void AddTextOverlayRGB(const Vector& origin, int line_offset, float duration, float r, float g, float b, float alpha, const char *format, ...) {}
|
|
};
|
|
|
|
static CVPhysicsDebugOverlay s_DefaultDebugOverlay;
|
|
|
|
|
|
CPhysicsEnvironment::CPhysicsEnvironment( void )
|
|
// assume that these lists will have at least one object
|
|
{
|
|
// set this to true to force the
|
|
m_deleteQuick = false;
|
|
m_queueDeleteObject = false;
|
|
m_inSimulation = false;
|
|
m_fixedTimestep = true; // try to simulate using fixed timesteps
|
|
m_enableConstraintNotify = false;
|
|
|
|
// build a default environment
|
|
IVP_Environment_Manager *env_manager;
|
|
env_manager = IVP_Environment_Manager::get_environment_manager();
|
|
|
|
IVP_Application_Environment appl_env;
|
|
m_pCollisionSolver = new CCollisionSolver;
|
|
appl_env.collision_filter = m_pCollisionSolver;
|
|
appl_env.material_manager = physprops->GetIVPManager();
|
|
appl_env.anomaly_manager = m_pCollisionSolver;
|
|
// UNDONE: This would save another 45K of RAM on xbox, test perf
|
|
// if ( IsXbox() )
|
|
// {
|
|
// appl_env.n_cache_object = 128;
|
|
// }
|
|
|
|
|
|
BEGIN_IVP_ALLOCATION();
|
|
m_pPhysEnv = env_manager->create_environment( &appl_env, "JAY", 0xBEEF );
|
|
END_IVP_ALLOCATION();
|
|
|
|
// UNDONE: Revisit brush/terrain/object shrinking and tune this number to something larger
|
|
// UNDONE: Expose this to callers, also via physcollision
|
|
m_pPhysEnv->set_global_collision_tolerance( ConvertDistanceToIVP( g_PhysicsUnits.globalCollisionTolerance - 1e-4f ) ); // just under 1/4 inch tolerance
|
|
m_pSleepEvents = new CSleepObjects;
|
|
|
|
m_pDeleteQueue = new CDeleteQueue;
|
|
|
|
BEGIN_IVP_ALLOCATION();
|
|
m_pPhysEnv->add_listener_object_global( m_pSleepEvents );
|
|
END_IVP_ALLOCATION();
|
|
|
|
m_pCollisionListener = new CPhysicsListenerCollision;
|
|
|
|
BEGIN_IVP_ALLOCATION();
|
|
m_pPhysEnv->add_listener_collision_global( m_pCollisionListener );
|
|
END_IVP_ALLOCATION();
|
|
|
|
m_pConstraintListener = new CPhysicsListenerConstraint;
|
|
|
|
BEGIN_IVP_ALLOCATION();
|
|
m_pPhysEnv->add_listener_constraint_global( m_pConstraintListener );
|
|
END_IVP_ALLOCATION();
|
|
|
|
m_pDragController = new CDragController;
|
|
|
|
physics_performanceparams_t perf;
|
|
perf.Defaults();
|
|
SetPerformanceSettings( &perf );
|
|
m_pPhysEnv->client_data = (void *)this;
|
|
m_lastObjectThisTick = 0;
|
|
}
|
|
|
|
CPhysicsEnvironment::~CPhysicsEnvironment( void )
|
|
{
|
|
// no callbacks during shutdown
|
|
SetCollisionSolver( NULL );
|
|
m_pPhysEnv->remove_listener_object_global( m_pSleepEvents );
|
|
|
|
// don't bother waking up other objects as we clear them out
|
|
SetQuickDelete( true );
|
|
|
|
// delete/remove the listeners
|
|
m_pPhysEnv->remove_listener_collision_global( m_pCollisionListener );
|
|
delete m_pCollisionListener;
|
|
m_pPhysEnv->remove_listener_constraint_global( m_pConstraintListener );
|
|
delete m_pConstraintListener;
|
|
|
|
// Clean out the list of physics objects
|
|
for ( int i = m_objects.Count()-1; i >= 0; --i )
|
|
{
|
|
CPhysicsObject *pObject = static_cast<CPhysicsObject *>(m_objects[i]);
|
|
PhantomRemove( pObject );
|
|
delete pObject;
|
|
}
|
|
|
|
m_objects.RemoveAll();
|
|
ClearDeadObjects();
|
|
|
|
// Clean out the list of fluids
|
|
m_fluids.PurgeAndDeleteElements();
|
|
|
|
delete m_pSleepEvents;
|
|
delete m_pDragController;
|
|
delete m_pPhysEnv;
|
|
delete m_pDeleteQueue;
|
|
|
|
// must be deleted after the environment (calls back in destructor)
|
|
delete m_pCollisionSolver;
|
|
}
|
|
|
|
IPhysicsCollisionEvent *CPhysicsEnvironment::GetCollisionEventHandler()
|
|
{
|
|
return m_pCollisionListener->GetHandler();
|
|
}
|
|
|
|
void CPhysicsEnvironment::NotifyConstraintDisabled( IPhysicsConstraint *pConstraint )
|
|
{
|
|
if ( m_enableConstraintNotify )
|
|
{
|
|
m_pConstraintListener->event_constraint_broken( pConstraint );
|
|
}
|
|
}
|
|
|
|
void CPhysicsEnvironment::DebugCheckContacts(void)
|
|
{
|
|
if ( m_pSleepEvents )
|
|
{
|
|
m_pSleepEvents->DebugCheckContacts( m_pPhysEnv );
|
|
}
|
|
}
|
|
|
|
void CPhysicsEnvironment::SetDebugOverlay( CreateInterfaceFn debugOverlayFactory )
|
|
{
|
|
m_pDebugOverlay = NULL;
|
|
if (debugOverlayFactory)
|
|
{
|
|
m_pDebugOverlay = ( IVPhysicsDebugOverlay * )debugOverlayFactory( VPHYSICS_DEBUG_OVERLAY_INTERFACE_VERSION, NULL );
|
|
}
|
|
|
|
if (!m_pDebugOverlay)
|
|
{
|
|
m_pDebugOverlay = &s_DefaultDebugOverlay;
|
|
}
|
|
|
|
#if IVP_ENABLE_VISUALIZER
|
|
m_pCollisionSolver->pVisualizer = new CCollisionVisualizer( m_pDebugOverlay );
|
|
INSTALL_SHORTRANGE_CALLBACK(m_pCollisionSolver->pVisualizer);
|
|
INSTALL_LONGRANGE_CALLBACK(m_pCollisionSolver->pVisualizer);
|
|
|
|
#endif
|
|
}
|
|
|
|
|
|
IVPhysicsDebugOverlay *CPhysicsEnvironment::GetDebugOverlay( void )
|
|
{
|
|
return m_pDebugOverlay;
|
|
}
|
|
|
|
|
|
void CPhysicsEnvironment::SetGravity( const Vector& gravityVector )
|
|
{
|
|
IVP_U_Point gravity;
|
|
|
|
ConvertPositionToIVP( gravityVector, gravity );
|
|
m_pPhysEnv->set_gravity( &gravity );
|
|
// BUGBUG: global collision tolerance has a constant that depends on gravity.
|
|
m_pPhysEnv->set_global_collision_tolerance( m_pPhysEnv->get_global_collision_tolerance(), gravity.real_length() );
|
|
DevMsg(1,"Set Gravity %.1f (%.3f tolerance)\n", gravityVector.Length(), IVP2HL(m_pPhysEnv->get_global_collision_tolerance()) );
|
|
}
|
|
|
|
|
|
void CPhysicsEnvironment::GetGravity( Vector *pGravityVector ) const
|
|
{
|
|
const IVP_U_Point *gravity = m_pPhysEnv->get_gravity();
|
|
|
|
ConvertPositionToHL( *gravity, *pGravityVector );
|
|
}
|
|
|
|
|
|
IPhysicsObject *CPhysicsEnvironment::CreatePolyObject( const CPhysCollide *pCollisionModel, int materialIndex, const Vector& position, const QAngle& angles, objectparams_t *pParams )
|
|
{
|
|
IPhysicsObject *pObject = ::CreatePhysicsObject( this, pCollisionModel, materialIndex, position, angles, pParams, false );
|
|
if ( pObject )
|
|
{
|
|
m_objects.AddToTail( pObject );
|
|
}
|
|
return pObject;
|
|
}
|
|
|
|
IPhysicsObject *CPhysicsEnvironment::CreatePolyObjectStatic( const CPhysCollide *pCollisionModel, int materialIndex, const Vector& position, const QAngle& angles, objectparams_t *pParams )
|
|
{
|
|
IPhysicsObject *pObject = ::CreatePhysicsObject( this, pCollisionModel, materialIndex, position, angles, pParams, true );
|
|
if ( pObject )
|
|
{
|
|
m_objects.AddToTail( pObject );
|
|
}
|
|
return pObject;
|
|
}
|
|
|
|
unsigned int CPhysicsEnvironment::GetObjectSerializeSize( IPhysicsObject *pObject ) const
|
|
{
|
|
return sizeof(vphysics_save_cphysicsobject_t);
|
|
}
|
|
|
|
void CPhysicsEnvironment::SerializeObjectToBuffer( IPhysicsObject *pObject, unsigned char *pBuffer, unsigned int bufferSize )
|
|
{
|
|
CPhysicsObject *pPhysics = static_cast<CPhysicsObject *>(pObject);
|
|
if ( bufferSize >= sizeof(vphysics_save_cphysicsobject_t))
|
|
{
|
|
vphysics_save_cphysicsobject_t *pTemplate = reinterpret_cast<vphysics_save_cphysicsobject_t *>(pBuffer);
|
|
pPhysics->WriteToTemplate( *pTemplate );
|
|
}
|
|
}
|
|
|
|
IPhysicsObject *CPhysicsEnvironment::UnserializeObjectFromBuffer( void *pGameData, unsigned char *pBuffer, unsigned int bufferSize, bool enableCollisions )
|
|
{
|
|
IPhysicsObject *pObject = ::CreateObjectFromBuffer( this, pGameData, pBuffer, bufferSize, enableCollisions );
|
|
if ( pObject )
|
|
{
|
|
m_objects.AddToTail( pObject );
|
|
}
|
|
return pObject;
|
|
}
|
|
|
|
const IPhysicsObject **CPhysicsEnvironment::GetObjectList( int *pOutputObjectCount ) const
|
|
{
|
|
int iCount = m_objects.Count();
|
|
if( pOutputObjectCount )
|
|
*pOutputObjectCount = iCount;
|
|
|
|
if( iCount )
|
|
return (const IPhysicsObject **)m_objects.Base();
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
extern void ControlPhysicsShadowControllerAttachment_Silent( IPhysicsShadowController *pController, IVP_Real_Object *pivp, bool bAttach );
|
|
extern void ControlPhysicsPlayerControllerAttachment_Silent( IPhysicsPlayerController *pController, IVP_Real_Object *pivp, bool bAttach );
|
|
|
|
bool CPhysicsEnvironment::TransferObject( IPhysicsObject *pObject, IPhysicsEnvironment *pDestinationEnvironment )
|
|
{
|
|
int iIndex = m_objects.Find( pObject );
|
|
if( iIndex == -1 || (pObject->GetCallbackFlags() & CALLBACK_MARKED_FOR_DELETE ) )
|
|
return false;
|
|
|
|
CPhysicsObject *pPhysics = static_cast<CPhysicsObject *>(pObject);
|
|
//pPhysics->Wake();
|
|
//pPhysics->NotifyWake();
|
|
|
|
void *pGameData = pObject->GetGameData();
|
|
|
|
//Find any controllers attached to this object
|
|
IPhysicsShadowController *pController = pObject->GetShadowController();
|
|
IPhysicsPlayerController *pPlayerController = NULL;
|
|
|
|
if( (pObject->GetCallbackFlags() & CALLBACK_IS_PLAYER_CONTROLLER) != 0 )
|
|
{
|
|
pPlayerController = FindPlayerController( pObject );
|
|
}
|
|
|
|
|
|
|
|
|
|
//temporarily (and silently) detach any physics controllers we found because destroying the object would destroy them
|
|
if( pController )
|
|
{
|
|
//detach the controller from the object
|
|
((CPhysicsObject *)pObject)->m_pShadow = NULL;
|
|
|
|
IVP_Real_Object *pivp = ((CPhysicsObject *)pObject)->GetObject();
|
|
ControlPhysicsShadowControllerAttachment_Silent( pController, pivp, false );
|
|
}
|
|
else if( pPlayerController )
|
|
{
|
|
RemovePlayerController( pPlayerController );
|
|
pObject->SetCallbackFlags( pObject->GetCallbackFlags() & ~CALLBACK_IS_PLAYER_CONTROLLER );
|
|
|
|
IVP_Real_Object *pivp = ((CPhysicsObject *)pObject)->GetObject();
|
|
ControlPhysicsPlayerControllerAttachment_Silent( pPlayerController, pivp, false );
|
|
}
|
|
|
|
|
|
//templatize the object
|
|
vphysics_save_cphysicsobject_t objectTemplate;
|
|
memset( &objectTemplate, 0, sizeof( vphysics_save_cphysicsobject_t ) );
|
|
pPhysics->WriteToTemplate( objectTemplate );
|
|
|
|
//these should be detached already
|
|
Assert( objectTemplate.pShadow == NULL );
|
|
Assert( objectTemplate.hasShadowController == false );
|
|
|
|
//destroy the existing version of the object
|
|
m_objects.FastRemove( iIndex );
|
|
pPhysics->ForceSilentDelete();
|
|
m_pSleepEvents->DeleteObject( pPhysics );
|
|
pPhysics->CPhysicsObject::~CPhysicsObject();
|
|
|
|
//now recreate in place in the destination environment
|
|
CPhysicsEnvironment *pDest = static_cast<CPhysicsEnvironment *>(pDestinationEnvironment);
|
|
CreateObjectFromBuffer_UseExistingMemory( pDest, pGameData, (unsigned char *)&objectTemplate, sizeof(objectTemplate), pPhysics );
|
|
pDest->m_objects.AddToTail( pObject );
|
|
|
|
//even if this is going to sleep in a second, put it active right away to fix some object hitching problems
|
|
pPhysics->Wake();
|
|
pPhysics->NotifyWake();
|
|
/*int iActiveIndex = pDest->m_pSleepEvents->m_activeObjects.AddToTail( pPhysics );
|
|
pPhysics->SetActiveIndex( iActiveIndex );*/
|
|
|
|
pDest->m_pPhysEnv->force_psi_on_next_simulation(); //avoids an object pause
|
|
|
|
if( pController )
|
|
{
|
|
//re-attach the controller to the new object
|
|
((CPhysicsObject *)pObject)->m_pShadow = pController;
|
|
|
|
IVP_Real_Object *pivp = ((CPhysicsObject *)pObject)->GetObject();
|
|
ControlPhysicsShadowControllerAttachment_Silent( pController, pivp, true );
|
|
}
|
|
else if( pPlayerController )
|
|
{
|
|
IVP_Real_Object *pivp = ((CPhysicsObject *)pObject)->GetObject();
|
|
pObject->SetCallbackFlags( pObject->GetCallbackFlags() | CALLBACK_IS_PLAYER_CONTROLLER );
|
|
ControlPhysicsPlayerControllerAttachment_Silent( pPlayerController, pivp, true );
|
|
|
|
pDest->AddPlayerController( pPlayerController );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
IPhysicsSpring *CPhysicsEnvironment::CreateSpring( IPhysicsObject *pObjectStart, IPhysicsObject *pObjectEnd, springparams_t *pParams )
|
|
{
|
|
return ::CreateSpring( m_pPhysEnv, static_cast<CPhysicsObject *>(pObjectStart), static_cast<CPhysicsObject *>(pObjectEnd), pParams );
|
|
}
|
|
|
|
IPhysicsFluidController *CPhysicsEnvironment::CreateFluidController( IPhysicsObject *pFluidObject, fluidparams_t *pParams )
|
|
{
|
|
CPhysicsFluidController *pFluid = ::CreateFluidController( m_pPhysEnv, static_cast<CPhysicsObject *>(pFluidObject), pParams );
|
|
m_fluids.AddToTail( pFluid );
|
|
return pFluid;
|
|
}
|
|
|
|
IPhysicsConstraint *CPhysicsEnvironment::CreateRagdollConstraint( IPhysicsObject *pReferenceObject, IPhysicsObject *pAttachedObject, IPhysicsConstraintGroup *pGroup, const constraint_ragdollparams_t &ragdoll )
|
|
{
|
|
return ::CreateRagdollConstraint( m_pPhysEnv, (CPhysicsObject *)pReferenceObject, (CPhysicsObject *)pAttachedObject, pGroup, ragdoll );
|
|
}
|
|
|
|
IPhysicsConstraint *CPhysicsEnvironment::CreateHingeConstraint( IPhysicsObject *pReferenceObject, IPhysicsObject *pAttachedObject, IPhysicsConstraintGroup *pGroup, const constraint_hingeparams_t &hinge )
|
|
{
|
|
constraint_limitedhingeparams_t limitedhinge(hinge);
|
|
return ::CreateHingeConstraint( m_pPhysEnv, (CPhysicsObject *)pReferenceObject, (CPhysicsObject *)pAttachedObject, pGroup, limitedhinge );
|
|
}
|
|
|
|
IPhysicsConstraint *CPhysicsEnvironment::CreateLimitedHingeConstraint( IPhysicsObject *pReferenceObject, IPhysicsObject *pAttachedObject, IPhysicsConstraintGroup *pGroup, const constraint_limitedhingeparams_t &hinge )
|
|
{
|
|
return ::CreateHingeConstraint( m_pPhysEnv, (CPhysicsObject *)pReferenceObject, (CPhysicsObject *)pAttachedObject, pGroup, hinge );
|
|
}
|
|
|
|
IPhysicsConstraint *CPhysicsEnvironment::CreateFixedConstraint( IPhysicsObject *pReferenceObject, IPhysicsObject *pAttachedObject, IPhysicsConstraintGroup *pGroup, const constraint_fixedparams_t &fixed )
|
|
{
|
|
return ::CreateFixedConstraint( m_pPhysEnv, (CPhysicsObject *)pReferenceObject, (CPhysicsObject *)pAttachedObject, pGroup, fixed );
|
|
}
|
|
|
|
IPhysicsConstraint *CPhysicsEnvironment::CreateSlidingConstraint( IPhysicsObject *pReferenceObject, IPhysicsObject *pAttachedObject, IPhysicsConstraintGroup *pGroup, const constraint_slidingparams_t &sliding )
|
|
{
|
|
return ::CreateSlidingConstraint( m_pPhysEnv, (CPhysicsObject *)pReferenceObject, (CPhysicsObject *)pAttachedObject, pGroup, sliding );
|
|
}
|
|
|
|
IPhysicsConstraint *CPhysicsEnvironment::CreateBallsocketConstraint( IPhysicsObject *pReferenceObject, IPhysicsObject *pAttachedObject, IPhysicsConstraintGroup *pGroup, const constraint_ballsocketparams_t &ballsocket )
|
|
{
|
|
return ::CreateBallsocketConstraint( m_pPhysEnv, (CPhysicsObject *)pReferenceObject, (CPhysicsObject *)pAttachedObject, pGroup, ballsocket );
|
|
}
|
|
|
|
IPhysicsConstraint *CPhysicsEnvironment::CreatePulleyConstraint( IPhysicsObject *pReferenceObject, IPhysicsObject *pAttachedObject, IPhysicsConstraintGroup *pGroup, const constraint_pulleyparams_t &pulley )
|
|
{
|
|
return ::CreatePulleyConstraint( m_pPhysEnv, (CPhysicsObject *)pReferenceObject, (CPhysicsObject *)pAttachedObject, pGroup, pulley );
|
|
}
|
|
|
|
IPhysicsConstraint *CPhysicsEnvironment::CreateLengthConstraint( IPhysicsObject *pReferenceObject, IPhysicsObject *pAttachedObject, IPhysicsConstraintGroup *pGroup, const constraint_lengthparams_t &length )
|
|
{
|
|
return ::CreateLengthConstraint( m_pPhysEnv, (CPhysicsObject *)pReferenceObject, (CPhysicsObject *)pAttachedObject, pGroup, length );
|
|
}
|
|
|
|
IPhysicsConstraintGroup *CPhysicsEnvironment::CreateConstraintGroup( const constraint_groupparams_t &group )
|
|
{
|
|
return CreatePhysicsConstraintGroup( m_pPhysEnv, group );
|
|
}
|
|
|
|
void CPhysicsEnvironment::Simulate( float deltaTime )
|
|
{
|
|
LOCAL_THREAD_LOCK();
|
|
|
|
if ( !m_pPhysEnv )
|
|
return;
|
|
|
|
ClearDeadObjects();
|
|
#if DEBUG_CHECK_CONTATCTS_AUTO
|
|
m_pSleepEvents->DebugCheckContacts( m_pPhysEnv );
|
|
#endif
|
|
|
|
// save this to catch objects deleted without being simulated
|
|
m_lastObjectThisTick = m_objects.Count()-1;
|
|
|
|
// stop updating objects that went to sleep during the previous frame.
|
|
m_pSleepEvents->UpdateSleepObjects();
|
|
|
|
// Trap interrupts and clock changes
|
|
// don't simulate less than .1 ms
|
|
if ( deltaTime <= 1.0 && deltaTime > 0.0001 )
|
|
{
|
|
if ( deltaTime > 0.1 )
|
|
{
|
|
deltaTime = 0.1f;
|
|
}
|
|
|
|
m_pCollisionSolver->EventPSI( this );
|
|
m_pCollisionListener->EventPSI( this );
|
|
|
|
m_inSimulation = true;
|
|
BEGIN_IVP_ALLOCATION();
|
|
if ( !m_fixedTimestep || deltaTime != m_pPhysEnv->get_delta_PSI_time() )
|
|
{
|
|
m_fixedTimestep = false;
|
|
m_pPhysEnv->simulate_dtime( deltaTime );
|
|
}
|
|
else
|
|
{
|
|
m_pPhysEnv->simulate_time_step();
|
|
}
|
|
END_IVP_ALLOCATION();
|
|
m_inSimulation = false;
|
|
}
|
|
|
|
// If the queue is disabled, it's only used during simulation.
|
|
// Flush it as soon as possible (which is now)
|
|
if ( !m_queueDeleteObject )
|
|
{
|
|
ClearDeadObjects();
|
|
}
|
|
|
|
if ( m_pCollisionListener->GetHandler() )
|
|
{
|
|
m_pSleepEvents->ProcessActiveObjects( m_pPhysEnv, m_pCollisionListener->GetHandler() );
|
|
}
|
|
VISUALIZE_COLLISIONS();
|
|
VirtualMeshPSI();
|
|
GetNextFrameTime();
|
|
}
|
|
|
|
void CPhysicsEnvironment::ResetSimulationClock()
|
|
{
|
|
// UNDONE: You'd think that all of this would make the system deterministic, but
|
|
// it doesn't.
|
|
extern void SeedRandomGenerators();
|
|
|
|
m_pPhysEnv->reset_time();
|
|
m_pPhysEnv->get_time_manager()->env_set_current_time( m_pPhysEnv, IVP_Time(0) );
|
|
m_pPhysEnv->reset_time();
|
|
m_fixedTimestep = true;
|
|
SeedRandomGenerators();
|
|
}
|
|
|
|
float CPhysicsEnvironment::GetSimulationTimestep( void ) const
|
|
{
|
|
return m_pPhysEnv->get_delta_PSI_time();
|
|
}
|
|
|
|
void CPhysicsEnvironment::SetSimulationTimestep( float timestep )
|
|
{
|
|
m_pPhysEnv->set_delta_PSI_time( timestep );
|
|
}
|
|
|
|
float CPhysicsEnvironment::GetSimulationTime( void ) const
|
|
{
|
|
return (float)m_pPhysEnv->get_current_time().get_time();
|
|
}
|
|
|
|
float CPhysicsEnvironment::GetNextFrameTime( void ) const
|
|
{
|
|
return (float)m_pPhysEnv->get_next_PSI_time().get_time();
|
|
}
|
|
|
|
|
|
// true if currently running the simulator (i.e. in a callback during physenv->Simulate())
|
|
bool CPhysicsEnvironment::IsInSimulation( void ) const
|
|
{
|
|
return m_inSimulation;
|
|
}
|
|
|
|
void CPhysicsEnvironment::DestroyObject( IPhysicsObject *pObject )
|
|
{
|
|
if ( !pObject )
|
|
{
|
|
DevMsg("Deleted NULL vphysics object\n");
|
|
return;
|
|
}
|
|
|
|
// search from the end because we usually delete the most recent objects during run time
|
|
int index = -1;
|
|
for ( int i = m_objects.Count(); --i >= 0; )
|
|
{
|
|
if ( m_objects[i] == pObject )
|
|
{
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( index != -1 )
|
|
{
|
|
m_objects.FastRemove( index );
|
|
}
|
|
else
|
|
{
|
|
DevMsg(1,"error deleting physics object\n");
|
|
CPhysicsObject *pPhysics = static_cast<CPhysicsObject *>(pObject);
|
|
if ( pPhysics->GetCallbackFlags() & CALLBACK_MARKED_FOR_DELETE )
|
|
{
|
|
// deleted twice
|
|
Assert(0);
|
|
return;
|
|
}
|
|
// bad ptr?
|
|
Assert(0);
|
|
return;
|
|
}
|
|
|
|
CPhysicsObject *pPhysics = static_cast<CPhysicsObject *>(pObject);
|
|
// add this flag so we can optimize some cases
|
|
pPhysics->AddCallbackFlags( CALLBACK_MARKED_FOR_DELETE );
|
|
|
|
// was created/destroyed without simulating. No need to wake the neighbors!
|
|
if ( index > m_lastObjectThisTick )
|
|
{
|
|
pPhysics->ForceSilentDelete();
|
|
}
|
|
|
|
if ( m_inSimulation || m_queueDeleteObject )
|
|
{
|
|
// don't delete while simulating
|
|
m_deadObjects.AddToTail( pObject );
|
|
}
|
|
else
|
|
{
|
|
m_pSleepEvents->DeleteObject( pPhysics );
|
|
delete pObject;
|
|
}
|
|
}
|
|
|
|
void CPhysicsEnvironment::DestroySpring( IPhysicsSpring *pSpring )
|
|
{
|
|
delete pSpring;
|
|
}
|
|
void CPhysicsEnvironment::DestroyFluidController( IPhysicsFluidController *pFluid )
|
|
{
|
|
m_fluids.FindAndRemove( (CPhysicsFluidController *)pFluid );
|
|
delete pFluid;
|
|
}
|
|
|
|
|
|
void CPhysicsEnvironment::DestroyConstraint( IPhysicsConstraint *pConstraint )
|
|
{
|
|
if ( !m_deleteQuick && pConstraint )
|
|
{
|
|
IPhysicsObject *pObj0 = pConstraint->GetReferenceObject();
|
|
if ( pObj0 )
|
|
{
|
|
pObj0->Wake();
|
|
}
|
|
|
|
IPhysicsObject *pObj1 = pConstraint->GetAttachedObject();
|
|
if ( pObj1 )
|
|
{
|
|
pObj1->Wake();
|
|
}
|
|
}
|
|
if ( m_inSimulation )
|
|
{
|
|
pConstraint->Deactivate();
|
|
m_pDeleteQueue->QueueForDelete( pConstraint );
|
|
}
|
|
else
|
|
{
|
|
delete pConstraint;
|
|
}
|
|
}
|
|
|
|
void CPhysicsEnvironment::DestroyConstraintGroup( IPhysicsConstraintGroup *pGroup )
|
|
{
|
|
delete pGroup;
|
|
}
|
|
|
|
void CPhysicsEnvironment::TraceBox( trace_t *ptr, const Vector &mins, const Vector &maxs, const Vector &start, const Vector &end )
|
|
{
|
|
// UNDONE: Need this?
|
|
}
|
|
|
|
void CPhysicsEnvironment::SetCollisionSolver( IPhysicsCollisionSolver *pSolver )
|
|
{
|
|
m_pCollisionSolver->SetHandler( pSolver );
|
|
}
|
|
|
|
|
|
void CPhysicsEnvironment::ClearDeadObjects( void )
|
|
{
|
|
for ( int i = 0; i < m_deadObjects.Count(); i++ )
|
|
{
|
|
CPhysicsObject *pObject = (CPhysicsObject *)m_deadObjects.Element(i);
|
|
|
|
m_pSleepEvents->DeleteObject( pObject );
|
|
delete pObject;
|
|
}
|
|
m_deadObjects.Purge();
|
|
m_pDeleteQueue->DeleteAll();
|
|
}
|
|
|
|
void CPhysicsEnvironment::AddPlayerController( IPhysicsPlayerController *pController )
|
|
{
|
|
if ( m_playerControllers.Find(pController) != -1 )
|
|
{
|
|
Assert(0);
|
|
return;
|
|
}
|
|
m_playerControllers.AddToTail( pController );
|
|
}
|
|
|
|
void CPhysicsEnvironment::RemovePlayerController( IPhysicsPlayerController *pController )
|
|
{
|
|
m_playerControllers.FindAndRemove( pController );
|
|
}
|
|
|
|
IPhysicsPlayerController *CPhysicsEnvironment::FindPlayerController( IPhysicsObject *pPhysicsObject )
|
|
{
|
|
for ( int i = m_playerControllers.Count()-1; i >= 0; --i )
|
|
{
|
|
if ( m_playerControllers[i]->GetObject() == pPhysicsObject )
|
|
return m_playerControllers[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void CPhysicsEnvironment::SetCollisionEventHandler( IPhysicsCollisionEvent *pCollisionEvents )
|
|
{
|
|
m_pCollisionListener->SetHandler( pCollisionEvents );
|
|
}
|
|
|
|
|
|
void CPhysicsEnvironment::SetObjectEventHandler( IPhysicsObjectEvent *pObjectEvents )
|
|
{
|
|
m_pSleepEvents->SetHandler( pObjectEvents );
|
|
}
|
|
|
|
void CPhysicsEnvironment::SetConstraintEventHandler( IPhysicsConstraintEvent *pConstraintEvents )
|
|
{
|
|
m_pConstraintListener->SetHandler( pConstraintEvents );
|
|
}
|
|
|
|
|
|
IPhysicsShadowController *CPhysicsEnvironment::CreateShadowController( IPhysicsObject *pObject, bool allowTranslation, bool allowRotation )
|
|
{
|
|
return ::CreateShadowController( static_cast<CPhysicsObject*>(pObject), allowTranslation, allowRotation );
|
|
}
|
|
|
|
void CPhysicsEnvironment::DestroyShadowController( IPhysicsShadowController *pController )
|
|
{
|
|
delete pController;
|
|
}
|
|
|
|
IPhysicsPlayerController *CPhysicsEnvironment::CreatePlayerController( IPhysicsObject *pObject )
|
|
{
|
|
IPhysicsPlayerController *pController = ::CreatePlayerController( static_cast<CPhysicsObject*>(pObject) );
|
|
AddPlayerController( pController );
|
|
return pController;
|
|
}
|
|
|
|
void CPhysicsEnvironment::DestroyPlayerController( IPhysicsPlayerController *pController )
|
|
{
|
|
RemovePlayerController( pController );
|
|
::DestroyPlayerController( pController );
|
|
}
|
|
|
|
IPhysicsMotionController *CPhysicsEnvironment::CreateMotionController( IMotionEvent *pHandler )
|
|
{
|
|
return ::CreateMotionController( this, pHandler );
|
|
}
|
|
|
|
void CPhysicsEnvironment::DestroyMotionController( IPhysicsMotionController *pController )
|
|
{
|
|
delete pController;
|
|
}
|
|
|
|
IPhysicsVehicleController *CPhysicsEnvironment::CreateVehicleController( IPhysicsObject *pVehicleBodyObject, const vehicleparams_t ¶ms, unsigned int nVehicleType, IPhysicsGameTrace *pGameTrace )
|
|
{
|
|
return ::CreateVehicleController( this, static_cast<CPhysicsObject*>(pVehicleBodyObject), params, nVehicleType, pGameTrace );
|
|
}
|
|
|
|
void CPhysicsEnvironment::DestroyVehicleController( IPhysicsVehicleController *pController )
|
|
{
|
|
delete pController;
|
|
}
|
|
|
|
int CPhysicsEnvironment::GetActiveObjectCount( void ) const
|
|
{
|
|
return m_pSleepEvents->GetActiveObjectCount();
|
|
}
|
|
|
|
|
|
void CPhysicsEnvironment::GetActiveObjects( IPhysicsObject **pOutputObjectList ) const
|
|
{
|
|
m_pSleepEvents->GetActiveObjects( pOutputObjectList );
|
|
}
|
|
|
|
void CPhysicsEnvironment::SetAirDensity( float density )
|
|
{
|
|
CDragController *pDrag = ((CDragController *)m_pDragController);
|
|
if ( pDrag )
|
|
{
|
|
pDrag->SetAirDensity( density );
|
|
}
|
|
}
|
|
|
|
float CPhysicsEnvironment::GetAirDensity( void ) const
|
|
{
|
|
const CDragController *pDrag = ((CDragController *)m_pDragController);
|
|
if ( pDrag )
|
|
{
|
|
return pDrag->GetAirDensity();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void CPhysicsEnvironment::CleanupDeleteList()
|
|
{
|
|
ClearDeadObjects();
|
|
}
|
|
|
|
bool CPhysicsEnvironment::IsCollisionModelUsed( CPhysCollide *pCollide ) const
|
|
{
|
|
int i;
|
|
|
|
for ( i = m_deadObjects.Count()-1; i >= 0; --i )
|
|
{
|
|
if ( m_deadObjects[i]->GetCollide() == pCollide )
|
|
return true;
|
|
}
|
|
|
|
for ( i = m_objects.Count()-1; i >= 0; --i )
|
|
{
|
|
if ( m_objects[i]->GetCollide() == pCollide )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
// manage phantoms
|
|
void CPhysicsEnvironment::PhantomAdd( CPhysicsObject *pObject )
|
|
{
|
|
IVP_Controller_Phantom *pPhantom = pObject->GetObject()->get_controller_phantom();
|
|
if ( pPhantom )
|
|
{
|
|
pPhantom->add_listener_phantom( m_pCollisionListener );
|
|
}
|
|
}
|
|
|
|
void CPhysicsEnvironment::PhantomRemove( CPhysicsObject *pObject )
|
|
{
|
|
IVP_Controller_Phantom *pPhantom = pObject->GetObject()->get_controller_phantom();
|
|
if ( pPhantom )
|
|
{
|
|
pPhantom->remove_listener_phantom( m_pCollisionListener );
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
IPhysicsObject *CPhysicsEnvironment::CreateSphereObject( float radius, int materialIndex, const Vector& position, const QAngle& angles, objectparams_t *pParams, bool isStatic )
|
|
{
|
|
IPhysicsObject *pObject = ::CreatePhysicsSphere( this, radius, materialIndex, position, angles, pParams, isStatic );
|
|
m_objects.AddToTail( pObject );
|
|
return pObject;
|
|
}
|
|
|
|
void CPhysicsEnvironment::TraceRay( const Ray_t &ray, unsigned int fMask, IPhysicsTraceFilter *pTraceFilter, trace_t *pTrace )
|
|
{
|
|
}
|
|
|
|
void CPhysicsEnvironment::SweepCollideable( const CPhysCollide *pCollide, const Vector &vecAbsStart, const Vector &vecAbsEnd,
|
|
const QAngle &vecAngles, unsigned int fMask, IPhysicsTraceFilter *pTraceFilter, trace_t *pTrace )
|
|
{
|
|
}
|
|
|
|
|
|
void CPhysicsEnvironment::GetPerformanceSettings( physics_performanceparams_t *pOutput ) const
|
|
{
|
|
if ( !pOutput )
|
|
return;
|
|
|
|
IVP_Anomaly_Limits *limits = m_pPhysEnv->get_anomaly_limits();
|
|
if ( limits )
|
|
{
|
|
// UNDONE: Expose these values for tuning
|
|
pOutput->maxVelocity = ConvertDistanceToHL( limits->max_velocity );
|
|
pOutput->maxAngularVelocity = ConvertAngleToHL(limits->max_angular_velocity_per_psi) * m_pPhysEnv->get_inv_delta_PSI_time();
|
|
pOutput->maxCollisionsPerObjectPerTimestep = limits->max_collisions_per_psi;
|
|
pOutput->maxCollisionChecksPerTimestep = limits->max_collision_checks_per_psi;
|
|
pOutput->minFrictionMass = limits->min_friction_mass;
|
|
pOutput->maxFrictionMass = limits->max_friction_mass;
|
|
}
|
|
|
|
IVP_Range_Manager *range = m_pPhysEnv->get_range_manager();
|
|
if ( range )
|
|
{
|
|
pOutput->lookAheadTimeObjectsVsWorld = range->look_ahead_time_world;
|
|
pOutput->lookAheadTimeObjectsVsObject = range->look_ahead_time_intra;
|
|
}
|
|
}
|
|
|
|
void CPhysicsEnvironment::SetPerformanceSettings( const physics_performanceparams_t *pSettings )
|
|
{
|
|
if ( !pSettings )
|
|
return;
|
|
|
|
IVP_Anomaly_Limits *limits = m_pPhysEnv->get_anomaly_limits();
|
|
if ( limits )
|
|
{
|
|
// UNDONE: Expose these values for tuning
|
|
limits->max_velocity = ConvertDistanceToIVP( pSettings->maxVelocity );
|
|
limits->max_collisions_per_psi = pSettings->maxCollisionsPerObjectPerTimestep;
|
|
limits->max_collision_checks_per_psi = pSettings->maxCollisionChecksPerTimestep;
|
|
limits->max_angular_velocity_per_psi = ConvertAngleToIVP(pSettings->maxAngularVelocity) * m_pPhysEnv->get_delta_PSI_time();
|
|
limits->min_friction_mass = clamp(pSettings->minFrictionMass, 1.0f, VPHYSICS_MAX_MASS );
|
|
limits->max_friction_mass = clamp(pSettings->maxFrictionMass, 1.0f, VPHYSICS_MAX_MASS );
|
|
}
|
|
|
|
IVP_Range_Manager *range = m_pPhysEnv->get_range_manager();
|
|
if ( range )
|
|
{
|
|
range->look_ahead_time_world = pSettings->lookAheadTimeObjectsVsWorld;
|
|
range->look_ahead_time_intra = pSettings->lookAheadTimeObjectsVsObject;
|
|
}
|
|
}
|
|
|
|
|
|
// perf/cost statistics
|
|
void CPhysicsEnvironment::ReadStats( physics_stats_t *pOutput )
|
|
{
|
|
if ( !pOutput )
|
|
return;
|
|
IVP_Statistic_Manager *stats = m_pPhysEnv->get_statistic_manager();
|
|
if ( stats )
|
|
{
|
|
pOutput->maxRescueSpeed = ConvertDistanceToHL( stats->max_rescue_speed );
|
|
pOutput->maxSpeedGain = ConvertDistanceToHL( stats->max_speed_gain );
|
|
pOutput->impactSysNum = stats->impact_sys_num;
|
|
pOutput->impactCounter = stats->impact_counter;
|
|
pOutput->impactSumSys = stats->impact_sum_sys;
|
|
pOutput->impactHardRescueCount = stats->impact_hard_rescue_counter;
|
|
pOutput->impactRescueAfterCount = stats->impact_rescue_after_counter;
|
|
|
|
pOutput->impactDelayedCount = stats->impact_delayed_counter;
|
|
pOutput->impactCollisionChecks = stats->impact_coll_checks;
|
|
pOutput->impactStaticCount = stats->impact_unmov;
|
|
|
|
pOutput->totalEnergyDestroyed = stats->sum_energy_destr;
|
|
pOutput->collisionPairsTotal = stats->sum_of_mindists;
|
|
pOutput->collisionPairsCreated = stats->mindists_generated;
|
|
pOutput->collisionPairsDestroyed = stats->mindists_deleted;
|
|
|
|
pOutput->potentialCollisionsObjectVsObject = stats->range_intra_exceeded;
|
|
pOutput->potentialCollisionsObjectVsWorld = stats->range_world_exceeded;
|
|
|
|
pOutput->frictionEventsProcessed = stats->processed_fmindists;
|
|
}
|
|
}
|
|
|
|
void CPhysicsEnvironment::ClearStats()
|
|
{
|
|
IVP_Statistic_Manager *stats = m_pPhysEnv->get_statistic_manager();
|
|
if ( stats )
|
|
{
|
|
stats->clear_statistic();
|
|
}
|
|
}
|
|
|
|
void CPhysicsEnvironment::EnableConstraintNotify( bool bEnable )
|
|
{
|
|
m_enableConstraintNotify = bEnable;
|
|
}
|
|
|
|
|
|
IPhysicsEnvironment *CreatePhysicsEnvironment( void )
|
|
{
|
|
return new CPhysicsEnvironment;
|
|
}
|
|
|
|
|
|
// This wraps IVP_Collision_Filter_Exclusive_Pair since we're reusing it
|
|
// as a general void * pair hash and it's API implies that has something
|
|
// to do with collision detection
|
|
class CVoidPairHash : private IVP_Collision_Filter_Exclusive_Pair
|
|
{
|
|
public:
|
|
void AddPair( void *pObject0, void *pObject1 )
|
|
{
|
|
// disabled pairs are stored int the collision filter's hash
|
|
disable_collision_between_objects( (IVP_Real_Object *)pObject0, (IVP_Real_Object *)pObject1 );
|
|
}
|
|
|
|
void RemovePair( void *pObject0, void *pObject1 )
|
|
{
|
|
// enabling removes the stored hash pair
|
|
enable_collision_between_objects( (IVP_Real_Object *)pObject0, (IVP_Real_Object *)pObject1 );
|
|
}
|
|
|
|
bool HasPair( void *pObject0, void *pObject1 )
|
|
{
|
|
// If collision is enabled, the pair is NOT present, so invert the test here.
|
|
return check_objects_for_collision_detection( (IVP_Real_Object *)pObject0, (IVP_Real_Object *)pObject1 ) ? false : true;
|
|
}
|
|
};
|
|
|
|
|
|
class CObjectPairHash : public IPhysicsObjectPairHash
|
|
{
|
|
public:
|
|
CObjectPairHash()
|
|
{
|
|
m_pObjectHash = new IVP_VHash_Store(1024);
|
|
}
|
|
|
|
~CObjectPairHash()
|
|
{
|
|
delete m_pObjectHash;
|
|
}
|
|
|
|
// converts the void * stored in the hash to a list in the multilist
|
|
unsigned short HashToListIndex( void *pHash )
|
|
{
|
|
if ( !pHash )
|
|
return m_objectList.InvalidIndex();
|
|
|
|
unsigned int hash = (unsigned int)pHash;
|
|
// mask off the extra bit we added to avoid zeros
|
|
hash &= 0xFFFF;
|
|
return (unsigned short)hash;
|
|
}
|
|
|
|
// converts the list in the multilist to a void * we can put in the hash
|
|
void *ListIndexToHash( unsigned short listIndex )
|
|
{
|
|
unsigned int hash = (unsigned int)listIndex;
|
|
|
|
// set the high bit, so zero means "not there"
|
|
hash |= 0x80000000;
|
|
return (void *)hash;
|
|
}
|
|
|
|
// Lookup this object and get a multilist entry
|
|
unsigned short GetListForObject( void *pObject )
|
|
{
|
|
return HashToListIndex( m_pObjectHash->find_elem( pObject ) );
|
|
}
|
|
|
|
// new object, set up his list
|
|
void SetListForObject( void *pObject, unsigned short listIndex )
|
|
{
|
|
Assert( !m_pObjectHash->find_elem( pObject ) );
|
|
m_pObjectHash->add_elem( pObject, ListIndexToHash(listIndex) );
|
|
}
|
|
|
|
// last entry is gone, remove the object
|
|
void DestroyListForObject( void *pObject, unsigned short listIndex )
|
|
{
|
|
if ( m_objectList.IsValidList( listIndex ) )
|
|
{
|
|
m_objectList.DestroyList( listIndex );
|
|
m_pObjectHash->remove_elem( pObject );
|
|
}
|
|
}
|
|
|
|
// Add this object to the list of disabled objects
|
|
void AddToObjectList( void *pObject, void *pAdd )
|
|
{
|
|
unsigned short listIndex = GetListForObject( pObject );
|
|
if ( !m_objectList.IsValidList( listIndex ) )
|
|
{
|
|
listIndex = m_objectList.CreateList();
|
|
SetListForObject( pObject, listIndex );
|
|
}
|
|
|
|
m_objectList.AddToTail( listIndex, pAdd );
|
|
}
|
|
|
|
// Remove one object from a particular object's list (linear time)
|
|
void RemoveFromObjectList( void *pObject, void *pRemove )
|
|
{
|
|
unsigned short listIndex = GetListForObject( pObject );
|
|
if ( !m_objectList.IsValidList( listIndex ) )
|
|
return;
|
|
|
|
for ( unsigned short item = m_objectList.Head(listIndex); item != m_objectList.InvalidIndex(); item = m_objectList.Next(item) )
|
|
{
|
|
if ( m_objectList[item] == pRemove )
|
|
{
|
|
// found it, remove
|
|
m_objectList.Remove( listIndex, item );
|
|
if ( m_objectList.Head(listIndex) == m_objectList.InvalidIndex() )
|
|
{
|
|
DestroyListForObject( pObject, listIndex );
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add a pair (constant time)
|
|
virtual void AddObjectPair( void *pObject0, void *pObject1 )
|
|
{
|
|
if ( IsObjectPairInHash(pObject0,pObject1) )
|
|
return;
|
|
|
|
// add the pair to the hash
|
|
m_pairHash.AddPair( pObject0, pObject1 );
|
|
AddToObjectList( pObject0, pObject1 );
|
|
AddToObjectList( pObject1, pObject0 );
|
|
}
|
|
|
|
// remove a pair (linear time x 2)
|
|
virtual void RemoveObjectPair( void *pObject0, void *pObject1 )
|
|
{
|
|
if ( !IsObjectPairInHash(pObject0,pObject1) )
|
|
return;
|
|
|
|
// remove the pair from the hash
|
|
m_pairHash.RemovePair( pObject0, pObject1 );
|
|
RemoveFromObjectList( pObject0, pObject1 );
|
|
RemoveFromObjectList( pObject1, pObject0 );
|
|
}
|
|
|
|
// check for pair presence (fast constant time)
|
|
virtual bool IsObjectPairInHash( void *pObject0, void *pObject1 )
|
|
{
|
|
return m_pairHash.HasPair( pObject0, pObject1 );
|
|
}
|
|
|
|
virtual void RemoveAllPairsForObject( void *pObject )
|
|
{
|
|
unsigned short listIndex = GetListForObject( pObject );
|
|
if ( !m_objectList.IsValidList( listIndex ) )
|
|
return;
|
|
|
|
unsigned short item = m_objectList.Head(listIndex);
|
|
while ( item != m_objectList.InvalidIndex() )
|
|
{
|
|
unsigned short next = m_objectList.Next(item);
|
|
void *pOther = m_objectList[item];
|
|
m_objectList.Remove( listIndex, item );
|
|
// remove the matching entry
|
|
RemoveFromObjectList( pOther, pObject );
|
|
m_pairHash.RemovePair( pOther, pObject );
|
|
item = next;
|
|
}
|
|
DestroyListForObject( pObject, listIndex );
|
|
}
|
|
|
|
// Gets the # of dependencies for a particular entity
|
|
virtual int GetPairCountForObject( void *pObject0 )
|
|
{
|
|
unsigned short listIndex = GetListForObject( pObject0 );
|
|
if ( !m_objectList.IsValidList( listIndex ) )
|
|
return 0;
|
|
|
|
int nCount = 0;
|
|
unsigned short item;
|
|
for ( item = m_objectList.Head(listIndex); item != m_objectList.InvalidIndex();
|
|
item = m_objectList.Next(item) )
|
|
{
|
|
++nCount;
|
|
}
|
|
return nCount;
|
|
}
|
|
|
|
// Gets all dependencies for a particular entity
|
|
virtual int GetPairListForObject( void *pObject0, int nMaxCount, void **ppObjectList )
|
|
{
|
|
unsigned short listIndex = GetListForObject( pObject0 );
|
|
if ( !m_objectList.IsValidList( listIndex ) )
|
|
return 0;
|
|
|
|
int nCount = 0;
|
|
unsigned short item;
|
|
for ( item = m_objectList.Head(listIndex); item != m_objectList.InvalidIndex();
|
|
item = m_objectList.Next(item) )
|
|
{
|
|
ppObjectList[nCount] = m_objectList[item];
|
|
if ( ++nCount >= nMaxCount )
|
|
break;
|
|
}
|
|
return nCount;
|
|
}
|
|
|
|
virtual bool IsObjectInHash( void *pObject0 )
|
|
{
|
|
return m_pObjectHash->find_elem(pObject0) != NULL ? true : false;
|
|
}
|
|
#if 0
|
|
virtual int CountObjectsInHash()
|
|
{
|
|
return m_pObjectHash->n_elems();
|
|
}
|
|
#endif
|
|
|
|
private:
|
|
// this is a hash of object pairs
|
|
CVoidPairHash m_pairHash;
|
|
// this is a hash of pObjects with each element storing an index to the head of its list of disabled collisions
|
|
IVP_VHash_Store *m_pObjectHash;
|
|
|
|
// this is the list of disabled collisions for each object. Uses multilist
|
|
CUtlMultiList<void *, unsigned short> m_objectList;
|
|
};
|
|
|
|
IPhysicsObjectPairHash *CreateObjectPairHash()
|
|
{
|
|
return new CObjectPairHash;
|
|
}
|