//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Provides structures and classes necessary to simulate a portal. // // $NoKeywords: $ //=====================================================================================// #ifndef PORTALSIMULATION_H #define PORTALSIMULATION_H #ifdef _WIN32 #pragma once #endif #include "mathlib/polyhedron.h" #include "const.h" #include "tier1/utlmap.h" #include "tier1/utlvector.h" #define PORTAL_SIMULATORS_EMBED_GUID //define this to embed a unique integer with each portal simulator for debugging purposes struct StaticPropPolyhedronGroups_t //each static prop is made up of a group of polyhedrons, these help us pull those groups from an array { int iStartIndex; int iNumPolyhedrons; }; enum PortalSimulationEntityFlags_t { PSEF_OWNS_ENTITY = (1 << 0), //this environment is responsible for the entity's physics objects PSEF_OWNS_PHYSICS = (1 << 1), PSEF_IS_IN_PORTAL_HOLE = (1 << 2), //updated per-phyframe PSEF_CLONES_ENTITY_FROM_MAIN = (1 << 3), //entity is close enough to the portal to affect objects intersecting the portal //PSEF_HAS_LINKED_CLONE = (1 << 1), //this environment has a clone of the entity which is transformed from its linked portal }; enum PS_PhysicsObjectSourceType_t { PSPOST_LOCAL_BRUSHES, PSPOST_REMOTE_BRUSHES, PSPOST_LOCAL_STATICPROPS, PSPOST_REMOTE_STATICPROPS, PSPOST_HOLYWALL_TUBE }; struct PortalTransformAsAngledPosition_t //a matrix transformation from this portal to the linked portal, stored as vector and angle transforms { Vector ptOriginTransform; QAngle qAngleTransform; }; inline bool LessFunc_Integer( const int &a, const int &b ) { return a < b; }; class CPortalSimulatorEventCallbacks //sends out notifications of events to game specific code { public: virtual void PortalSimulator_TookOwnershipOfEntity( CBaseEntity *pEntity ) { }; virtual void PortalSimulator_ReleasedOwnershipOfEntity( CBaseEntity *pEntity ) { }; virtual void PortalSimulator_TookPhysicsOwnershipOfEntity( CBaseEntity *pEntity ) { }; virtual void PortalSimulator_ReleasedPhysicsOwnershipOfEntity( CBaseEntity *pEntity ) { }; }; //==================================================================================== // To any coder trying to understand the following nested structures.... // // You may be wondering... why? wtf? // // The answer. The previous incarnation of server side portal simulation suffered // terribly from evolving variables with increasingly cryptic names with no clear // definition of what part of the system the variable was involved with. // // It's my hope that a nested structure with clear boundaries will eliminate that // horrible, awful, nasty, frustrating confusion. (It was really really bad). This // system has the added benefit of pseudo-forcing a naming structure. // // Lastly, if it all roots in one struct, we can const reference it out to allow // easy reads without writes // // It's broken out like this to solve a few problems.... // 1. It cleans up intellisense when you don't actually define a structure // within a structure. // 2. Shorter typenames when you want to have a pointer/reference deep within // the nested structure. // 3. Needed at least one level removed from CPortalSimulator so // pointers/references could be made while the primary instance of the // data was private/protected. // // It may be slightly difficult to understand in it's broken out structure, but // intellisense brings all the data together in a very cohesive manner for // working with. //==================================================================================== struct PS_PlacementData_t //stuff useful for geometric operations { Vector ptCenter; QAngle qAngles; Vector vForward; Vector vUp; Vector vRight; VPlane PortalPlane; VMatrix matThisToLinked; VMatrix matLinkedToThis; PortalTransformAsAngledPosition_t ptaap_ThisToLinked; PortalTransformAsAngledPosition_t ptaap_LinkedToThis; CPhysCollide *pHoleShapeCollideable; //used to test if a collideable is in the hole, should NOT be collided against in general PS_PlacementData_t( void ) { memset( this, 0, sizeof( PS_PlacementData_t ) ); } }; struct PS_SD_Static_World_Brushes_t { CUtlVector Polyhedrons; //the building blocks of more complex collision CPhysCollide *pCollideable; #ifndef CLIENT_DLL IPhysicsObject *pPhysicsObject; PS_SD_Static_World_Brushes_t() : pCollideable(NULL), pPhysicsObject(NULL) {}; #else PS_SD_Static_World_Brushes_t() : pCollideable(NULL) {}; #endif }; struct PS_SD_Static_World_StaticProps_ClippedProp_t { StaticPropPolyhedronGroups_t PolyhedronGroup; CPhysCollide * pCollide; #ifndef CLIENT_DLL IPhysicsObject * pPhysicsObject; #endif IHandleEntity * pSourceProp; int iTraceContents; short iTraceSurfaceProps; static CBaseEntity * pTraceEntity; static const char * szTraceSurfaceName; //same for all static props, here just for easy reference static const int iTraceSurfaceFlags; //same for all static props, here just for easy reference }; struct PS_SD_Static_World_StaticProps_t { CUtlVector Polyhedrons; //the building blocks of more complex collision CUtlVector ClippedRepresentations; bool bCollisionExists; //the shortcut to know if collideables exist for each prop bool bPhysicsExists; //the shortcut to know if physics obects exist for each prop PS_SD_Static_World_StaticProps_t( void ) : bCollisionExists( false ), bPhysicsExists( false ) { }; }; struct PS_SD_Static_World_t //stuff in front of the portal { PS_SD_Static_World_Brushes_t Brushes; PS_SD_Static_World_StaticProps_t StaticProps; }; struct PS_SD_Static_Wall_Local_Tube_t //a minimal tube, an object must fit inside this to be eligible for portaling { CUtlVector Polyhedrons; //the building blocks of more complex collision CPhysCollide *pCollideable; #ifndef CLIENT_DLL IPhysicsObject *pPhysicsObject; PS_SD_Static_Wall_Local_Tube_t() : pCollideable(NULL), pPhysicsObject(NULL) {}; #else PS_SD_Static_Wall_Local_Tube_t() : pCollideable(NULL) {}; #endif }; struct PS_SD_Static_Wall_Local_Brushes_t { CUtlVector Polyhedrons; //the building blocks of more complex collision CPhysCollide *pCollideable; #ifndef CLIENT_DLL IPhysicsObject *pPhysicsObject; PS_SD_Static_Wall_Local_Brushes_t() : pCollideable(NULL), pPhysicsObject(NULL) {}; #else PS_SD_Static_Wall_Local_Brushes_t() : pCollideable(NULL) {}; #endif }; struct PS_SD_Static_Wall_Local_t //things in the wall that are completely independant of having a linked portal { PS_SD_Static_Wall_Local_Tube_t Tube; PS_SD_Static_Wall_Local_Brushes_t Brushes; }; struct PS_SD_Static_Wall_RemoteTransformedToLocal_Brushes_t { IPhysicsObject *pPhysicsObject; PS_SD_Static_Wall_RemoteTransformedToLocal_Brushes_t() : pPhysicsObject(NULL) {}; }; struct PS_SD_Static_Wall_RemoteTransformedToLocal_StaticProps_t { CUtlVector PhysicsObjects; }; struct PS_SD_Static_Wall_RemoteTransformedToLocal_t //things taken from the linked portal's "World" collision and transformed into local space { PS_SD_Static_Wall_RemoteTransformedToLocal_Brushes_t Brushes; PS_SD_Static_Wall_RemoteTransformedToLocal_StaticProps_t StaticProps; }; struct PS_SD_Static_Wall_t //stuff behind the portal { PS_SD_Static_Wall_Local_t Local; #ifndef CLIENT_DLL PS_SD_Static_Wall_RemoteTransformedToLocal_t RemoteTransformedToLocal; #endif }; struct PS_SD_Static_SurfaceProperties_t //surface properties to pretend every collideable here is using { int contents; csurface_t surface; CBaseEntity *pEntity; }; struct PS_SD_Static_t //stuff that doesn't move around { PS_SD_Static_World_t World; PS_SD_Static_Wall_t Wall; PS_SD_Static_SurfaceProperties_t SurfaceProperties; }; class CPhysicsShadowClone; struct PS_SD_Dynamic_PhysicsShadowClones_t { CUtlVector ShouldCloneFromMain; //a list of entities that should be cloned from main if physics simulation is enabled //in single-environment mode, this helps us track who should collide with who CUtlVector FromLinkedPortal; }; struct PS_SD_Dynamic_t //stuff that moves around { unsigned int EntFlags[MAX_EDICTS]; //flags maintained for every entity in the world based on its index PS_SD_Dynamic_PhysicsShadowClones_t ShadowClones; CUtlVector OwnedEntities; PS_SD_Dynamic_t() { memset( EntFlags, 0, sizeof( EntFlags ) ); } }; class CPSCollisionEntity; struct PS_SimulationData_t //compartmentalized data for coherent management { PS_SD_Static_t Static; #ifndef CLIENT_DLL PS_SD_Dynamic_t Dynamic; IPhysicsEnvironment *pPhysicsEnvironment; CPSCollisionEntity *pCollisionEntity; //the entity we'll be tying physics objects to for collision PS_SimulationData_t() : pPhysicsEnvironment(NULL), pCollisionEntity(NULL) {}; #endif }; struct PS_InternalData_t { PS_PlacementData_t Placement; PS_SimulationData_t Simulation; }; class CPortalSimulator { public: CPortalSimulator( void ); ~CPortalSimulator( void ); void MoveTo( const Vector &ptCenter, const QAngle &angles ); void ClearEverything( void ); void AttachTo( CPortalSimulator *pLinkedPortalSimulator ); void DetachFromLinked( void ); //detach portals to sever the connection, saves work when planning on moving both portals CPortalSimulator *GetLinkedPortalSimulator( void ) const; void SetPortalSimulatorCallbacks( CPortalSimulatorEventCallbacks *pCallbacks ); bool IsReadyToSimulate( void ) const; //is active and linked to another portal void SetCollisionGenerationEnabled( bool bEnabled ); //enable/disable collision generation for the hole in the wall, needed for proper vphysics simulation bool IsCollisionGenerationEnabled( void ) const; void SetVPhysicsSimulationEnabled( bool bEnabled ); //enable/disable vphysics simulation. Will automatically update the linked portal to be the same bool IsSimulatingVPhysics( void ) const; //this portal is setup to handle any physically simulated object, false means the portal is handling player movement only bool EntityIsInPortalHole( CBaseEntity *pEntity ) const; //true if the entity is within the portal cutout bounds and crossing the plane. Not just *near* the portal bool EntityHitBoxExtentIsInPortalHole( CBaseAnimating *pBaseAnimating ) const; //true if the entity is within the portal cutout bounds and crossing the plane. Not just *near* the portal void RemoveEntityFromPortalHole( CBaseEntity *pEntity ); //if the entity is in the portal hole, this forcibly moves it out by any means possible bool RayIsInPortalHole( const Ray_t &ray ) const; //traces a ray against the same detector for EntityIsInPortalHole(), bias is towards false positives #ifndef CLIENT_DLL int GetMoveableOwnedEntities( CBaseEntity **pEntsOut, int iEntOutLimit ); //gets owned entities that aren't either world or static props. Excludes fake portal ents such as physics clones static CPortalSimulator *GetSimulatorThatOwnsEntity( const CBaseEntity *pEntity ); //fairly cheap to call static CPortalSimulator *GetSimulatorThatCreatedPhysicsObject( const IPhysicsObject *pObject, PS_PhysicsObjectSourceType_t *pOut_SourceType = NULL ); static void Pre_UTIL_Remove( CBaseEntity *pEntity ); static void Post_UTIL_Remove( CBaseEntity *pEntity ); //these three really should be made internal and the public interface changed to a "watch this entity" setup void TakeOwnershipOfEntity( CBaseEntity *pEntity ); //general ownership, not necessarily physics ownership void ReleaseOwnershipOfEntity( CBaseEntity *pEntity, bool bMovingToLinkedSimulator = false ); //if bMovingToLinkedSimulator is true, the code skips some steps that are going to be repeated when the entity is added to the other simulator void ReleaseAllEntityOwnership( void ); //go back to not owning any entities //void TeleportEntityToLinkedPortal( CBaseEntity *pEntity ); void StartCloningEntity( CBaseEntity *pEntity ); void StopCloningEntity( CBaseEntity *pEntity ); bool OwnsEntity( const CBaseEntity *pEntity ) const; bool OwnsPhysicsForEntity( const CBaseEntity *pEntity ) const; bool CreatedPhysicsObject( const IPhysicsObject *pObject, PS_PhysicsObjectSourceType_t *pOut_SourceType = NULL ) const; //true if the physics object was generated by this portal simulator static void PrePhysFrame( void ); static void PostPhysFrame( void ); #endif //#ifndef CLIENT_DLL #ifdef PORTAL_SIMULATORS_EMBED_GUID int GetPortalSimulatorGUID( void ) const { return m_iPortalSimulatorGUID; }; #endif protected: bool m_bLocalDataIsReady; //this side of the portal is properly setup, no guarantees as to linkage to another portal bool m_bSimulateVPhysics; bool m_bGenerateCollision; bool m_bSharedCollisionConfiguration; //when portals are in certain configurations, they need to cross-clip and share some collision data and things get nasty. For the love of all that is holy, pray that this is false. CPortalSimulator *m_pLinkedPortal; bool m_bInCrossLinkedFunction; //A flag to mark that we're already in a linked function and that the linked portal shouldn't call our side CPortalSimulatorEventCallbacks *m_pCallbacks; #ifdef PORTAL_SIMULATORS_EMBED_GUID int m_iPortalSimulatorGUID; #endif struct { bool bPolyhedronsGenerated; bool bLocalCollisionGenerated; bool bLinkedCollisionGenerated; bool bLocalPhysicsGenerated; bool bLinkedPhysicsGenerated; } m_CreationChecklist; friend class CPSCollisionEntity; #ifndef CLIENT_DLL //physics handled purely by server side void TakePhysicsOwnership( CBaseEntity *pEntity ); void ReleasePhysicsOwnership( CBaseEntity *pEntity, bool bContinuePhysicsCloning = true, bool bMovingToLinkedSimulator = false ); void CreateAllPhysics( void ); void CreateMinimumPhysics( void ); //stuff needed by any part of physics simulations void CreateLocalPhysics( void ); void CreateLinkedPhysics( void ); void ClearAllPhysics( void ); void ClearMinimumPhysics( void ); void ClearLocalPhysics( void ); void ClearLinkedPhysics( void ); void ClearLinkedEntities( void ); //gets rid of transformed shadow clones #endif void CreateAllCollision( void ); void CreateLocalCollision( void ); void CreateLinkedCollision( void ); void ClearAllCollision( void ); void ClearLinkedCollision( void ); void ClearLocalCollision( void ); void CreatePolyhedrons( void ); //carves up the world around the portal's position into sets of polyhedrons void ClearPolyhedrons( void ); void UpdateLinkMatrix( void ); void MarkAsOwned( CBaseEntity *pEntity ); void MarkAsReleased( CBaseEntity *pEntity ); PS_InternalData_t m_InternalData; public: const PS_InternalData_t &m_DataAccess; friend class CPS_AutoGameSys_EntityListener; }; extern CUtlVector const &g_PortalSimulators; #ifndef CLIENT_DLL class CPSCollisionEntity : public CBaseEntity { DECLARE_CLASS( CPSCollisionEntity, CBaseEntity ); private: CPortalSimulator *m_pOwningSimulator; public: CPSCollisionEntity( void ); virtual ~CPSCollisionEntity( void ); virtual void Spawn( void ); virtual void Activate( void ); virtual int ObjectCaps( void ); virtual IPhysicsObject *VPhysicsGetObject( void ); virtual int VPhysicsGetObjectList( IPhysicsObject **pList, int listMax ); virtual void UpdateOnRemove( void ); virtual bool ShouldCollide( int collisionGroup, int contentsMask ) const; virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) {} virtual void VPhysicsFriction( IPhysicsObject *pObject, float energy, int surfaceProps, int surfacePropsHit ) {} static bool IsPortalSimulatorCollisionEntity( const CBaseEntity *pEntity ); friend class CPortalSimulator; }; #endif #ifndef CLIENT_DLL inline bool CPortalSimulator::OwnsEntity( const CBaseEntity *pEntity ) const { return ((m_InternalData.Simulation.Dynamic.EntFlags[pEntity->entindex()] & PSEF_OWNS_ENTITY) != 0); } inline bool CPortalSimulator::OwnsPhysicsForEntity( const CBaseEntity *pEntity ) const { return ((m_InternalData.Simulation.Dynamic.EntFlags[pEntity->entindex()] & PSEF_OWNS_PHYSICS) != 0); } #endif inline bool CPortalSimulator::IsReadyToSimulate( void ) const { return m_bLocalDataIsReady && m_pLinkedPortal && m_pLinkedPortal->m_bLocalDataIsReady; } inline bool CPortalSimulator::IsSimulatingVPhysics( void ) const { return m_bSimulateVPhysics; } inline bool CPortalSimulator::IsCollisionGenerationEnabled( void ) const { return m_bGenerateCollision; } inline CPortalSimulator *CPortalSimulator::GetLinkedPortalSimulator( void ) const { return m_pLinkedPortal; } #endif //#ifndef PORTALSIMULATION_H