vphysics_jolt/vphysics_jolt/vjolt_controller_player.cpp

438 lines
14 KiB
C++
Raw Normal View History

2022-08-28 22:31:01 +01:00
#include "cbase.h"
#include "vjolt_layers.h"
#include "vjolt_controller_player.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-------------------------------------------------------------------------------------------------
static ConVar vjolt_player_collision_tolerance( "vjolt_player_collision_tolerance", "0.05" );
//-------------------------------------------------------------------------------------------------
JoltPhysicsPlayerController::JoltPhysicsPlayerController( JoltPhysicsObject *pObject )
{
SetObjectInternal( pObject );
}
JoltPhysicsPlayerController::~JoltPhysicsPlayerController()
{
SetObjectInternal( nullptr );
SetGround( nullptr );
}
//-------------------------------------------------------------------------------------------------
void JoltPhysicsPlayerController::Update( const Vector &position, const Vector &velocity, float secondsToArrival, bool onground, IPhysicsObject *ground )
{
// timeOffset == secondsToArrival
const JPH::Vec3 targetPosition = SourceToJolt::Distance( position );
if ( targetPosition.IsClose( m_targetPosition ) )
return;
const JPH::Vec3 targetVelocity = SourceToJolt::Distance( velocity );
m_targetPosition = targetPosition;
m_targetVelocity = targetVelocity;
m_secondsToArrival = secondsToArrival;
// Bogus assertion: onground can be true and ground can be null when touching the world. That is okay
//VJoltAssert( ( onground && ground ) || ( !onground && !ground ) );
SetGround( static_cast<JoltPhysicsObject *>( ground ) );
}
void JoltPhysicsPlayerController::SetEventHandler( IPhysicsPlayerControllerEvent *handler )
{
m_pHandler = handler;
}
bool JoltPhysicsPlayerController::IsInContact()
{
uint32 nState = GetContactState( 0 );
return !!( nState & PLAYER_CONTACT_PHYSICS );
}
void JoltPhysicsPlayerController::MaxSpeed( const Vector &velocity )
{
// Do we have to care about this? IVP used a rigid body for the player shadow because it didn't
// have the concept of kinematic objects, our Jolt shadow follows the player exactly and
// is kinematic so if the game follows this max speed limit we don't need to care.
}
//-------------------------------------------------------------------------------------------------
void JoltPhysicsPlayerController::SetObject( IPhysicsObject *pObject )
{
SetObjectInternal( static_cast<JoltPhysicsObject *>( pObject ) );
}
//-------------------------------------------------------------------------------------------------
int JoltPhysicsPlayerController::GetShadowPosition( Vector *position, QAngle *angles )
{
return m_pObject->GetShadowPosition( position, angles );
}
void JoltPhysicsPlayerController::StepUp( float height )
{
if ( height == 0.0f )
return;
// Since the player is a kinematic object that slides around the world using velocity, when
// stepping up onto a platform we need to go there instantly, AddToPosition does that.
m_pObject->AddToPosition( JPH::Vec3( 0.0f, 0.0f, SourceToJolt::Distance( height ) ) );
}
void JoltPhysicsPlayerController::Jump()
{
// This does nothing in VPhysics.
}
void JoltPhysicsPlayerController::GetShadowVelocity( Vector *velocity )
{
if ( !velocity )
return;
JPH::Vec3 jphVelocity = m_pObject->GetBody()->GetLinearVelocity();
if ( m_pGround )
{
jphVelocity -= m_pGround->GetBody()->GetPointVelocity( m_groundPos );
}
*velocity = JoltToSource::Distance( jphVelocity );
}
IPhysicsObject *JoltPhysicsPlayerController::GetObject()
{
return m_pObject;
}
void JoltPhysicsPlayerController::GetLastImpulse( Vector *pOut )
{
VectorClear( *pOut );
}
//-------------------------------------------------------------------------------------------------
void JoltPhysicsPlayerController::SetPushMassLimit( float maxPushMass )
{
m_flPushableMassLimit = maxPushMass;
}
void JoltPhysicsPlayerController::SetPushSpeedLimit( float maxPushSpeed )
{
m_flPushableSpeedLimit = maxPushSpeed;
}
//-------------------------------------------------------------------------------------------------
float JoltPhysicsPlayerController::GetPushMassLimit()
{
return m_flPushableMassLimit;
}
float JoltPhysicsPlayerController::GetPushSpeedLimit()
{
return m_flPushableSpeedLimit;
}
//-------------------------------------------------------------------------------------------------
bool JoltPhysicsPlayerController::WasFrozen()
{
// I think here the code is referring to IVP freezing objects after inactivity (sleeping),
// our objects are forced to never sleep, so we don't need to care?
return false;
}
//-------------------------------------------------------------------------------------------------
static void CheckCollision( JoltPhysicsObject *pObject, JPH::CollideShapeCollector &ioCollector, JPH::BodyFilter &ioFilter )
{
JPH::PhysicsSystem *pSystem = pObject->GetEnvironment()->GetPhysicsSystem();
// TODO(Josh): Make a PLAYER ONLY layer that will only collide with MOVING ONLY annd
// NOTHING ELSE tomorrow.
// Create query broadphase layer filter
JPH::DefaultBroadPhaseLayerFilter broadphase_layer_filter = pSystem->GetDefaultBroadPhaseLayerFilter( Layers::MOVING );
// Create query object layer filter
JPH::DefaultObjectLayerFilter object_layer_filter = pSystem->GetDefaultLayerFilter( Layers::MOVING );
// Determine position to test
JPH::Vec3 position;
JPH::Quat rotation;
JPH::BodyInterface &bi = pSystem->GetBodyInterfaceNoLock();
bi.GetPositionAndRotation( pObject->GetBodyID(), position, rotation );
JPH::Mat44 query_transform = JPH::Mat44::sRotationTranslation( rotation, position + rotation * pObject->GetBody()->GetShape()->GetCenterOfMass() );
// Settings for collide shape
JPH::CollideShapeSettings settings;
settings.mActiveEdgeMode = JPH::EActiveEdgeMode::CollideOnlyWithActive;
settings.mActiveEdgeMovementDirection = bi.GetLinearVelocity( pObject->GetBodyID() );
settings.mBackFaceMode = JPH::EBackFaceMode::IgnoreBackFaces;
settings.mMaxSeparationDistance = vjolt_player_collision_tolerance.GetFloat();
pSystem->GetNarrowPhaseQueryNoLock().CollideShape( pObject->GetBody()->GetShape(), JPH::Vec3::sReplicate( 1.0f ), query_transform, settings, ioCollector, broadphase_layer_filter, object_layer_filter, ioFilter );
}
// Slart: This is a version of CheckCollision that projects the player by their velocity, to attempt to push objects that we'll walk into soon
#if 0
static void CheckCollision2( JoltPhysicsObject *pObject, JPH::CollideShapeCollector &ioCollector, const JPH::Vec3Arg targetVelocity, float flDeltaTime )
{
JPH::PhysicsSystem *pSystem = pObject->GetEnvironment()->GetPhysicsSystem();
// TODO(Josh): Make a PLAYER ONLY layer that will only collide with MOVING ONLY annd
// NOTHING ELSE tomorrow.
// Create query broadphase layer filter
JPH::DefaultBroadPhaseLayerFilter broadphase_layer_filter = pSystem->GetDefaultBroadPhaseLayerFilter( Layers::MOVING );
// Create query object layer filter
JPH::DefaultObjectLayerFilter object_layer_filter = pSystem->GetDefaultLayerFilter( Layers::MOVING );
// Ignore my own body
JPH::IgnoreSingleBodyFilter body_filter( pObject->GetBodyID() );
// Determine position to test
JPH::Vec3 position;
JPH::Quat rotation;
JPH::BodyInterface &bi = pSystem->GetBodyInterfaceNoLock();
bi.GetPositionAndRotation( pObject->GetBodyID(), position, rotation );
position += targetVelocity * ( flDeltaTime * 2.0f );
JPH::Mat44 query_transform = JPH::Mat44::sRotationTranslation( rotation, position + rotation * pObject->GetBody()->GetShape()->GetCenterOfMass() );
// Settings for collide shape
JPH::CollideShapeSettings settings;
settings.mActiveEdgeMode = JPH::EActiveEdgeMode::CollideOnlyWithActive;
settings.mActiveEdgeMovementDirection = bi.GetLinearVelocity( pObject->GetBodyID() );
settings.mBackFaceMode = JPH::EBackFaceMode::IgnoreBackFaces;
settings.mMaxSeparationDistance = vjolt_player_collision_tolerance.GetFloat();
pSystem->GetNarrowPhaseQueryNoLock().CollideShape( pObject->GetBody()->GetShape(), JPH::Vec3::sReplicate( 1.0f ), query_transform, settings, ioCollector, broadphase_layer_filter, object_layer_filter, body_filter );
}
#endif
template <bool MoveablesOnly>
class SourceHitFilter : public JPH::BodyFilter
{
public:
SourceHitFilter( JPH::PhysicsSystem* pPhysicsSystem, JoltPhysicsObject* pSelfObject )
: m_pPhysicsSystem( pPhysicsSystem )
, m_pSelfObject( pSelfObject )
{
}
bool ShouldCollideLocked( const JPH::Body &inBody ) const override
{
JoltPhysicsObject* pObject = reinterpret_cast<JoltPhysicsObject*>( inBody.GetUserData() );
// Ignore self if specified. This can be nullptr if you don't want this.
if ( pObject == m_pSelfObject )
return false;
if constexpr ( MoveablesOnly )
{
if ( pObject->IsTrigger() || !pObject->IsMoveable() )
return false;
}
if ( !pObject->GetEnvironment()->GetContactListener()->ShouldCollide( m_pSelfObject, pObject ) )
return false;
return true;
}
private:
JPH::PhysicsSystem *m_pPhysicsSystem;
JoltPhysicsObject *m_pSelfObject;
};
uint32 JoltPhysicsPlayerController::GetContactState( uint16 nGameFlags )
{
// This does not seem to affect much, we should aspire to have our physics be as 1:1 to brush collisions as possible anyway
#ifdef GAME_PORTAL2_OR_NEWER
if ( !m_pObject->IsCollisionEnabled() )
return 0;
// Collector that finds the hit with the normal that is the most 'up'
class ContactStateCollector : public JPH::CollideShapeCollector
{
public:
ContactStateCollector( JPH::PhysicsSystem *pPhysicsSystem, JoltPhysicsObject *pPlayerObject, uint16 nGameFlags )
: m_pPhysicsSystem( pPhysicsSystem )
, m_pPlayerObject( pPlayerObject )
, m_nGameFlags( nGameFlags )
{
}
void AddHit( const JPH::CollideShapeResult &inResult ) override
{
JPH::BodyLockRead lock( m_pPhysicsSystem->GetBodyLockInterfaceNoLock(), inResult.mBodyID2 );
const JPH::Body &body = lock.GetBody();
JoltPhysicsObject *pObject = reinterpret_cast<JoltPhysicsObject *>( body.GetUserData() );
if ( !pObject->IsControlledByGame() )
m_nFlagsOut |= PLAYER_CONTACT_PHYSICS;
if ( pObject->GetGameFlags() & m_nGameFlags )
m_nFlagsOut |= PLAYER_CONTACT_GAMEOBJECT;
}
uint32 m_nFlagsOut = 0;
private:
JPH::PhysicsSystem *m_pPhysicsSystem;
JoltPhysicsObject *m_pPlayerObject;
uint16 m_nGameFlags;
};
JPH::PhysicsSystem *pSystem = m_pObject->GetEnvironment()->GetPhysicsSystem();
ContactStateCollector collector( pSystem, m_pObject, nGameFlags );
SourceHitFilter<true> filter( pSystem, m_pObject );
CheckCollision( m_pObject, collector, filter );
return collector.m_nFlagsOut;
#else
return 0;
#endif
}
//-------------------------------------------------------------------------------------------------
void JoltPhysicsPlayerController::OnPreSimulate( float flDeltaTime )
{
VJoltAssertMsg( m_pObject->GetBody()->GetMotionType() == JPH::EMotionType::Kinematic, "Shadow controllers must be kinematic!" );
#if 0
if ( m_pGround )
{
JPH::Mat44 matrix = JPH::Mat44::sRotationTranslation( m_pObject->GetBody()->GetRotation(), m_pObject->GetBody()->GetPosition() ).Transposed3x3();
m_groundPos = -matrix.Multiply3x3( m_targetPosition );
matrix3x4_t mat;
m_pGround->GetPositionMatrix( &mat );
m_targetPosition = mat.TransformVector( m_GroundPos );
m_pGround->GetVelocityAtPoint( m_GroundPos, &groundVelocity );
m_pObject->AddVelocity( -groundVelocity );
}
#else
/*if ( m_pGround )
{
JPH::Mat44 matrix = JPH::Mat44::sRotationTranslation( m_pObject->GetBody()->GetRotation(), m_pObject->GetBody()->GetPosition() ).Transposed3x3();
JPH::Vec3 groundPos = -matrix.Multiply3x3( m_targetPosition );
int g = 5;
m_targetPosition = matrix * groundPos;
}*/
#endif
// Apply downwards force to the ground
// This code mimics JoltObject::ApplyForceOffset but without Source > Jolt conversions
/*if ( m_pGround && m_pGround->IsMoveable() )
{
JPH::PhysicsSystem *pPhysicsSystem = m_pGround->GetEnvironment()->GetPhysicsSystem();
JPH::BodyInterface &bodyInterface = pPhysicsSystem->GetBodyInterfaceNoLock();
bodyInterface.AddImpulse( m_pGround->GetBodyID(), pPhysicsSystem->GetGravity() * m_pObject->GetMass() * flDeltaTime, m_pObject->GetBody()->GetPosition() );
}*/
JPH::PhysicsSystem *pPhysicsSystem = m_pObject->GetEnvironment()->GetPhysicsSystem();
JPH::BodyInterface &bodyInterface = pPhysicsSystem->GetBodyInterfaceNoLock();
// Project ourselves towards our velocity
JPH::AnyHitCollisionCollector<JPH::CollideShapeCollector> collector;
SourceHitFilter<true> filter( pPhysicsSystem, m_pObject );
CheckCollision( m_pObject, collector, filter );
if ( collector.HadHit() )
{
JPH::BodyID otherID = collector.mHit.mBodyID2;
//bodyInterface.AddImpulse( otherID, m_pObject->GetMass() * m_targetVelocity * flDeltaTime, m_pObject->GetBody()->GetPosition() );
bodyInterface.AddImpulse( otherID, m_pObject->GetMass() * pPhysicsSystem->GetGravity() * flDeltaTime, m_pObject->GetBody()->GetPosition());
}
if ( m_secondsToArrival > 0.0f )
bodyInterface.MoveKinematic( m_pObject->GetBodyID(), m_targetPosition, JPH::Quat::sIdentity(), m_secondsToArrival );
else
{
bodyInterface.SetPositionAndRotation( m_pObject->GetBodyID(), m_targetPosition, JPH::Quat::sIdentity(), JPH::EActivation::Activate );
bodyInterface.SetLinearAndAngularVelocity( m_pObject->GetBodyID(), JPH::Vec3::sZero(), JPH::Vec3::sZero() );
}
m_secondsToArrival = Max( m_secondsToArrival - flDeltaTime, 0.0f );
}
void JoltPhysicsPlayerController::OnJoltPhysicsObjectDestroyed( JoltPhysicsObject *pObject )
{
if ( pObject == m_pObject )
{
SetObjectInternal( nullptr );
}
if ( pObject == m_pGround )
{
SetGround( nullptr );
}
}
//-------------------------------------------------------------------------------------------------
void JoltPhysicsPlayerController::SetObjectInternal( JoltPhysicsObject *pObject )
{
if ( m_pObject == pObject )
return;
// Reset the last object
if ( m_pObject )
{
// Don't bother resetting kinematic or sleep state, it does not matter because
// any object tied to a player controller was created to be a player object
m_pObject->RemoveDestroyedListener( this );
m_pObject->RemoveCallbackFlags( CALLBACK_IS_PLAYER_CONTROLLER );
}
// Set our new object
m_pObject = pObject;
// Adjust the new object
if ( m_pObject )
{
// Set kinematic
m_pObject->GetBody()->SetMotionType( JPH::EMotionType::Kinematic );
m_pObject->GetBody()->SetAllowSleeping( false );
m_pObject->AddDestroyedListener( this );
m_pObject->AddCallbackFlags( CALLBACK_IS_PLAYER_CONTROLLER );
}
}
void JoltPhysicsPlayerController::SetGround( JoltPhysicsObject *pGround )
{
if ( m_pGround == pGround )
return;
if ( m_pGround )
{
m_pGround->RemoveDestroyedListener( this );
}
// Set our new ground
m_pGround = pGround;
if ( m_pGround )
{
m_pGround->AddDestroyedListener( this );
}
}