summaryrefslogtreecommitdiff
path: root/vphysics/physics_environment.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'vphysics/physics_environment.cpp')
-rw-r--r--vphysics/physics_environment.cpp2228
1 files changed, 2228 insertions, 0 deletions
diff --git a/vphysics/physics_environment.cpp b/vphysics/physics_environment.cpp
new file mode 100644
index 0000000..c261f68
--- /dev/null
+++ b/vphysics/physics_environment.cpp
@@ -0,0 +1,2228 @@
+//========= 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 &params, 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;
+}