//================================================================================================= // // A physics object, implemented as a wrapper over JPH::Body // Every tangible object in the game has one of these // //================================================================================================= #include "cbase.h" #include "vjolt_collide.h" #include "vjolt_surfaceprops.h" #include "vjolt_friction.h" #include "vjolt_environment.h" #include "vjolt_layers.h" #include "vjolt_controller_shadow.h" #include "vjolt_object.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //------------------------------------------------------------------------------------------------- JoltPhysicsObject::JoltPhysicsObject( JPH::Body *pBody, JoltPhysicsEnvironment *pEnvironment, bool bStatic, int nMaterialIndex, const objectparams_t *pParams ) : m_pBody( pBody ) , m_pEnvironment( pEnvironment ) , m_pPhysicsSystem( pEnvironment->GetPhysicsSystem() ) , m_bStatic( bStatic ) , m_flCachedMass( pParams->mass ) , m_flCachedInvMass( m_flCachedMass ? 1.0f / m_flCachedMass : 0.0f ) , m_pGameData( pParams->pGameData ) , m_materialIndex( Max( nMaterialIndex, 0 ) ) // Sometimes we get passed -1. , m_flVolume( pParams->volume ) , m_pName( pParams->pName ) { // Josh: // Assert that m_pGameData is the first element, some games // decide to just read this directly by offsetting by the vtable size // instead of calling GetGameData(). static_assert( offsetof( JoltPhysicsObject, m_pGameData ) == sizeof( void * ) ); // Set the body's userdata as ourselves pBody->SetUserData( reinterpret_cast( this ) ); if ( !m_pBody->IsStatic() ) { JPH::MotionProperties* pMotionProperties = m_pBody->GetMotionProperties(); pMotionProperties->SetLinearDamping( pParams->damping ); pMotionProperties->SetAngularDamping( pParams->rotdamping ); } UpdateMaterialProperties(); } JoltPhysicsObject::JoltPhysicsObject( JPH::Body *pBody, JoltPhysicsEnvironment *pEnvironment, void *pGameData, JPH::StateRecorder &recorder ) : m_pBody( pBody ) , m_pEnvironment( pEnvironment ) , m_pPhysicsSystem( pEnvironment->GetPhysicsSystem() ) , m_pGameData( pGameData ) { RestoreObjectState( recorder ); } JoltPhysicsObject::~JoltPhysicsObject() { RemoveShadowController(); // Josh: // Iterate over this in reverse as we could remove a listener from inside this callback for ( int i = m_destroyedListeners.Count() - 1; i >= 0; i-- ) m_destroyedListeners[ i ]->OnJoltPhysicsObjectDestroyed( this ); m_pEnvironment->RemoveDirtyStaticBody( GetBodyID() ); JPH::BodyInterface& bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); bodyInterface.DestroyBody( GetBodyID() ); } bool JoltPhysicsObject::IsStatic() const { // Whether this a static body to VPhysics and not // to Jolt (static or motion disabled) itself. return m_bStatic; } bool JoltPhysicsObject::IsAsleep() const { return !m_pBody->IsActive(); } bool JoltPhysicsObject::IsTrigger() const { return m_pBody->IsSensor(); } bool JoltPhysicsObject::IsFluid() const { return m_pFluidController != nullptr; } bool JoltPhysicsObject::IsHinged() const { Log_Stub( LOG_VJolt ); return false; } bool JoltPhysicsObject::IsCollisionEnabled() const { return m_bCachedCollisionEnabled; } bool JoltPhysicsObject::IsGravityEnabled() const { if ( !m_pBody->IsStatic() ) { JPH::MotionProperties* pMotionProperties = m_pBody->GetMotionProperties(); return pMotionProperties->GetGravityFactor() != 0.0f; } return false; } bool JoltPhysicsObject::IsDragEnabled() const { Log_Stub( LOG_VJolt ); return false; } bool JoltPhysicsObject::IsMotionEnabled() const { return !m_bPinned; } bool JoltPhysicsObject::IsMoveable() const { return IsMotionEnabled() && !IsStatic(); } bool JoltPhysicsObject::IsAttachedToConstraint( bool bExternalOnly ) const { Log_Stub( LOG_VJolt ); return false; } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::EnableCollisions( bool enable ) { // Josh: // When collisions are disabled, they disable for EVERYTHING, including against the world. m_bCachedCollisionEnabled = enable; UpdateLayer(); } void JoltPhysicsObject::EnableGravity( bool enable ) { if ( !m_pBody->IsStatic() ) { JPH::MotionProperties* pMotionProperties = m_pBody->GetMotionProperties(); pMotionProperties->SetGravityFactor( enable ? 1.0f : 0.0f ); } } void JoltPhysicsObject::EnableDrag( bool enable ) { Log_Stub( LOG_VJolt ); } void JoltPhysicsObject::EnableMotion( bool enable ) { if ( IsStatic() ) return; const bool bPinned = !enable; if ( m_bPinned == bPinned ) return; m_bPinned = bPinned; UpdateLayer(); } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::SetGameData( void *pGameData ) { m_pGameData = pGameData; } void *JoltPhysicsObject::GetGameData() const { return m_pGameData; } void JoltPhysicsObject::SetGameFlags( unsigned short userFlags ) { m_gameFlags = userFlags; } unsigned short JoltPhysicsObject::GetGameFlags() const { return m_gameFlags; } void JoltPhysicsObject::SetGameIndex( unsigned short gameIndex ) { m_gameIndex = gameIndex; } unsigned short JoltPhysicsObject::GetGameIndex() const { return m_gameIndex; } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::SetCallbackFlags( unsigned short callbackflags ) { m_callbackFlags = callbackflags; } unsigned short JoltPhysicsObject::GetCallbackFlags() const { return m_callbackFlags; } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::Wake() { if ( !m_pBody->IsStatic() ) { JPH::BodyInterface& bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); bodyInterface.ActivateBody( m_pBody->GetID() ); } else { // See other comments in UpdateLayer. m_pEnvironment->AddDirtyStaticBody( m_pBody->GetID() ); } } void JoltPhysicsObject::Sleep() { if ( !m_pBody->IsStatic() ) { JPH::BodyInterface& bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); bodyInterface.DeactivateBody( m_pBody->GetID() ); } } void JoltPhysicsObject::RecheckCollisionFilter() { RecheckContactPoints(); } void JoltPhysicsObject::RecheckContactPoints( bool bSearchForNewContacts /*= false*/ ) { JPH::BodyInterface& bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); bodyInterface.InvalidateContactCache( GetBodyID() ); } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::SetMass( float mass ) { // To match regular VPhysics, allow 0 here but not at init. mass = Clamp( mass, 0.0f, VPHYSICS_MAX_MASS ); m_flCachedMass = mass; m_flCachedInvMass = mass ? 1.0f / mass : 0.0f; if ( !IsStatic() ) { JPH::MotionProperties* pMotionProperties = m_pBody->GetMotionProperties(); // Mass is already in KG. IVP is weird. // Josh: This is what we used to do and it was giving VERY whacky results // when moving objects around after calling SetMass. // pMotionProperties->SetInverseMass( m_flCachedInvMass ); // This method below seems to work properly because it deals with all of the inertia crap. JPH::MassProperties massProperties = m_pBody->GetShape()->GetMassProperties(); massProperties.ScaleToMass( mass ); massProperties.mInertia( 3, 3 ) = 1.0f; pMotionProperties->SetMassProperties( JPH::EAllowedDOFs::All, massProperties ); CalculateBuoyancy(); } } float JoltPhysicsObject::GetMass() const { return m_flCachedMass; } float JoltPhysicsObject::GetInvMass() const { return m_flCachedInvMass; } Vector JoltPhysicsObject::GetInertia() const { Vector inv = GetInvInertia(); return Vector( 1.0f / inv.x, 1.0f / inv.y, 1.0f / inv.z ); } Vector JoltPhysicsObject::GetInvInertia() const { if ( IsStatic() ) return Vector( 1.0f, 1.0f, 1.0f ); //const JPH::Vec3 inertia = m_pBody->GetMotionProperties()->GetInverseInertiaDiagonal(); const JPH::Vec3 inertia = m_pBody->GetInverseInertia() * JPH::Vec3::sReplicate( 1.0f ); return Abs( JoltToSource::Unitless( inertia ) ); } void JoltPhysicsObject::SetInertia( const Vector &inertia ) { // TODO(Josh): Does anything use this? // and if so, does this specify the local diagonal or rotated diagonal? Log_Stub( LOG_VJolt ); } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::SetDamping( const float *speed, const float *rot ) { if ( IsStatic() ) return; JPH::MotionProperties* pMotionProperties = m_pBody->GetMotionProperties(); if ( speed ) pMotionProperties->SetLinearDamping( *speed ); if ( rot ) pMotionProperties->SetAngularDamping( *rot ); } void JoltPhysicsObject::GetDamping( float *speed, float *rot ) const { if ( IsStatic() ) return; JPH::MotionProperties* pMotionProperties = m_pBody->GetMotionProperties(); if ( speed ) *speed = pMotionProperties->GetLinearDamping(); if ( rot ) *rot = pMotionProperties->GetAngularDamping(); } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::SetDragCoefficient( float *pDrag, float *pAngularDrag ) { Log_Stub( LOG_VJolt ); } void JoltPhysicsObject::SetBuoyancyRatio( float ratio ) { m_flBuoyancyRatio = ratio; } //------------------------------------------------------------------------------------------------- int JoltPhysicsObject::GetMaterialIndex() const { return m_materialIndex; } void JoltPhysicsObject::SetMaterialIndex( int materialIndex ) { // Gotta clamp it, because we get -1 sometimes materialIndex = Max( 0, materialIndex ); if ( m_materialIndex != materialIndex ) { m_materialIndex = materialIndex; UpdateMaterialProperties(); } } //------------------------------------------------------------------------------------------------- unsigned int JoltPhysicsObject::GetContents() const { return m_contents; } void JoltPhysicsObject::SetContents( unsigned int contents ) { m_contents = contents; } //------------------------------------------------------------------------------------------------- float JoltPhysicsObject::GetSphereRadius() const { if ( m_pBody->GetShape()->GetSubType() != JPH::EShapeSubType::Sphere ) return 0.0f; const JPH::SphereShape *pSphereShape = static_cast< const JPH::SphereShape * >( m_pBody->GetShape() ); return JoltToSource::Distance( pSphereShape->GetRadius() ); } void JoltPhysicsObject::SetSphereRadius( float radius ) { if ( m_pBody->GetShape()->GetSubType() != JPH::EShapeSubType::Sphere ) return; // Can't get this shape non-const... urg... Log_Stub( LOG_VJolt ); } float JoltPhysicsObject::GetEnergy() const { // 1/2 * mv^2 const float flKineticEnergy = 0.5f * m_flCachedMass * m_pBody->GetLinearVelocity().LengthSq(); // TODO(Josh): We need to factor in inertia or something here to get this right. // as this AngularVelocity is in rads/s... // I guess it's a good enough approximation for now. // Right now the equation is 1/2ww where we probably want 1/2wIw. // Intertia in Jolt is weird... Not sure what's going on with its matrix thingy. const float flAngularEnergy = 0.5f * m_flCachedMass * m_pBody->GetAngularVelocity().LengthSq(); return JoltToSource::Energy( flKineticEnergy + flAngularEnergy ); } Vector JoltPhysicsObject::GetMassCenterLocalSpace() const { return JoltToSource::Distance( m_pBody->GetShape()->GetCenterOfMass() ); } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::SetPosition( const Vector &worldPosition, const QAngle &angles, bool isTeleport ) { JPH::Vec3 joltPosition = SourceToJolt::Distance( worldPosition ); JPH::Quat joltRotation = SourceToJolt::Angle( angles ); JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); bodyInterface.SetPositionAndRotation( m_pBody->GetID(), joltPosition, joltRotation, JPH::EActivation::DontActivate ); } void JoltPhysicsObject::SetPositionMatrix( const matrix3x4_t &matrix, bool isTeleport ) { SetPosition( GetColumn( matrix, MatrixAxis::Origin ), ToQAngle( matrix ), isTeleport ); } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::GetPosition( Vector *worldPosition, QAngle *angles ) const { JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); JPH::Vec3 joltPosition; JPH::Quat joltRotation; bodyInterface.GetPositionAndRotation( m_pBody->GetID(), joltPosition, joltRotation ); if ( worldPosition ) *worldPosition = JoltToSource::Distance( joltPosition ); if ( angles ) *angles = JoltToSource::Angle( joltRotation ); } void JoltPhysicsObject::GetPositionMatrix( matrix3x4_t *positionMatrix ) const { matrix3x4_t matrix; SetIdentityMatrix( matrix ); AngleMatrix( JoltToSource::Angle( m_pBody->GetRotation() ), JoltToSource::Distance( m_pBody->GetPosition() ), matrix ); *positionMatrix = matrix; } void JoltPhysicsObject::SetVelocity( const Vector *velocity, const AngularImpulse *angularVelocity ) { JPH::Vec3 joltLinearVelocity = velocity ? SourceToJolt::Distance( *velocity ) : JPH::Vec3{}; JPH::Vec3 joltAngularVelocity = angularVelocity ? SourceToJolt::AngularImpulse( *angularVelocity ) : JPH::Vec3{}; JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); if ( velocity && angularVelocity ) bodyInterface.SetLinearAndAngularVelocity( m_pBody->GetID(), joltLinearVelocity, joltAngularVelocity ); else if ( velocity ) bodyInterface.SetLinearVelocity( m_pBody->GetID(), joltLinearVelocity ); else if ( angularVelocity ) bodyInterface.SetAngularVelocity( m_pBody->GetID(), joltAngularVelocity ); } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::SetVelocityInstantaneous( const Vector *velocity, const AngularImpulse *angularVelocity ) { SetVelocity( velocity, angularVelocity ); } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::GetVelocity( Vector *velocity, AngularImpulse *angularVelocity ) const { JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); JPH::Vec3 joltLinearVelocity; JPH::Vec3 joltAngularVelocity; bodyInterface.GetLinearAndAngularVelocity( m_pBody->GetID(), joltLinearVelocity, joltAngularVelocity ); if ( velocity ) *velocity = JoltToSource::Distance( joltLinearVelocity ); if ( angularVelocity ) *angularVelocity = JoltToSource::AngularImpulse( joltAngularVelocity ); } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::AddVelocity( const Vector *velocity, const AngularImpulse *angularVelocity ) { if ( !IsMoveable() ) return; // Do this longer method do set velocity and angular velocity // in the same lock. const JPH::BodyLockInterfaceNoLock &bodyLockInterface = m_pPhysicsSystem->GetBodyLockInterfaceNoLock(); JPH::BodyLockWrite lock( bodyLockInterface, m_pBody->GetID() ); if ( lock.Succeeded() ) { JPH::Body &body = lock.GetBody(); if ( velocity ) body.SetLinearVelocityClamped( body.GetLinearVelocity() + SourceToJolt::Distance( *velocity ) ); if ( angularVelocity ) body.SetAngularVelocityClamped( body.GetAngularVelocity() + SourceToJolt::AngularImpulse( *angularVelocity ) ); if ( !body.IsActive() && ( !body.GetLinearVelocity().IsNearZero() || !body.GetAngularVelocity().IsNearZero() ) ) m_pPhysicsSystem->GetBodyInterfaceNoLock().ActivateBodies( &m_pBody->GetID(), 1 ); } } void JoltPhysicsObject::GetVelocityAtPoint( const Vector &worldPosition, Vector *pVelocity ) const { VJoltAssert( pVelocity ); *pVelocity = JoltToSource::Distance( m_pPhysicsSystem->GetBodyInterfaceNoLock().GetPointVelocity( m_pBody->GetID(), SourceToJolt::Distance( worldPosition ) ) ); } void JoltPhysicsObject::GetImplicitVelocity( Vector *velocity, AngularImpulse *angularVelocity ) const { Log_Stub( LOG_VJolt ); if ( velocity ) *velocity = vec3_origin; if ( angularVelocity ) *angularVelocity = vec3_origin; } void JoltPhysicsObject::LocalToWorld( Vector *worldPosition, const Vector &localPosition ) const { matrix3x4_t matrix; GetPositionMatrix( &matrix ); // Copy in case src == dest VectorTransform( Vector( localPosition ), matrix, *worldPosition ); } void JoltPhysicsObject::WorldToLocal( Vector *localPosition, const Vector &worldPosition ) const { matrix3x4_t matrix; GetPositionMatrix( &matrix ); // Copy in case src == dest VectorITransform( Vector( worldPosition ), matrix, *localPosition ); } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::LocalToWorldVector( Vector *worldVector, const Vector &localVector ) const { matrix3x4_t matrix; GetPositionMatrix( &matrix ); // Copy in case src == dest VectorRotate( Vector( localVector ), matrix, *worldVector ); } void JoltPhysicsObject::WorldToLocalVector( Vector *localVector, const Vector &worldVector ) const { matrix3x4_t matrix; GetPositionMatrix( &matrix ); // Copy in case src == dest VectorIRotate( Vector( worldVector ), matrix, *localVector ); } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::ApplyForceCenter( const Vector &forceVector ) { if ( !IsMoveable() ) return; JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); bodyInterface.AddImpulse( m_pBody->GetID(), SourceToJolt::Distance( forceVector ) ); } void JoltPhysicsObject::ApplyForceOffset( const Vector &forceVector, const Vector &worldPosition ) { if ( !IsMoveable() ) return; JPH::Vec3 impulse = SourceToJolt::Distance( forceVector ); JPH::Vec3 point = SourceToJolt::Distance( worldPosition ); JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); bodyInterface.AddImpulse( m_pBody->GetID(), impulse, point ); } void JoltPhysicsObject::ApplyTorqueCenter( const AngularImpulse &torque ) { if ( !IsMoveable() ) return; // Do this longer method do set velocity and angular velocity // in the same lock. const JPH::BodyLockInterfaceNoLock &bodyLockInterface = m_pPhysicsSystem->GetBodyLockInterfaceNoLock(); JPH::BodyLockWrite lock( bodyLockInterface, m_pBody->GetID() ); if ( lock.Succeeded() ) { JPH::Body &body = lock.GetBody(); body.AddAngularImpulse( SourceToJolt::AngularImpulse( torque ) ); if ( !body.IsActive() && ( !body.GetAngularVelocity().IsNearZero() ) ) m_pPhysicsSystem->GetBodyInterfaceNoLock().ActivateBodies( &m_pBody->GetID(), 1 ); } } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::CalculateForceOffset( const Vector &forceVector, const Vector &worldPosition, Vector *centerForce, AngularImpulse *centerTorque ) const { JPH::Vec3 pos = SourceToJolt::Distance( worldPosition ); JPH::Vec3 force = SourceToJolt::Distance( forceVector ); JPH::Vec3 com = pos - m_pBody->GetCenterOfMassPosition(); JPH::Vec3 cross = com.Cross( force ); if ( centerForce ) *centerForce = JoltToSource::Distance( force ); if ( centerTorque ) *centerTorque = JoltToSource::AngularImpulse( cross ); } void JoltPhysicsObject::CalculateVelocityOffset( const Vector &forceVector, const Vector &worldPosition, Vector *centerVelocity, AngularImpulse *centerAngularVelocity ) const { // Convert force to SI units to multiply by mass for impulse. JPH::Vec3 siForce = SourceToJolt::Distance( forceVector ); if ( centerVelocity ) *centerVelocity = JoltToSource::Distance( siForce * m_flCachedInvMass ); if ( centerAngularVelocity ) { JPH::Vec3 siPosition = SourceToJolt::Distance( worldPosition ); // TODO(Josh): Check this math. JPH::Vec3 siRelativePosition = siPosition - m_pBody->GetCenterOfMassPosition(); JPH::Vec3 cross = siRelativePosition.Cross( siForce ); cross = m_pBody->GetWorldTransform().Transposed3x3() * cross; *centerAngularVelocity = JoltToSource::AngularImpulse( cross ); } } float JoltPhysicsObject::CalculateLinearDrag( const Vector &unitDirection ) const { Log_Stub( LOG_VJolt ); return 0.0f; } float JoltPhysicsObject::CalculateAngularDrag( const Vector &objectSpaceRotationAxis ) const { Log_Stub( LOG_VJolt ); return 0.0f; } //------------------------------------------------------------------------------------------------- bool JoltPhysicsObject::GetContactPoint( Vector *contactPoint, IPhysicsObject **contactObject ) const { Log_Stub( LOG_VJolt ); return false; } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::SetShadow( float maxSpeed, float maxAngularSpeed, bool allowPhysicsMovement, bool allowPhysicsRotation ) { if ( m_pShadowController ) { m_pShadowController->MaxSpeed( maxSpeed, maxAngularSpeed ); } else { #if 1 m_bShadowTemporarilyDisableGravity = false; #endif m_pShadowController = static_cast( m_pEnvironment->CreateShadowController( this, allowPhysicsMovement, allowPhysicsRotation ) ); m_pShadowController->MaxSpeed( maxSpeed, maxAngularSpeed ); } } void JoltPhysicsObject::UpdateShadow( const Vector &targetPosition, const QAngle &targetAngles, bool tempDisableGravity, float timeOffset ) { if ( m_pShadowController ) { #if 1 if ( tempDisableGravity != m_bShadowTemporarilyDisableGravity ) { m_bShadowTemporarilyDisableGravity = tempDisableGravity; if ( !m_pShadowController || m_pShadowController->AllowsTranslation() ) EnableGravity( !m_bShadowTemporarilyDisableGravity ); } #endif m_pShadowController->Update( targetPosition, targetAngles, timeOffset ); } } //------------------------------------------------------------------------------------------------- int JoltPhysicsObject::GetShadowPosition( Vector *position, QAngle *angles ) const { // Josh: // If func_door_rotating, func_tracktrains are moving slowly, // check this function out... // // Interpolates to the next timestep float flTimeStep = m_pEnvironment->GetSimulationTimestep(); JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); JPH::Vec3 joltPosition, joltLinearVelocity, joltAngularVelocity; JPH::Quat joltRotation; bodyInterface.GetPositionAndRotation( m_pBody->GetID(), joltPosition, joltRotation ); bodyInterface.GetLinearAndAngularVelocity( m_pBody->GetID(), joltLinearVelocity, joltAngularVelocity ); if ( position ) { *position = JoltToSource::Distance( joltPosition + ( joltLinearVelocity * flTimeStep ) ); } if ( angles ) { // From Jolt's AddRotationStep. JPH::Vec3 joltAngularVelocityTimesDT = joltAngularVelocity * flTimeStep; float len = joltAngularVelocityTimesDT.Length(); JPH::Quat newQuat = joltRotation; if ( len > 1.0e-6f ) newQuat = ( JPH::Quat::sRotation( joltAngularVelocityTimesDT / len, len ) * joltRotation ).Normalized(); *angles = JoltToSource::Angle( newQuat ); } return 1; } IPhysicsShadowController *JoltPhysicsObject::GetShadowController() const { return m_pShadowController; } void JoltPhysicsObject::RemoveShadowController() { if ( m_pShadowController ) { m_pEnvironment->DestroyShadowController(m_pShadowController); m_pShadowController = nullptr; } } static void ComputeController( JPH::Vec3 &vecCurrentVelocity, const JPH::Vec3 &vecDeltaPos, float flMaxSpeed, float flMaxDampSpeed, float flScaleDelta, float flDamping, JPH::Vec3 *pOutImpulse = nullptr ) { float flCurrentSpeedSq = vecCurrentVelocity.LengthSq(); if ( flCurrentSpeedSq < 1e-6f ) { vecCurrentVelocity = JPH::Vec3::sZero(); } else if ( flMaxDampSpeed > 0 ) { JPH::Vec3 vecAccelDampening = vecCurrentVelocity * -flDamping; float flSpeed = sqrtf( flCurrentSpeedSq ) * fabsf( flDamping ); if ( flSpeed > flMaxDampSpeed ) { flSpeed = flMaxDampSpeed / flSpeed; vecAccelDampening *= flSpeed; } vecCurrentVelocity += vecAccelDampening; } JPH::Vec3 vecAcceleration = JPH::Vec3::sZero(); if ( flMaxSpeed > 0.0f ) { vecAcceleration = vecDeltaPos * flScaleDelta; float flSpeed = vecDeltaPos.Length() * flScaleDelta; if ( flSpeed > flMaxSpeed ) { flSpeed = flMaxSpeed / flSpeed; vecAcceleration *= flSpeed; } vecCurrentVelocity += vecAcceleration; } if ( pOutImpulse ) *pOutImpulse = vecAcceleration; } // hlshadowcontrol_params_t but in Jolt space. struct JoltShadowControlParams { JPH::Vec3 TargetPosition; JPH::Quat TargetRotation; JPH::Vec3 LastPosition; JPH::Vec3 LastImpulse; float MaxAngular; float MaxDampAngular; float MaxSpeed; float MaxDampSpeed; float DampFactor; float TeleportDistance; }; static float ComputeShadowController( JoltShadowControlParams ¶ms, JPH::Vec3 &position, JPH::Quat &rotation, JPH::Vec3 &linearVelocity, JPH::Vec3& angularVelocity, float flSecondsToArrival, float flDeltaTime ) { const float flFraction = flSecondsToArrival > 0.0f ? Min( flDeltaTime / flSecondsToArrival, 1.0f ) : 1.0f; flSecondsToArrival = Max( flSecondsToArrival - flDeltaTime, 0.0f ); if ( flFraction <= 0.0f ) return flSecondsToArrival; JPH::Vec3 deltaPosition = params.TargetPosition - position; if ( params.TeleportDistance > 0.0f && deltaPosition.LengthSq() > Square( params.TeleportDistance ) ) { position = params.TargetPosition; rotation = params.TargetRotation; deltaPosition = JPH::Vec3::sZero(); } const float flInvDeltaTime = 1.0f / flDeltaTime; const float flFractionTime = flFraction * flInvDeltaTime; ComputeController( linearVelocity, deltaPosition, params.MaxSpeed, params.MaxDampSpeed, flFractionTime, params.DampFactor, ¶ms.LastImpulse); params.LastPosition = position + linearVelocity * flDeltaTime; JPH::Quat deltaRotation = params.TargetRotation * rotation.Inversed(); JPH::Vec3 axis; float angle; deltaRotation.GetAxisAngle( axis, angle ); JPH::Vec3 deltaAngles = axis * angle; ComputeController( angularVelocity, deltaAngles, params.MaxAngular, params.MaxDampAngular, flFractionTime, params.DampFactor ); return flSecondsToArrival; } float JoltPhysicsObject::ComputeShadowControl( const hlshadowcontrol_params_t ¶ms, float flSecondsToArrival, float flDeltaTime ) { JoltShadowControlParams joltParams = { .TargetPosition = SourceToJolt::Distance( params.targetPosition ), .TargetRotation = SourceToJolt::Angle( params.targetRotation ), .MaxAngular = SourceToJolt::Angle( params.maxAngular ), .MaxDampAngular = SourceToJolt::Angle( params.maxDampAngular ), .MaxSpeed = SourceToJolt::Distance( params.maxSpeed ), .MaxDampSpeed = SourceToJolt::Distance( params.maxDampSpeed ), .DampFactor = params.dampFactor, .TeleportDistance = SourceToJolt::Distance( params.teleportDistance ), }; JPH::BodyInterface& bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); JPH::Vec3 position; JPH::Quat rotation; bodyInterface.GetPositionAndRotation( m_pBody->GetID(), position, rotation ); JPH::Vec3 linearVelocity; JPH::Vec3 angularVelocity; bodyInterface.GetLinearAndAngularVelocity( m_pBody->GetID(), linearVelocity, angularVelocity ); JPH::Vec3 scratchPosition = position; JPH::Quat scratchRotation = rotation; JPH::Vec3 scratchLinearVelocity = linearVelocity; JPH::Vec3 scratchAngularVelocity = angularVelocity; float flNewSecondsToArrival = ComputeShadowController( joltParams, scratchPosition, scratchRotation, scratchLinearVelocity, scratchAngularVelocity, flSecondsToArrival, flDeltaTime ); if ( scratchPosition != position || scratchRotation != rotation ) bodyInterface.SetPositionAndRotation( m_pBody->GetID(), scratchPosition, scratchRotation, JPH::EActivation::Activate ); if ( scratchLinearVelocity != linearVelocity || scratchAngularVelocity != angularVelocity ) bodyInterface.SetLinearAndAngularVelocity( m_pBody->GetID(), scratchLinearVelocity, scratchAngularVelocity ); return flNewSecondsToArrival; } //------------------------------------------------------------------------------------------------- const CPhysCollide *JoltPhysicsObject::GetCollide() const { const CPhysCollide *pCollide = CPhysCollide::FromShape( m_pBody->GetShape() ); return pCollide; } const char *JoltPhysicsObject::GetName() const { return m_pName; } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::BecomeTrigger() { m_pBody->SetIsSensor( true ); } void JoltPhysicsObject::RemoveTrigger() { if ( !IsTrigger() ) return; // Josh: // All this logic below is to trigger ObjectLeaveTrigger // when the trigger is deleted. IPhysicsCollisionEvent *pEventListener = m_pEnvironment->GetContactListener()->GetGameListener(); if ( pEventListener && IsTrigger() ) { class SourceTriggerCollector : public JPH::CollideShapeCollector { public: SourceTriggerCollector( JPH::PhysicsSystem *pPhysicsSystem, IPhysicsCollisionEvent *pGameListener, JoltPhysicsObject *pTrigger ) : m_pPhysicsSystem( pPhysicsSystem ) , m_pGameListener ( pGameListener ) , m_pTrigger ( pTrigger ) {} void AddHit( const ResultType &inResult ) override { const JPH::BodyID inBodyID = inResult.mBodyID2; JPH::BodyLockWrite lock( m_pPhysicsSystem->GetBodyLockInterface(), inBodyID ); JPH::Body &body = lock.GetBody(); JoltPhysicsObject *pObject = reinterpret_cast( body.GetUserData() ); if ( !pObject ) return; m_pGameListener->ObjectLeaveTrigger( m_pTrigger, pObject ); } private: JPH::PhysicsSystem *m_pPhysicsSystem; IPhysicsCollisionEvent *m_pGameListener; JoltPhysicsObject *m_pTrigger; }; SourceTriggerCollector collector( m_pPhysicsSystem, pEventListener, this ); JPH::IgnoreSingleBodyFilter body_filter( GetBodyID() ); JPH::CollideShapeSettings collideSettings; collideSettings.mActiveEdgeMode = JPH::EActiveEdgeMode::CollideWithAll; JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); JPH::Mat44 queryTransform = bodyInterface.GetCenterOfMassTransform( GetBodyID() ); const JPH::Shape *pShape = GetCollide()->ToShape(); m_pPhysicsSystem->GetNarrowPhaseQueryNoLock().CollideShape( pShape, JPH::Vec3::sReplicate( 1.0f ), queryTransform, collideSettings, JPH::Vec3::sZero(), collector, JPH::SpecifiedBroadPhaseLayerFilter( BroadPhaseLayers::MOVING ), JPH::SpecifiedObjectLayerFilter( Layers::MOVING ), body_filter ); } m_pBody->SetIsSensor( false ); } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::BecomeHinged( int localAxis ) { Log_Stub( LOG_VJolt ); } void JoltPhysicsObject::RemoveHinged() { Log_Stub( LOG_VJolt ); } //------------------------------------------------------------------------------------------------- IPhysicsFrictionSnapshot *JoltPhysicsObject::CreateFrictionSnapshot() { return new JoltPhysicsFrictionSnapshot; } void JoltPhysicsObject::DestroyFrictionSnapshot( IPhysicsFrictionSnapshot *pSnapshot ) { delete pSnapshot; } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::OutputDebugInfo() const { Log_Stub( LOG_VJolt ); } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::SetUseAlternateGravity( bool bSet ) { Log_Stub( LOG_VJolt ); } void JoltPhysicsObject::SetCollisionHints( uint32 collisionHints ) { m_collisionHints = collisionHints; UpdateLayer(); } uint32 JoltPhysicsObject::GetCollisionHints() const { return m_collisionHints; } //------------------------------------------------------------------------------------------------- IPredictedPhysicsObject *JoltPhysicsObject::GetPredictedInterface() const { Log_Stub( LOG_VJolt ); return nullptr; } void JoltPhysicsObject::SyncWith( IPhysicsObject *pOther ) { if ( this->IsCollisionEnabled() != pOther->IsCollisionEnabled() ) EnableCollisions( pOther->IsCollisionEnabled() ); if ( this->IsGravityEnabled() != pOther->IsGravityEnabled() ) EnableGravity( pOther->IsGravityEnabled() ); if ( this->IsDragEnabled() != pOther->IsDragEnabled() ) EnableDrag(pOther->IsDragEnabled() ); if ( this->IsMotionEnabled() != pOther->IsMotionEnabled() ) EnableMotion(pOther->IsMotionEnabled() ); } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::UpdateEnvironment( JoltPhysicsEnvironment *pEnvironment ) { m_pEnvironment = pEnvironment; m_pPhysicsSystem = pEnvironment->GetPhysicsSystem(); } void JoltPhysicsObject::AddDestroyedListener( IJoltObjectDestroyedListener *pListener ) { m_destroyedListeners.AddToTail( pListener ); } void JoltPhysicsObject::RemoveDestroyedListener( IJoltObjectDestroyedListener *pListener ) { m_destroyedListeners.FindAndRemove( pListener ); } void JoltPhysicsObject::AddToPosition( JPH::Vec3Arg addPos ) { const JPH::BodyLockInterfaceNoLock &bodyLockInterface = m_pPhysicsSystem->GetBodyLockInterfaceNoLock(); JPH::BodyLockWrite lock( bodyLockInterface, m_pBody->GetID() ); if ( lock.Succeeded() ) { JPH::Body &body = lock.GetBody(); JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); bodyInterface.SetPosition( m_pBody->GetID(), body.GetPosition() + addPos, JPH::EActivation::DontActivate ); } } void JoltPhysicsObject::SetPosition( const Vector &worldPosition ) { JPH::Vec3 joltPosition = SourceToJolt::Distance( worldPosition ); JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); bodyInterface.SetPosition( m_pBody->GetID(), joltPosition, JPH::EActivation::DontActivate ); } void JoltPhysicsObject::AddVelocity( const Vector &worldPosition ) { JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); bodyInterface.AddLinearVelocity( m_pBody->GetID(), SourceToJolt::Distance( worldPosition ) ); } Vector JoltPhysicsObject::GetVelocity() { JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); return JoltToSource::Distance( bodyInterface.GetLinearVelocity( m_pBody->GetID() ) ); } void JoltPhysicsObject::CalculateBuoyancy() { if ( m_flVolume != 0.0f ) { float flVolume = SourceToJolt::Volume( Max( m_flVolume, 5.0f ) ); float flDensity = m_flCachedMass / flVolume; m_flBuoyancyRatio = flDensity / m_flMaterialDensity; } else { m_flBuoyancyRatio = 1.0f; } } float JoltPhysicsObject::GetMaterialDensity() const { return m_flMaterialDensity; } float JoltPhysicsObject::GetBuoyancyRatio() const { return m_flBuoyancyRatio; } bool JoltPhysicsObject::IsControlledByGame() const { if ( m_pShadowController && !m_pShadowController->IsPhysicallyControlled() ) return true; if ( m_callbackFlags & CALLBACK_IS_PLAYER_CONTROLLER ) return true; return false; } void JoltPhysicsObject::SaveObjectState( JPH::StateRecorder &recorder ) { m_pBody->SaveState( recorder ); // Josh: Do not write m_pGameData, as this is passed in, in UnserializeObjectFromBuffer. //recorder.Write( m_pGameData ); recorder.Write( m_gameFlags ); recorder.Write( m_gameIndex ); recorder.Write( m_callbackFlags ); recorder.Write( m_bStatic ); recorder.Write( m_bPinned ); recorder.Write( m_materialIndex ); recorder.Write( m_contents ); recorder.Write( m_flCachedMass ); recorder.Write( m_flCachedInvMass ); recorder.Write( m_bCachedCollisionEnabled ); recorder.Write( m_flMaterialDensity ); recorder.Write( m_flBuoyancyRatio ); recorder.Write( m_flVolume ); recorder.Write( m_GameMaterial ); // Josh: // In regular VPhysics, shadows are serialized but then forced to never be read. // Lets just not bother serializing these. } void JoltPhysicsObject::RestoreObjectState( JPH::StateRecorder &recorder ) { // Restore the body's state. m_pBody->RestoreState( recorder ); // Set the body's userdata as ourselves m_pBody->SetUserData( reinterpret_cast( this ) ); // Josh: Do not read m_pGameData, as this is not serialized. //recorder.Read( m_pGameData ); recorder.Read( m_gameFlags ); recorder.Read( m_gameIndex ); recorder.Read( m_callbackFlags ); recorder.Read( m_bStatic ); recorder.Read( m_bPinned ); recorder.Read( m_materialIndex ); recorder.Read( m_contents ); recorder.Read( m_flCachedMass ); recorder.Read( m_flCachedInvMass ); recorder.Read( m_bCachedCollisionEnabled ); recorder.Read( m_flMaterialDensity ); recorder.Read( m_flBuoyancyRatio ); recorder.Read( m_flVolume ); recorder.Read( m_GameMaterial ); // Recompute states. UpdateMaterialProperties(); UpdateLayer(); } //------------------------------------------------------------------------------------------------- void JoltPhysicsObject::UpdateMaterialProperties() { const surfacedata_t *pSurface = JoltPhysicsSurfaceProps::GetInstance().GetSurfaceData( m_materialIndex ); m_pBody->SetRestitution( pSurface->physics.elasticity ); m_pBody->SetFriction( pSurface->physics.friction ); m_flMaterialDensity = pSurface->physics.density; m_GameMaterial = pSurface->game.material; CalculateBuoyancy(); } void JoltPhysicsObject::UpdateLayer() { JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); const bool bCollisionsEnabled = m_bCachedCollisionEnabled; const bool bStatic = IsStatic(); const bool bPinned = m_bPinned; const bool bDebris = m_collisionHints & COLLISION_HINT_DEBRIS; const bool bStaticSolid = m_collisionHints & COLLISION_HINT_STATICSOLID; // Update motion type if not made as a complete solid. if ( !bStatic && !IsControlledByGame() ) { bool bStaticMotionType = bStaticSolid || bPinned; // If we are transfering to being static, and we were active // add us to a list of bodies on the environment so we can be included in // GetActiveObjects for the next step. // This way the game can correctly update the transforms on the game side // when move -> wake -> become pinned happens. if ( bStaticMotionType && m_pBody->IsActive() ) m_pEnvironment->AddDirtyStaticBody( m_pBody->GetID() ); else if ( !bStaticMotionType ) m_pEnvironment->RemoveDirtyStaticBody( m_pBody->GetID() ); bodyInterface.SetMotionType( m_pBody->GetID(), bStaticMotionType ? JPH::EMotionType::Static : JPH::EMotionType::Dynamic, JPH::EActivation::Activate ); } // Update layer uint8 layer = Layers::MOVING; if ( bDebris ) layer = Layers::DEBRIS; if ( bStatic || bStaticSolid ) layer = Layers::NON_MOVING_WORLD; else if ( bPinned ) layer = Layers::NON_MOVING_OBJECT; if ( !bCollisionsEnabled ) layer = Layers::NO_COLLIDE; bodyInterface.SetObjectLayer( m_pBody->GetID(), layer ); }