vphysics_jolt/vphysics_jolt/vjolt_controller_vehicle.cpp

550 lines
20 KiB
C++

#include "cbase.h"
#include "vjolt_layers.h"
#include "vjolt_controller_vehicle.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//------------------------------------------------------------------------------------------------
static ConVar vjolt_vehicle_wheel_debug( "vjolt_vehicle_wheel_debug", "0", FCVAR_CHEAT );
static ConVar vjolt_vehicle_throttle_opposition_limit( "vjolt_vehicle_throttle_opposition_limit", "5", FCVAR_NONE,
"Below what speed should we be attempting to drive/climb with handbrake on to avoid falling down." );
//------------------------------------------------------------------------------------------------
static const JPH::Vec3 VehicleUpVector = JPH::Vec3( 0, 0, 1 );
static const JPH::Vec3 VehicleForwardVector = JPH::Vec3( 0, 1, 0 );
static const char *VehicleTypeToName( unsigned int VehicleType )
{
switch ( VehicleType )
{
case VEHICLE_TYPE_CAR_WHEELS: return "Car Wheels";
case VEHICLE_TYPE_CAR_RAYCAST: return "Car Raycast";
case VEHICLE_TYPE_JETSKI_RAYCAST: return "Jetski Raycast";
case VEHICLE_TYPE_AIRBOAT_RAYCAST: return "Airboat Raycast";
default: return "Unknown";
}
}
JPH::Ref< JPH::VehicleCollisionTester > CreateVehicleCollisionTester( unsigned int VehicleType, float LargestWheelRadius )
{
switch ( VehicleType )
{
default:
Log_Warning( LOG_VJolt, "Don't know how to make vehicle type: %s (%u).\n", VehicleTypeToName( VehicleType ), VehicleType );
[[ fallthrough ]];
case VEHICLE_TYPE_CAR_WHEELS:
return new JPH::VehicleCollisionTesterCastSphere( Layers::MOVING, LargestWheelRadius, VehicleUpVector );
}
}
//------------------------------------------------------------------------------------------------
JoltPhysicsVehicleController::JoltPhysicsVehicleController( JoltPhysicsEnvironment* pEnvironment, JPH::PhysicsSystem* pPhysicsSystem, JoltPhysicsObject* pVehicleBodyObject, const vehicleparams_t& params, unsigned int nVehicleType, IPhysicsGameTrace* pGameTrace )
: m_pEnvironment( pEnvironment )
, m_pPhysicsSystem( pPhysicsSystem )
, m_pCarBodyObject( pVehicleBodyObject )
, m_VehicleType( nVehicleType )
, m_VehicleParams( params )
{
JPH::VehicleConstraintSettings vehicle;
vehicle.mUp = VehicleUpVector;
vehicle.mForward = VehicleForwardVector;
vehicle.mDrawConstraintSize = 0.1f;
CreateWheels( vehicle );
vehicle.mController = CreateVehicleController();
m_Tester = CreateVehicleCollisionTester( nVehicleType, m_InternalState.LargestWheelRadius );
m_pCarBodyObject->AddDestroyedListener( this );
m_VehicleConstraint = new JPH::VehicleConstraint( *m_pCarBodyObject->GetBody(), vehicle );
m_pPhysicsSystem->AddConstraint( m_VehicleConstraint );
m_pPhysicsSystem->AddStepListener( m_VehicleConstraint );
}
JoltPhysicsVehicleController::~JoltPhysicsVehicleController()
{
DetachObject();
for ( auto &wheel : m_Wheels )
m_pEnvironment->DestroyObject( wheel.pObject );
m_Wheels.clear();
}
//------------------------------------------------------------------------------------------------
void JoltPhysicsVehicleController::Update( float dt, vehicle_controlparams_t &controls )
{
m_ControlParams = controls;
UpdateBooster( dt );
HandleBoostKey();
}
const vehicle_operatingparams_t &JoltPhysicsVehicleController::GetOperatingParams()
{
return m_OperatingParams;
}
const vehicleparams_t &JoltPhysicsVehicleController::GetVehicleParams()
{
return m_VehicleParams;
}
vehicleparams_t &JoltPhysicsVehicleController::GetVehicleParamsForChange()
{
return m_VehicleParams;
}
float JoltPhysicsVehicleController::UpdateBooster( float dt )
{
m_InternalState.BoostDelay = Max( m_InternalState.BoostDelay - dt, 0.0f );
m_InternalState.BoosterRemainingTime = Max( m_InternalState.BoosterRemainingTime - dt, 0.0f );
return m_InternalState.BoostDelay;
}
int JoltPhysicsVehicleController::GetWheelCount()
{
return int( m_Wheels.size() );
}
IPhysicsObject *JoltPhysicsVehicleController::GetWheel( int index )
{
if ( index >= int( m_Wheels.size() ) )
return nullptr;
return m_Wheels[ index ].pObject;
}
bool JoltPhysicsVehicleController::GetWheelContactPoint( int index, Vector *pContactPoint, int *pSurfaceProps )
{
if ( index < int( m_Wheels.size() ) && m_VehicleConstraint->GetWheels()[ index ]->HasContact() )
{
if ( pContactPoint )
*pContactPoint = JoltToSource::Distance( m_VehicleConstraint->GetWheels()[ index ]->GetContactPosition() );
// TODO(Josh): This!
if ( pSurfaceProps )
*pSurfaceProps = 0;
return true;
}
else
{
if ( pContactPoint )
*pContactPoint = vec3_origin;
if ( pSurfaceProps )
*pSurfaceProps = 0;
return false;
}
}
void JoltPhysicsVehicleController::SetSpringLength( int wheelIndex, float length )
{
}
void JoltPhysicsVehicleController::SetWheelFriction( int wheelIndex, float friction )
{
}
//-------------------------------------------------------------------------------------------------
void JoltPhysicsVehicleController::OnVehicleEnter()
{
// Undo any damping we may have set to slow the boat when
// we got out.
if ( m_VehicleType == VEHICLE_TYPE_AIRBOAT_RAYCAST )
{
float flDampSpeed = 0.0f;
float flDampRotSpeed = 0.0f;
m_pCarBodyObject->SetDamping( &flDampSpeed, &flDampRotSpeed );
}
}
void JoltPhysicsVehicleController::OnVehicleExit()
{
// If we are an airboat, set a bunch of damping to slow us down.
if ( m_VehicleType == VEHICLE_TYPE_AIRBOAT_RAYCAST )
{
float flDampSpeed = 1.0f;
float flDampRotSpeed = 1.0f;
m_pCarBodyObject->SetDamping( &flDampSpeed, &flDampRotSpeed );
}
SetEngineDisabled( false );
}
//-------------------------------------------------------------------------------------------------
void JoltPhysicsVehicleController::SetEngineDisabled( bool bDisable )
{
m_InternalState.EngineDisabled = bDisable;
}
bool JoltPhysicsVehicleController::IsEngineDisabled()
{
return m_InternalState.EngineDisabled;
}
void JoltPhysicsVehicleController::GetCarSystemDebugData( vehicle_debugcarsystem_t &debugCarSystem )
{
}
void JoltPhysicsVehicleController::VehicleDataReload()
{
}
//-------------------------------------------------------------------------------------------------
float JoltPhysicsVehicleController::GetSpeed()
{
const Vector orientation = GetColumn( GetBodyMatrix(), MatrixAxis::Left );
return orientation.Dot( m_pCarBodyObject->GetVelocity() );
}
//------------------------------------------------------------------------------------------------
void JoltPhysicsVehicleController::HandleBoostKey()
{
// Handle triggering boosting if the key is pressed and we aren't currently boosting or in cooldown.
if ( m_ControlParams.boost && !m_InternalState.BoostDelay && !m_InternalState.BoosterRemainingTime )
{
m_InternalState.BoosterRemainingTime = m_VehicleParams.engine.boostDuration;
m_InternalState.BoostDelay = m_VehicleParams.engine.boostDuration + m_VehicleParams.engine.boostDelay;
}
}
void JoltPhysicsVehicleController::HandleBoostDecay()
{
// Decay the boost time if we are currently boosting or have a delay.
if ( m_VehicleParams.engine.boostDuration || m_VehicleParams.engine.boostDelay )
{
m_OperatingParams.boostTimeLeft = m_InternalState.BoostDelay
? 100.0f - ( 100.0f * ( m_InternalState.BoostDelay / ( m_VehicleParams.engine.boostDuration + m_VehicleParams.engine.boostDelay ) ) )
: 100.0f;
}
}
//------------------------------------------------------------------------------------------------
void JoltPhysicsVehicleController::OnPreSimulate( float flDeltaTime )
{
JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock();
// With any user input, assure that the car is active
if ( m_ControlParams.steering != 0.0f || m_ControlParams.throttle != 0.0f || m_ControlParams.brake != 0.0f || m_ControlParams.handbrake )
bodyInterface.ActivateBody( m_pCarBodyObject->GetBodyID() );
bool bHandbrake = m_ControlParams.handbrake;
// Don't throttle when holding handbrake (like Source)
float flThrottle = bHandbrake ? 0.0f : m_ControlParams.throttle;
// Apply a little brake without throttle to stop the vehicle from coasting (like Source).
const bool bCoasting = flThrottle == 0.0f && m_ControlParams.brake == 0.0f && !bHandbrake;
const float flBrake = bCoasting ? 0.1f : m_ControlParams.brake;
const float ThrottleOpositionSpeed = vjolt_vehicle_throttle_opposition_limit.GetFloat();
// Enable the handbrake when going at low speeds to avoid slipping when going up hill.
if ( ( flThrottle < 0.0f && m_OperatingParams.speed > ThrottleOpositionSpeed ) ||
( flThrottle > 0.0f && m_OperatingParams.speed < -ThrottleOpositionSpeed ) )
bHandbrake = true;
// Are we boosting?
float flTotalTorqueMultiplier = 1.0f;
if ( m_InternalState.BoosterRemainingTime != 0.0f )
{
GetWheeledVehicleController()->GetEngine().SetCurrentRPM(m_VehicleParams.engine.maxRPM);
// Slam the throttle to 1, neeeowm!
m_ControlParams.throttle = 1.0f;
flThrottle = 1.0f;
const float flSpeedFactor = RemapValClamped( fabsf( m_OperatingParams.speed ), 0, m_VehicleParams.engine.maxSpeed, 0.1f, 1.0f );
const float flTurnFactor = 1.0f - ( fabsf( m_ControlParams.steering ) * 0.95f );
// Josh: * 2 as the original torque stuff in Source was based around 0.5 being the max, and 1.0 being boost.
const float flDampedBoost = 2.0f * m_VehicleParams.engine.boostForce * flSpeedFactor * flTurnFactor;
if ( flDampedBoost > flTotalTorqueMultiplier )
flTotalTorqueMultiplier = flDampedBoost;
}
// Update the torque factors as we may be boosting and be > 1.
// TODO(Josh): More than 2 wheels per axle.
VJoltAssert( m_VehicleParams.wheelsPerAxle == 2 );
for ( int i = 0; i < m_VehicleParams.axleCount; i++ )
GetWheeledVehicleController()->GetDifferentials()[i].mEngineTorqueRatio = flTotalTorqueMultiplier * m_VehicleParams.axles[i].torqueFactor;
// Pass the input on to the constraint
GetWheeledVehicleController()->SetDriverInput( flThrottle, m_ControlParams.steering, flBrake, bHandbrake ? 1.0f : 0.0f );
// Set the collision tester
m_VehicleConstraint->SetVehicleCollisionTester( m_Tester );
}
//------------------------------------------------------------------------------------------------
void JoltPhysicsVehicleController::OnPostSimulate( float flDeltaTime )
{
// Draw our wheels (this needs to be done in the pre update since we draw the bodies too in the state before the step)
float flSteeringAngle = 0.0f;
m_OperatingParams.wheelsInContact = 0;
m_OperatingParams.wheelsNotInContact = 0;
for ( int w = 0; w < GetWheelCount(); w++ )
{
const JPH::WheelSettings *settings = m_VehicleConstraint->GetWheels()[w]->GetSettings();
// The cyclinder we draw is aligned with Y so we specify that as rotational axis
JPH::Mat44 wheelTransform = m_VehicleConstraint->GetWheelWorldTransform( w, JPH::Vec3( 1, 0, 0 ), JPH::Vec3( 0, 0, 1 ) );
// Find our greatest steering angle.
float flWheelSteeringAngle = JoltToSource::Angle( m_VehicleConstraint->GetWheels()[w]->GetSteerAngle() );
if ( fabsf( flWheelSteeringAngle ) > fabsf( flSteeringAngle ) )
flSteeringAngle = flWheelSteeringAngle;
Vector newPos = JoltToSource::Distance( wheelTransform.GetTranslation() );
// TODO(Josh): This triggers JPH_ASSERT(mCol[3] == Vec4(0, 0, 0, 1));
// what to do about that?..
// We just want the local rotation, and this seems to work (?)
QAngle newQuat = JoltToSource::Angle( wheelTransform.GetQuaternion() );
m_Wheels[ w ].pObject->EnableCollisions( false );
// Set dummy wheel object pos/angles so the game code can update pose positions for wheels.
m_Wheels[ w ].pObject->SetPosition( newPos, newQuat, true );
// Wake it up so that the game bothers to do pose positions.
m_Wheels[ w ].pObject->Wake();
if ( m_VehicleConstraint->GetWheels()[w]->HasContact() )
m_OperatingParams.wheelsInContact++;
else
m_OperatingParams.wheelsNotInContact++;
IVJoltDebugOverlay *pDebugOverlay = JoltPhysicsInterface::GetInstance().GetDebugOverlay();
if ( vjolt_vehicle_wheel_debug.GetBool() && pDebugOverlay )
{
const Vector vecWheelPos = JoltToSource::Distance( wheelTransform.GetTranslation() );
const Vector vecWheelSize = JoltToSource::Distance( JPH::Vec3( settings->mWidth / 2.0f, settings->mRadius, settings->mRadius ) );
pDebugOverlay->AddBoxOverlay(
vecWheelPos,
-vecWheelSize, vecWheelSize,
newQuat,
255, 0, 255, 100,
-1.0f );
}
}
m_OperatingParams.gear = GetWheeledVehicleController()->GetTransmission().GetCurrentGear();
m_OperatingParams.engineRPM = GetWheeledVehicleController()->GetEngine().GetCurrentRPM();
m_OperatingParams.speed = GetSpeed();
m_OperatingParams.steeringAngle = -flSteeringAngle;
m_OperatingParams.boostDelay = m_InternalState.BoostDelay;
HandleBoostDecay();
}
//------------------------------------------------------------------------------------------------
void JoltPhysicsVehicleController::CreateWheel( JPH::VehicleConstraintSettings &vehicleSettings, matrix3x4_t& bodyMatrix, int axleIdx, int wheelIdx )
{
const vehicle_axleparams_t &axle = m_VehicleParams.axles[ axleIdx ];
const Vector wheelPositionLocal = axle.offset +
( ( wheelIdx % 2 == 1 ) ? axle.wheelOffset : -axle.wheelOffset );
Vector wheelPositionWorld;
VectorTransform( wheelPositionLocal, bodyMatrix, wheelPositionWorld );
// Josh: Good enough heuristic.
const float wheelRadius = axle.wheels.radius;
const float wheelWidth = wheelRadius / 2.0f;
// Josh: Area of a cylinder = π.h.r^2
// Using radius in terms of Source units as we pass this to CreateSphereObject.
const float wheelVolume = M_PI * wheelWidth * Cube( wheelRadius );
{
objectparams_t wheelParams =
{
.mass = axle.wheels.mass,
.inertia = axle.wheels.inertia,
.damping = axle.wheels.damping,
.rotdamping = axle.wheels.rotdamping,
.pName = "VehicleWheel",
.pGameData = m_pCarBodyObject->GetGameData(),
.volume = wheelVolume,
};
IPhysicsObject *pWheelObject = m_pEnvironment->CreateSphereObject(
wheelRadius, axle.wheels.materialIndex,
wheelPositionWorld, QAngle(),
&wheelParams, false );
JoltPhysicsObject *pJoltWheelObject = static_cast< JoltPhysicsObject * >( pWheelObject );
pJoltWheelObject->SetGameFlags( m_pCarBodyObject->GetGameFlags() );
pJoltWheelObject->SetCallbackFlags( CALLBACK_IS_VEHICLE_WHEEL );
// Josh: The wheel is a fake object, so disable collisions on it.
pJoltWheelObject->EnableCollisions( false );
m_Wheels.push_back( JoltPhysicsWheel{ .pObject = pJoltWheelObject } );
}
const float steeringAngle = DEG2RAD( Max( m_VehicleParams.steering.degreesSlow, m_VehicleParams.steering.degreesFast ) );
const float additionalLength = SourceToJolt::Distance( axle.wheels.springAdditionalLength );
Vector gravity;
m_pEnvironment->GetGravity( &gravity );
JPH::WheelSettingsWV *wheelSettings = new JPH::WheelSettingsWV;
wheelSettings->mPosition = SourceToJolt::Distance( wheelPositionLocal );
wheelSettings->mDirection = JPH::Vec3( 0, 0, -1 );
wheelSettings->mAngularDamping = axle.wheels.rotdamping;
// TODO(Josh): What about more than 4 wheels?
wheelSettings->mMaxSteerAngle = axleIdx == 0 ? steeringAngle : 0.0f;
wheelSettings->mRadius = SourceToJolt::Distance( axle.wheels.radius );
wheelSettings->mWidth = SourceToJolt::Distance( wheelWidth );
wheelSettings->mInertia = 0.5f * axle.wheels.mass * ( wheelSettings->mRadius * wheelSettings->mRadius );
wheelSettings->mSuspensionMinLength = 0;
wheelSettings->mSuspensionMaxLength = additionalLength;
wheelSettings->mSuspensionDamping = axle.suspension.springDamping;
// Josh:
// so to go from K (Spring Constant) -> freq we do
// sqrtf( K / Mass ) / ( 2.0f * PI )
// but it seems like it already has mass divided in Source so...
// sqrtf( K ) / ( 2.0f * PI )
wheelSettings->mSuspensionFrequency = sqrtf( axle.suspension.springConstant ) / ( 2.0f * M_PI_F );
// Josh: I don't know why but it looks and feels really wrong without this:
// TODO(Josh): Investigate more later, doesn't make much sense.
// May be related to mass of wheel or something.
wheelSettings->mSuspensionFrequency *= M_PI_F;
if ( axle.wheels.frictionScale )
{
wheelSettings->mLateralFriction.AddPoint( 1.0f, axle.wheels.frictionScale );
wheelSettings->mLongitudinalFriction.AddPoint( 1.0f, axle.wheels.frictionScale );
}
// TODO: We may want to update this every pre-simulation to account for changing gravity.
wheelSettings->mMaxBrakeTorque =
0.5f *
SourceToJolt::Distance( gravity.Length() ) *
( m_pCarBodyObject->GetMass() + m_TotalWheelMass ) *
axle.brakeFactor *
SourceToJolt::Distance( axle.wheels.radius );
vehicleSettings.mWheels.push_back( wheelSettings );
m_InternalState.LargestWheelRadius = Max( m_InternalState.LargestWheelRadius, SourceToJolt::Distance( wheelWidth ) );
}
void JoltPhysicsVehicleController::CreateWheels( JPH::VehicleConstraintSettings &vehicleSettings )
{
matrix3x4_t carBodyMtx = GetBodyMatrix();
m_Wheels.reserve( m_VehicleParams.axleCount * m_VehicleParams.wheelsPerAxle );
vehicleSettings.mAntiRollBars.reserve( m_VehicleParams.axleCount );
m_TotalWheelMass = 0.0f;
for ( int axle = 0; axle < m_VehicleParams.axleCount; axle++ )
m_TotalWheelMass += m_VehicleParams.axles[ axle ].wheels.mass * m_VehicleParams.wheelsPerAxle;
for ( int axle = 0; axle < m_VehicleParams.axleCount; axle++ )
{
for ( int wheel = 0; wheel < m_VehicleParams.wheelsPerAxle; wheel++ )
CreateWheel( vehicleSettings, carBodyMtx, axle, wheel );
// TODO(Josh): More than 2 wheels per axle.
VJoltAssert( m_VehicleParams.wheelsPerAxle == 2 );
JPH::VehicleAntiRollBar rollbar;
rollbar.mLeftWheel = ( axle * m_VehicleParams.wheelsPerAxle );
rollbar.mRightWheel = ( axle * m_VehicleParams.wheelsPerAxle ) + 1;
vehicleSettings.mAntiRollBars.push_back( rollbar );
}
}
JPH::WheeledVehicleControllerSettings *JoltPhysicsVehicleController::CreateVehicleController()
{
static constexpr float HorsePowerToWatts = 745.7f;
JPH::WheeledVehicleControllerSettings *pController = new JPH::WheeledVehicleControllerSettings;
// Josh:
// T = ( 745.7 * P ) / ( 2 * PI * ( RPM / 60 ) )
pController->mEngine.mMaxTorque = ( HorsePowerToWatts * m_VehicleParams.engine.horsepower ) / ( 2.0f * M_PI * ( m_VehicleParams.engine.maxRPM / 60.0f ) );
// Josh: Fudge
pController->mEngine.mMinRPM = Max( m_VehicleParams.engine.shiftDownRPM - 300, 0.0f );
pController->mEngine.mMaxRPM = m_VehicleParams.engine.maxRPM;
pController->mEngine.mAngularDamping = 0.0f;
pController->mTransmission.mMode = m_VehicleParams.engine.isAutoTransmission ? JPH::ETransmissionMode::Auto : JPH::ETransmissionMode::Manual;
pController->mTransmission.mGearRatios.clear();
for ( int i = 0; i < m_VehicleParams.engine.gearCount; i++ )
pController->mTransmission.mGearRatios.push_back( m_VehicleParams.engine.gearRatio[ i ] );
pController->mTransmission.mReverseGearRatios.clear();
pController->mTransmission.mReverseGearRatios.push_back( -m_VehicleParams.engine.gearRatio[0] );
pController->mTransmission.mShiftUpRPM = m_VehicleParams.engine.shiftUpRPM;
pController->mTransmission.mShiftDownRPM = m_VehicleParams.engine.shiftDownRPM;
pController->mDifferentials.reserve( m_VehicleParams.axleCount );
for ( int i = 0; i < m_VehicleParams.axleCount; i++ )
{
// TODO(Josh): More than 2 wheels per axle.
VJoltAssert( m_VehicleParams.wheelsPerAxle == 2 );
JPH::VehicleDifferentialSettings differential;
differential.mLeftWheel = ( i * m_VehicleParams.wheelsPerAxle );
differential.mRightWheel = ( i * m_VehicleParams.wheelsPerAxle ) + 1;
differential.mEngineTorqueRatio = m_VehicleParams.axles[ i ].torqueFactor;
pController->mDifferentials.push_back( differential );
}
return pController;
}
JPH::WheeledVehicleController *JoltPhysicsVehicleController::GetWheeledVehicleController()
{
return static_cast<JPH::WheeledVehicleController *>( m_VehicleConstraint->GetController() );
}
//------------------------------------------------------------------------------------------------
matrix3x4_t JoltPhysicsVehicleController::GetBodyMatrix() const
{
matrix3x4_t value;
m_pCarBodyObject->GetPositionMatrix( &value );
return value;
}
//------------------------------------------------------------------------------------------------
void JoltPhysicsVehicleController::OnJoltPhysicsObjectDestroyed( JoltPhysicsObject *pObject )
{
if ( m_pCarBodyObject == pObject )
DetachObject();
}
void JoltPhysicsVehicleController::DetachObject()
{
if ( m_pCarBodyObject )
{
m_pCarBodyObject->RemoveDestroyedListener( this );
// Remove the listeners and constraint now, we can never
// attach to another body.
m_pPhysicsSystem->RemoveConstraint( m_VehicleConstraint );
m_pPhysicsSystem->RemoveStepListener( m_VehicleConstraint );
m_pCarBodyObject = nullptr;
}
}