diff options
Diffstat (limited to 'vphysics/physics_vehicle.cpp')
| -rw-r--r-- | vphysics/physics_vehicle.cpp | 1606 |
1 files changed, 1606 insertions, 0 deletions
diff --git a/vphysics/physics_vehicle.cpp b/vphysics/physics_vehicle.cpp new file mode 100644 index 0000000..1c46798 --- /dev/null +++ b/vphysics/physics_vehicle.cpp @@ -0,0 +1,1606 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#ifdef _WIN32 +#pragma warning (disable:4127) +#pragma warning (disable:4244) +#endif + +#include "cbase.h" +#include "ivp_controller.hxx" +#include "ivp_cache_object.hxx" +#include "ivp_car_system.hxx" +#include "ivp_constraint_car.hxx" +#include "ivp_material.hxx" + +#include "vphysics/vehicles.h" +#include "vphysics/friction.h" +#include "physics_vehicle.h" +#include "physics_controller_raycast_vehicle.h" +#include "physics_airboat.h" +#include "ivp_car_system.hxx" +#include "ivp_listener_object.hxx" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define THROTTLE_OPPOSING_FORCE_EPSILON 5.0f +#define VEHICLE_SKID_EPSILON 0.1f + +// y in/s = x miles/hour * (5280 * 12 (in / mile)) * (1 / 3600 (hour / sec) ) +//#define MPH2INS(x) ( (x) * 5280.0f * 12.0f / 3600.0f ) +//#define INS2MPH(x) ( (x) * 3600 * (1/5280.0f) * (1/12.0f) ) + +#define MPH_TO_METERSPERSECOND 0.44707f +#define METERSPERSECOND_TO_MPH (1.0f / MPH_TO_METERSPERSECOND) + +#define MPH_TO_GAMEVEL(x) (ConvertDistanceToHL( (x) * MPH_TO_METERSPERSECOND )) +#define GAMEVEL_TO_MPH(x) (ConvertDistanceToIVP(x) * METERSPERSECOND_TO_MPH) + + +#define FVEHICLE_THROTTLE_STOPPED 0x00000001 +#define FVEHICLE_HANDBRAKE_ON 0x00000002 + +struct vphysics_save_cvehiclecontroller_t +{ + CPhysicsObject *m_pCarBody; + int m_wheelCount; + vehicleparams_t m_vehicleData; + vehicle_operatingparams_t m_currentState; + float m_wheelRadius; + float m_bodyMass; + float m_totalWheelMass; + float m_gravityLength; + float m_torqueScale; + CPhysicsObject *m_pWheels[VEHICLE_MAX_WHEEL_COUNT]; + Vector m_wheelPosition_Bs[VEHICLE_MAX_WHEEL_COUNT]; + Vector m_tracePosition_Bs[VEHICLE_MAX_WHEEL_COUNT]; + int m_vehicleFlags; + unsigned int m_nTireType; + unsigned int m_nVehicleType; + bool m_bTraceData; + bool m_bOccupied; + bool m_bEngineDisable; + float m_flVelocity[3]; + + DECLARE_SIMPLE_DATADESC(); +}; + + +BEGIN_SIMPLE_DATADESC( vphysics_save_cvehiclecontroller_t ) + DEFINE_VPHYSPTR( m_pCarBody ), + DEFINE_FIELD( m_wheelCount, FIELD_INTEGER ), + DEFINE_EMBEDDED( m_vehicleData ), + DEFINE_EMBEDDED( m_currentState ), + DEFINE_FIELD( m_wheelCount, FIELD_INTEGER ), + DEFINE_FIELD( m_bodyMass, FIELD_FLOAT ), + DEFINE_FIELD( m_totalWheelMass, FIELD_FLOAT ), + DEFINE_FIELD( m_gravityLength, FIELD_FLOAT ), + DEFINE_FIELD( m_torqueScale, FIELD_FLOAT ), + + DEFINE_VPHYSPTR_ARRAY( m_pWheels, VEHICLE_MAX_WHEEL_COUNT ), + DEFINE_ARRAY( m_wheelPosition_Bs, FIELD_VECTOR, VEHICLE_MAX_WHEEL_COUNT ), + DEFINE_ARRAY( m_tracePosition_Bs, FIELD_VECTOR, VEHICLE_MAX_WHEEL_COUNT ), + DEFINE_FIELD( m_vehicleFlags, FIELD_INTEGER ), + DEFINE_FIELD( m_nTireType, FIELD_INTEGER ), + DEFINE_FIELD( m_nVehicleType, FIELD_INTEGER ), + DEFINE_FIELD( m_bTraceData, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bOccupied, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bEngineDisable, FIELD_BOOLEAN ), + DEFINE_ARRAY( m_flVelocity, FIELD_FLOAT, 3 ), +END_DATADESC() + +BEGIN_SIMPLE_DATADESC( vehicle_operatingparams_t ) + DEFINE_FIELD( speed, FIELD_FLOAT ), + DEFINE_FIELD( engineRPM, FIELD_FLOAT ), + DEFINE_FIELD( gear, FIELD_INTEGER ), + DEFINE_FIELD( boostDelay, FIELD_FLOAT ), + DEFINE_FIELD( boostTimeLeft, FIELD_INTEGER ), + DEFINE_FIELD( skidSpeed, FIELD_FLOAT ), + DEFINE_CUSTOM_FIELD( skidMaterial, MaterialIndexDataOps() ), + DEFINE_FIELD( steeringAngle, FIELD_FLOAT ), + DEFINE_FIELD( wheelsInContact, FIELD_INTEGER ), + DEFINE_FIELD( wheelsNotInContact,FIELD_INTEGER ), + DEFINE_FIELD( isTorqueBoosting, FIELD_BOOLEAN ), +END_DATADESC() + +BEGIN_SIMPLE_DATADESC( vehicle_bodyparams_t ) + DEFINE_FIELD( massCenterOverride, FIELD_VECTOR ), + DEFINE_FIELD( massOverride, FIELD_FLOAT ), + DEFINE_FIELD( addGravity, FIELD_FLOAT ), + DEFINE_FIELD( maxAngularVelocity, FIELD_FLOAT ), + DEFINE_FIELD( tiltForce, FIELD_FLOAT ), + DEFINE_FIELD( tiltForceHeight, FIELD_FLOAT ), + DEFINE_FIELD( counterTorqueFactor, FIELD_FLOAT ), + DEFINE_FIELD( keepUprightTorque, FIELD_FLOAT ), +END_DATADESC() + +BEGIN_SIMPLE_DATADESC( vehicle_wheelparams_t ) + DEFINE_FIELD( radius, FIELD_FLOAT ), + DEFINE_FIELD( mass, FIELD_FLOAT ), + DEFINE_FIELD( inertia, FIELD_FLOAT ), + DEFINE_FIELD( damping, FIELD_FLOAT ), + DEFINE_FIELD( rotdamping, FIELD_FLOAT ), + DEFINE_FIELD( frictionScale, FIELD_FLOAT ), + DEFINE_CUSTOM_FIELD( materialIndex, MaterialIndexDataOps() ), + DEFINE_CUSTOM_FIELD( brakeMaterialIndex, MaterialIndexDataOps() ), + DEFINE_CUSTOM_FIELD( skidMaterialIndex, MaterialIndexDataOps() ), + DEFINE_FIELD( springAdditionalLength, FIELD_FLOAT ), +END_DATADESC() + +BEGIN_SIMPLE_DATADESC( vehicle_suspensionparams_t ) + DEFINE_FIELD( springConstant, FIELD_FLOAT ), + DEFINE_FIELD( springDamping, FIELD_FLOAT ), + DEFINE_FIELD( stabilizerConstant, FIELD_FLOAT ), + DEFINE_FIELD( springDampingCompression, FIELD_FLOAT ), + DEFINE_FIELD( maxBodyForce, FIELD_FLOAT ), +END_DATADESC() + +BEGIN_SIMPLE_DATADESC( vehicle_axleparams_t ) + DEFINE_FIELD( offset, FIELD_VECTOR ), + DEFINE_FIELD( wheelOffset, FIELD_VECTOR ), + DEFINE_FIELD( raytraceCenterOffset, FIELD_VECTOR ), + DEFINE_FIELD( raytraceOffset, FIELD_VECTOR ), + DEFINE_EMBEDDED( wheels ), + DEFINE_EMBEDDED( suspension ), + DEFINE_FIELD( torqueFactor, FIELD_FLOAT ), + DEFINE_FIELD( brakeFactor, FIELD_FLOAT ), +END_DATADESC() + +BEGIN_SIMPLE_DATADESC( vehicle_steeringparams_t ) + DEFINE_FIELD( degreesSlow, FIELD_FLOAT ), + DEFINE_FIELD( degreesFast, FIELD_FLOAT ), + DEFINE_FIELD( degreesBoost, FIELD_FLOAT ), + DEFINE_FIELD( steeringRateSlow, FIELD_FLOAT ), + DEFINE_FIELD( steeringRateFast, FIELD_FLOAT ), + DEFINE_FIELD( steeringRestRateSlow, FIELD_FLOAT ), + DEFINE_FIELD( steeringRestRateFast, FIELD_FLOAT ), + DEFINE_FIELD( throttleSteeringRestRateFactor, FIELD_FLOAT ), + DEFINE_FIELD( boostSteeringRestRateFactor, FIELD_FLOAT ), + DEFINE_FIELD( boostSteeringRateFactor, FIELD_FLOAT ), + DEFINE_FIELD( steeringExponent, FIELD_FLOAT ), + DEFINE_FIELD( speedSlow, FIELD_FLOAT ), + DEFINE_FIELD( speedFast, FIELD_FLOAT ), + DEFINE_FIELD( turnThrottleReduceSlow, FIELD_FLOAT ), + DEFINE_FIELD( turnThrottleReduceFast, FIELD_FLOAT ), + DEFINE_FIELD( powerSlideAccel, FIELD_FLOAT ), + DEFINE_FIELD( brakeSteeringRateFactor, FIELD_FLOAT ), + DEFINE_FIELD( isSkidAllowed, FIELD_BOOLEAN ), + DEFINE_FIELD( dustCloud, FIELD_BOOLEAN ), +END_DATADESC() + +BEGIN_SIMPLE_DATADESC( vehicle_engineparams_t ) + DEFINE_FIELD( horsepower, FIELD_FLOAT ), + DEFINE_FIELD( maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( maxRevSpeed, FIELD_FLOAT ), + DEFINE_FIELD( maxRPM, FIELD_FLOAT ), + DEFINE_FIELD( axleRatio, FIELD_FLOAT ), + DEFINE_FIELD( throttleTime, FIELD_FLOAT ), + DEFINE_FIELD( maxRPM, FIELD_FLOAT ), + DEFINE_FIELD( isAutoTransmission, FIELD_BOOLEAN ), + DEFINE_FIELD( gearCount, FIELD_INTEGER ), + DEFINE_AUTO_ARRAY( gearRatio, FIELD_FLOAT ), + DEFINE_FIELD( shiftUpRPM, FIELD_FLOAT ), + DEFINE_FIELD( shiftDownRPM, FIELD_FLOAT ), + DEFINE_FIELD( boostForce, FIELD_FLOAT ), + DEFINE_FIELD( boostDuration, FIELD_FLOAT ), + DEFINE_FIELD( boostDelay, FIELD_FLOAT ), + DEFINE_FIELD( boostMaxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( autobrakeSpeedGain, FIELD_FLOAT ), + DEFINE_FIELD( autobrakeSpeedFactor, FIELD_FLOAT ), + DEFINE_FIELD( torqueBoost, FIELD_BOOLEAN ), +END_DATADESC() + +BEGIN_SIMPLE_DATADESC( vehicleparams_t ) + DEFINE_FIELD( axleCount, FIELD_INTEGER ), + DEFINE_FIELD( wheelsPerAxle, FIELD_INTEGER ), + DEFINE_EMBEDDED( body ), + DEFINE_EMBEDDED_AUTO_ARRAY( axles ), + DEFINE_EMBEDDED( engine ), + DEFINE_EMBEDDED( steering ), +END_DATADESC() + + +bool IsVehicleWheel( IVP_Real_Object *pivp ) +{ + CPhysicsObject *pObject = static_cast<CPhysicsObject *>(pivp->client_data); + + // FIXME: Check why this is null! It occurs when jumping the ravine in seafloor + if (!pObject) + return false; + + return (pObject->CallbackFlags() & CALLBACK_IS_VEHICLE_WHEEL) ? true : false; +} + +inline bool IsMoveable( IVP_Real_Object *pObject ) +{ + IVP_Core *pCore = pObject->get_core(); + if ( pCore->pinned || pCore->physical_unmoveable ) + return false; + return true; +} + +inline bool IVPFloatPointIsZero( const IVP_U_Float_Point &test ) +{ + const float eps = 1e-4f; + return test.quad_length() < eps ? true : false; +} + +bool ShouldOverrideWheelContactFriction( float *pFrictionOut, IVP_Real_Object *pivp0, IVP_Real_Object *pivp1, IVP_U_Float_Point *pNormal ) +{ + if ( !pivp0->get_core()->car_wheel && !pivp1->get_core()->car_wheel ) + return false; + + if ( !IsVehicleWheel(pivp0) ) + { + if ( !IsVehicleWheel(pivp1) ) + return false; + // swap so pivp0 is a wheel + IVP_Real_Object *pTmp = pivp0; + pivp0 = pivp1; + pivp1 = pTmp; + } + + // if we got here then pivp0 is a car wheel object + // BUGBUG: IVP sometimes sends us a bogus normal + // when doing a material realc on existing contacts! + if ( !IVPFloatPointIsZero(pNormal) ) + { + IVP_U_Float_Point normalWheelSpace; + pivp0->get_core()->get_m_world_f_core_PSI()->vimult3( pNormal, &normalWheelSpace ); + if ( fabs(normalWheelSpace.k[0]) > 0.2588f ) // 15 degree wheel cone + { + // adjust friction here, this isn't a valid part of the wheel for contact, set friction to zero + //Vector tmp;ConvertDirectionToHL( normalWheelSpace, tmp );Msg("Wheel sliding on surface %.2f %.2f %.2f\n", tmp.x, tmp.y, tmp.z ); + *pFrictionOut = 0; + return true; + } + } + // was car wheel, but didn't adjust - use default friction + return false; +} + + +class CVehicleController : public IPhysicsVehicleController, public IVP_Listener_Object +{ +public: + CVehicleController( ); + CVehicleController( const vehicleparams_t ¶ms, CPhysicsEnvironment *pEnv, unsigned int nVehicleType, IPhysicsGameTrace *pGameTrace ); + ~CVehicleController(); + + // CVehicleController + void InitCarSystem( CPhysicsObject *pBodyObject ); + + // IPhysicsVehicleController + void Update( float dt, vehicle_controlparams_t &controls ); + float UpdateBooster( float dt ); + void SetSpringLength(int wheelIndex, float length); + const vehicle_operatingparams_t &GetOperatingParams() { return m_currentState; } + const vehicleparams_t &GetVehicleParams() { return m_vehicleData; } + vehicleparams_t &GetVehicleParamsForChange() { return m_vehicleData; } + int GetWheelCount(void) { return m_wheelCount; }; + IPhysicsObject* GetWheel(int index); + virtual bool GetWheelContactPoint( int index, Vector *pContactPoint, int *pSurfaceProps ); + void SetWheelFriction(int wheelIndex, float friction); + + void SetEngineDisabled( bool bDisable ) { m_bEngineDisable = bDisable; } + bool IsEngineDisabled( void ) { return m_bEngineDisable; } + + // Save/load + void WriteToTemplate( vphysics_save_cvehiclecontroller_t &controllerTemplate ); + void InitFromTemplate( CPhysicsEnvironment *pEnv, void *pGameData, IPhysicsGameTrace *pGameTrace, const vphysics_save_cvehiclecontroller_t &controllerTemplate ); + void VehicleDataReload(); + + // Debug + void GetCarSystemDebugData( vehicle_debugcarsystem_t &debugCarSystem ); + + // IVP_Listener_Object + // Object listener, only hook delete + virtual void event_object_deleted( IVP_Event_Object *); + virtual void event_object_created( IVP_Event_Object *) {} + virtual void event_object_revived( IVP_Event_Object *) {} + virtual void event_object_frozen ( IVP_Event_Object *) {} + + // Entry/Exit + void OnVehicleEnter( void ); + void OnVehicleExit( void ); + +protected: + void CreateIVPObjects( ); + void ShutdownCarSystem(); + + void InitVehicleData( const vehicleparams_t ¶ms ); + void InitCarSystemBody( IVP_Template_Car_System &ivpVehicleData ); + void InitCarSystemWheels( IVP_Template_Car_System &ivpVehicleData ); + + void AttachListener(); + + IVP_Real_Object *CreateWheel( int wheelIndex, vehicle_axleparams_t &axle ); + void CreateTraceData( int wheelIndex, vehicle_axleparams_t &axle ); + + // Update. + void UpdateSteering( const vehicle_controlparams_t &controls, float flDeltaTime, float flSpeed ); + void UpdatePowerslide( const vehicle_controlparams_t &controls, bool bPowerslide, float flSpeed ); + void UpdateEngine( const vehicle_controlparams_t &controls, float flDeltaTime, float flThrottle, float flBrake, bool bHandbrake, bool bPowerslide ); + bool UpdateEngineTurboStart( const vehicle_controlparams_t &controls, float flDeltaTime ); + void UpdateEngineTurboFinish( void ); + void UpdateHandbrake( const vehicle_controlparams_t &controls, float flThrottle, bool bHandbrake, bool bPowerslide ); + void UpdateSkidding( bool bHandbrake ); + void UpdateExtraForces( void ); + void UpdateWheelPositions( void ); + float CalcSteering( float dt, float speed, float steering, bool bAnalog ); + void CalcEngine( float throttle, float brake_val, bool handbrake, float steeringVal, bool torqueBoost ); + void CalcEngineTransmission( float flThrottle ); + + virtual bool IsBoosting( void ); + +private: + void ResetState(); + IVP_Car_System *m_pCarSystem; + CPhysicsObject *m_pCarBody; + CPhysicsEnvironment *m_pEnv; + IPhysicsGameTrace *m_pGameTrace; + int m_wheelCount; + vehicleparams_t m_vehicleData; + vehicle_operatingparams_t m_currentState; + float m_wheelRadius; + float m_bodyMass; + float m_totalWheelMass; + float m_gravityLength; + float m_torqueScale; + CPhysicsObject *m_pWheels[VEHICLE_MAX_WHEEL_COUNT]; + IVP_U_Float_Point m_wheelPosition_Bs[VEHICLE_MAX_WHEEL_COUNT]; + IVP_U_Float_Point m_tracePosition_Bs[VEHICLE_MAX_WHEEL_COUNT]; + int m_vehicleFlags; + unsigned int m_nTireType; + unsigned int m_nVehicleType; + bool m_bTraceData; + bool m_bOccupied; + bool m_bEngineDisable; + float m_flVelocity[3]; +}; + +CVehicleController::CVehicleController( const vehicleparams_t ¶ms, CPhysicsEnvironment *pEnv, unsigned int nVehicleType, IPhysicsGameTrace *pGameTrace ) +{ + m_pEnv = pEnv; + m_pGameTrace = pGameTrace; + m_nVehicleType = nVehicleType; + InitVehicleData( params ); + ResetState(); +} + + +CVehicleController::CVehicleController() +{ + ResetState(); +} + + +void CVehicleController::ResetState() +{ + m_pCarSystem = NULL; + m_flVelocity[0] = m_flVelocity[1]= m_flVelocity[2] = 0.0f; + for ( int i = 0; i < VEHICLE_MAX_WHEEL_COUNT; i++ ) + { + m_pWheels[i] = NULL; + } + m_pCarBody = NULL; + m_torqueScale = 1; + m_wheelCount = 0; + m_wheelRadius = 0; + memset( &m_currentState, 0, sizeof(m_currentState) ); + m_bodyMass = 0; + m_vehicleFlags = 0; + memset( m_wheelPosition_Bs, 0, sizeof(m_wheelPosition_Bs) ); + memset( m_tracePosition_Bs, 0, sizeof(m_tracePosition_Bs) ); + + m_bTraceData = false; + if ( m_nVehicleType == VEHICLE_TYPE_AIRBOAT_RAYCAST ) + { + m_bTraceData = true; + } + + m_nTireType = VEHICLE_TIRE_NORMAL; + + m_bOccupied = false; + m_bEngineDisable = false; +} + +CVehicleController::~CVehicleController() +{ + ShutdownCarSystem(); +} + +IPhysicsObject* CVehicleController::GetWheel( int index ) +{ + // TODO: This is getting messy. + if ( m_nVehicleType == VEHICLE_TYPE_CAR_WHEELS ) + { + return m_pWheels[index]; + } + else if ( m_nVehicleType == VEHICLE_TYPE_CAR_RAYCAST && m_pCarSystem ) + { + return static_cast<CPhysics_Car_System_Raycast_Wheels*>( m_pCarSystem )->GetWheel( index ); + } + else if ( m_nVehicleType == VEHICLE_TYPE_AIRBOAT_RAYCAST && m_pCarSystem ) + { + return static_cast<CPhysics_Airboat*>( m_pCarSystem )->GetWheel( index ); + } + + return NULL; +} + +void CVehicleController::SetWheelFriction(int wheelIndex, float friction) +{ + CPhysics_Airboat *pAirboat = static_cast<CPhysics_Airboat*>( m_pCarSystem ); + if ( !pAirboat ) + return; + + pAirboat->SetWheelFriction( wheelIndex, friction ); +} + +bool CVehicleController::GetWheelContactPoint( int index, Vector *pContactPoint, int *pSurfaceProps ) +{ + bool bSet = false; + if ( index < m_wheelCount ) + { + IPhysicsFrictionSnapshot *pSnapshot = m_pWheels[index]->CreateFrictionSnapshot(); + float forceMax = -1.0f; + m_pWheels[index]->GetPosition( pContactPoint, NULL ); + while ( pSnapshot->IsValid() ) + { + float thisForce = pSnapshot->GetNormalForce(); + if ( thisForce > forceMax ) + { + forceMax = thisForce; + if ( pContactPoint ) + { + pSnapshot->GetContactPoint( *pContactPoint ); + } + if ( pSurfaceProps ) + { + *pSurfaceProps = pSnapshot->GetMaterial(1); + } + bSet = true; + } + pSnapshot->NextFrictionData(); + } + m_pWheels[index]->DestroyFrictionSnapshot(pSnapshot); + } + else + { + if ( pContactPoint ) + { + pContactPoint->Init(); + } + if ( pSurfaceProps ) + { + *pSurfaceProps = 0; + } + + } + return bSet; +} + +void CVehicleController::AttachListener() +{ + m_pCarBody->GetObject()->add_listener_object( this ); +} + +void CVehicleController::event_object_deleted( IVP_Event_Object *pEvent ) +{ + // the car system's constraint solver is going to delete itself now, so NULL the car system. + + m_pCarSystem->event_object_deleted( pEvent ); + m_pCarSystem = NULL; + ShutdownCarSystem(); +} + +IVP_Real_Object *CVehicleController::CreateWheel( int wheelIndex, vehicle_axleparams_t &axle ) +{ + if ( wheelIndex >= VEHICLE_MAX_WHEEL_COUNT ) + return NULL; + + // HACKHACK: In Save/load, the wheel was reloaded, so pretend to create it + // ALSO NOTE: Save/load puts the results into m_pWheels regardless of vehicle type!!! + // That's why I'm not calling GetWheel(). + if ( m_pWheels[wheelIndex] ) + { + CPhysicsObject *pWheelObject = static_cast<CPhysicsObject *>(m_pWheels[wheelIndex]); + return pWheelObject->GetObject(); + } + + objectparams_t params; + memset( ¶ms, 0, sizeof(params) ); + + Vector bodyPosition; + QAngle bodyAngles; + m_pCarBody->GetPosition( &bodyPosition, &bodyAngles ); + matrix3x4_t matrix; + AngleMatrix( bodyAngles, bodyPosition, matrix ); + + Vector position = axle.offset; + + // BUGBUG: This only works with 2 wheels per axle + if ( wheelIndex & 1 ) + { + position += axle.wheelOffset; + } + else + { + position -= axle.wheelOffset; + } + + Vector wheelPositionHL; + VectorTransform( position, matrix, wheelPositionHL ); + + params.damping = axle.wheels.damping; + params.dragCoefficient = 0; + params.enableCollisions = false; + params.inertia = axle.wheels.inertia; + params.mass = axle.wheels.mass; + params.pGameData = m_pCarBody->GetGameData(); + params.pName = "VehicleWheel"; + params.rotdamping = axle.wheels.rotdamping; + params.rotInertiaLimit = 0; + params.massCenterOverride = NULL; + // needs to be in HL units because we're calling through the "outer" interface to create + // the wheels + float radius = axle.wheels.radius; + float r3 = radius * radius * radius; + params.volume = (4 / 3) * M_PI * r3; + + CPhysicsObject *pWheel = (CPhysicsObject *)m_pEnv->CreateSphereObject( radius, axle.wheels.materialIndex, wheelPositionHL, bodyAngles, ¶ms, false ); + pWheel->Wake(); + + // UNDONE: only mask off some of these flags? + unsigned int flags = pWheel->CallbackFlags(); + flags = 0; + pWheel->SetCallbackFlags( flags ); + // copy the body's game flags + pWheel->SetGameFlags( m_pCarBody->GetGameFlags() ); + // cache the wheel object pointer + m_pWheels[wheelIndex] = pWheel; + + IVP_U_Point wheelPositionIVP, wheelPositionBs; + ConvertPositionToIVP( wheelPositionHL, wheelPositionIVP ); + TransformIVPToLocal( wheelPositionIVP, wheelPositionBs, m_pCarBody->GetObject(), true ); + m_wheelPosition_Bs[wheelIndex].set_to_zero(); + m_wheelPosition_Bs[wheelIndex].set( &wheelPositionBs ); + + pWheel->AddCallbackFlags( CALLBACK_IS_VEHICLE_WHEEL ); + + return pWheel->GetObject(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleController::CreateTraceData( int wheelIndex, vehicle_axleparams_t &axle ) +{ + if ( wheelIndex >= VEHICLE_MAX_WHEEL_COUNT ) + return; + + objectparams_t params; + memset( ¶ms, 0, sizeof( params ) ); + + Vector bodyPosition; + QAngle bodyAngles; + matrix3x4_t matrix; + m_pCarBody->GetPosition( &bodyPosition, &bodyAngles ); + AngleMatrix( bodyAngles, bodyPosition, matrix ); + + Vector tracePosition = axle.raytraceCenterOffset; + // BUGBUG: This only works with 2 wheels per axle + if ( wheelIndex & 1 ) + { + tracePosition += axle.raytraceOffset; + } + else + { + tracePosition -= axle.raytraceOffset; + } + + Vector tracePositionHL; + VectorTransform( tracePosition, matrix, tracePositionHL ); + + IVP_U_Point tracePositionIVP, tracePositionBs; + ConvertPositionToIVP( tracePositionHL, tracePositionIVP ); + TransformIVPToLocal( tracePositionIVP, tracePositionBs, m_pCarBody->GetObject(), true ); + m_tracePosition_Bs[wheelIndex].set_to_zero(); + m_tracePosition_Bs[wheelIndex].set( &tracePositionBs ); +} + +void CVehicleController::CreateIVPObjects( ) +{ + // Initialize the car system (body and wheels). + IVP_Template_Car_System ivpVehicleData( m_wheelCount, m_vehicleData.axleCount ); + InitCarSystemBody( ivpVehicleData ); + + InitCarSystemWheels( ivpVehicleData ); + + BEGIN_IVP_ALLOCATION(); + + // Raycast Car + switch ( m_nVehicleType ) + { + case VEHICLE_TYPE_CAR_WHEELS: + m_pCarSystem = new IVP_Car_System_Real_Wheels( m_pEnv->GetIVPEnvironment(), &ivpVehicleData ); + break + ; + case VEHICLE_TYPE_CAR_RAYCAST: + m_pCarSystem = new CPhysics_Car_System_Raycast_Wheels( m_pEnv->GetIVPEnvironment(), &ivpVehicleData ); + break; + + case VEHICLE_TYPE_AIRBOAT_RAYCAST: + m_pCarSystem = new CPhysics_Airboat( m_pEnv->GetIVPEnvironment(), &ivpVehicleData, m_pGameTrace ); + break; + } + + AttachListener(); + + END_IVP_ALLOCATION(); +} + + +void CVehicleController::InitCarSystem( CPhysicsObject *pBodyObject ) +{ + if ( m_pCarSystem ) + { + ShutdownCarSystem(); + } + + // Car body. + m_pCarBody = pBodyObject; + m_bodyMass = m_pCarBody->GetMass(); + m_gravityLength = m_pEnv->GetIVPEnvironment()->get_gravity()->real_length(); + // Setup axle/wheel counts. + m_wheelCount = m_vehicleData.axleCount * m_vehicleData.wheelsPerAxle; + CreateIVPObjects(); + + if ( m_nVehicleType == VEHICLE_TYPE_AIRBOAT_RAYCAST ) + { + float flDampSpeed = 1.0f; + float flDampRotSpeed = 1.0f; + m_pCarBody->SetDamping( &flDampSpeed, &flDampRotSpeed ); + } +} + +void CVehicleController::VehicleDataReload() +{ + // compute torque normalization factor + m_torqueScale = 1; + // Clear accumulation. + float totalTorqueDistribution = 0.0f; + for ( int i = 0; i < m_vehicleData.axleCount; i++ ) + { + totalTorqueDistribution += m_vehicleData.axles[i].torqueFactor; + } + + if ( totalTorqueDistribution > 0 ) + { + m_torqueScale /= totalTorqueDistribution; + } + // input speed is in miles/hour. Convert to in/s + m_vehicleData.engine.maxSpeed = MPH_TO_GAMEVEL(m_vehicleData.engine.maxSpeed); + m_vehicleData.engine.maxRevSpeed = MPH_TO_GAMEVEL(m_vehicleData.engine.maxRevSpeed); + m_vehicleData.engine.boostMaxSpeed = MPH_TO_GAMEVEL(m_vehicleData.engine.boostMaxSpeed); +} + + +//----------------------------------------------------------------------------- +// Purpose: Setup the body parameters. +//----------------------------------------------------------------------------- +void CVehicleController::InitCarSystemBody( IVP_Template_Car_System &ivpVehicleData ) +{ + ivpVehicleData.car_body = m_pCarBody->GetObject(); + + ivpVehicleData.index_x = IVP_INDEX_X; + ivpVehicleData.index_y = IVP_INDEX_Y; + ivpVehicleData.index_z = IVP_INDEX_Z; + + ivpVehicleData.body_counter_torque_factor = m_vehicleData.body.counterTorqueFactor; + ivpVehicleData.body_down_force_vertical_offset = ConvertDistanceToIVP( m_vehicleData.body.tiltForceHeight ); + ivpVehicleData.extra_gravity_force_value = m_vehicleData.body.addGravity * m_gravityLength * m_bodyMass; + ivpVehicleData.extra_gravity_height_offset = 0; + +#if 0 + // HACKHACK: match example + ivpVehicleData.extra_gravity_force_value = 1.2; + ivpVehicleData.body_down_force_vertical_offset = 2; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Setup the wheel paramters. +//----------------------------------------------------------------------------- +void CVehicleController::InitCarSystemWheels( IVP_Template_Car_System &ivpVehicleData ) +{ + int wheelIndex = 0; + + m_wheelRadius = 0; + m_totalWheelMass = 0; + + int i; + for ( i = 0; i < m_vehicleData.axleCount; i++ ) + { + for ( int w = 0; w < m_vehicleData.wheelsPerAxle; w++, wheelIndex++ ) + { + IVP_Real_Object *pWheel = CreateWheel( wheelIndex, m_vehicleData.axles[i] ); + if ( pWheel ) + { + // Create ray trace data for wheel. + if ( m_bTraceData ) + { + CreateTraceData( wheelIndex, m_vehicleData.axles[i] ); + } + + ivpVehicleData.car_wheel[wheelIndex] = pWheel; + ivpVehicleData.wheel_radius[wheelIndex] = pWheel->get_core()->upper_limit_radius; + ivpVehicleData.wheel_reversed_sign[wheelIndex] = 1.0; + // only for raycast car + + ivpVehicleData.friction_of_wheel[wheelIndex] = m_vehicleData.axles[i].wheels.frictionScale; + ivpVehicleData.spring_constant[wheelIndex] = m_vehicleData.axles[i].suspension.springConstant * m_bodyMass; + ivpVehicleData.spring_dampening[wheelIndex] = m_vehicleData.axles[i].suspension.springDamping * m_bodyMass; + ivpVehicleData.spring_dampening_compression[wheelIndex] = m_vehicleData.axles[i].suspension.springDampingCompression * m_bodyMass; + ivpVehicleData.max_body_force[wheelIndex] = m_vehicleData.axles[i].suspension.maxBodyForce * m_bodyMass; + ivpVehicleData.spring_pre_tension[wheelIndex] = -ConvertDistanceToIVP( m_vehicleData.axles[i].wheels.springAdditionalLength ); + + ivpVehicleData.wheel_pos_Bos[wheelIndex] = m_wheelPosition_Bs[wheelIndex]; + if ( m_bTraceData ) + { + ivpVehicleData.trace_pos_Bos[wheelIndex] = m_tracePosition_Bs[wheelIndex]; + } + + m_totalWheelMass += m_vehicleData.axles[i].wheels.mass; + } + } + + ivpVehicleData.stabilizer_constant[i] = m_vehicleData.axles[i].suspension.stabilizerConstant * m_bodyMass; + // this should output in radians per second + float radius = ConvertDistanceToIVP( m_vehicleData.axles[i].wheels.radius ); + float totalMaxSpeed = max( m_vehicleData.engine.boostMaxSpeed, m_vehicleData.engine.maxSpeed ); + ivpVehicleData.wheel_max_rotation_speed[i] = totalMaxSpeed / radius; + if ( radius > m_wheelRadius ) + { + m_wheelRadius = radius; + } + } + + for ( i = 0; i < m_wheelCount; i++ ) + { + m_pWheels[i]->EnableCollisions( true ); + } +} + +void CVehicleController::ShutdownCarSystem() +{ + delete m_pCarSystem; + m_pCarSystem = NULL; + for ( int i = 0; i < m_wheelCount; i++ ) + { + if ( m_pWheels[i] ) + { + m_pEnv->DestroyObject( m_pWheels[i] ); + } + m_pWheels[i] = NULL; + } +} + + +void CVehicleController::InitVehicleData( const vehicleparams_t ¶ms ) +{ + m_vehicleData = params; + VehicleDataReload(); +} + +void CVehicleController::SetSpringLength(int wheelIndex, float length) +{ + m_pCarSystem->change_spring_length((IVP_POS_WHEEL)wheelIndex, length); +} + +//----------------------------------------------------------------------------- +// Purpose: Allows booster timer to run, +// Returns: true if time still exists +// false if timer has run out (i.e. can use boost again) +//----------------------------------------------------------------------------- +float CVehicleController::UpdateBooster( float dt ) +{ + m_pCarSystem->update_booster( dt ); + m_currentState.boostDelay = m_pCarSystem->get_booster_delay(); + return m_currentState.boostDelay; +} + +//----------------------------------------------------------------------------- +// Purpose: Are whe boosting? +//----------------------------------------------------------------------------- +bool CVehicleController::IsBoosting( void ) +{ + return ( m_pCarSystem->get_booster_time_to_go() > 0.0f ); +} + +//----------------------------------------------------------------------------- +// Purpose: Update the vehicle controller. +//----------------------------------------------------------------------------- +void CVehicleController::Update( float dt, vehicle_controlparams_t &controlsIn ) +{ + vehicle_controlparams_t controls = controlsIn; + // Speed. + m_currentState.speed = ConvertDistanceToHL( m_pCarSystem->get_body_speed() ); + float flSpeed = GAMEVEL_TO_MPH( m_currentState.speed ); + float flAbsSpeed = fabsf( flSpeed ); + + // Calculate the throttle and brake values. + float flThrottle = controls.throttle; + bool bHandbrake = controls.handbrake; + float flBrake = controls.brake; + bool bPowerslide = bHandbrake && ( flAbsSpeed > 18.0f ); + + if ( bHandbrake ) + { + flThrottle = 0.0f; + } + + if ( IsBoosting() ) + { + controls.boost = true; + flThrottle = flThrottle < 0.0f ? -1.0f : 1.0f; + } + + if ( flThrottle == 0.0f && flBrake == 0.0f && !bHandbrake ) + { + flBrake = 0.1f; + } + + // Update steering. + UpdateSteering( controls, dt, flAbsSpeed ); + + // Update powerslide. + UpdatePowerslide( controls, bPowerslide, flSpeed ); + + // Update engine. + UpdateEngine( controls, dt, flThrottle, flBrake, bHandbrake, bPowerslide ); + + // Update handbrake. + UpdateHandbrake( controls, flThrottle, bHandbrake, bPowerslide ); + + // Update skidding. + UpdateSkidding( bHandbrake ); + + // Apply the extra forces to the car (downward, counter-torque, etc.) + UpdateExtraForces(); + + // Update the physical position of the wheels for raycast vehicles. + UpdateWheelPositions(); +} + +//----------------------------------------------------------------------------- +// Purpose: Update the steering on the vehicle. +//----------------------------------------------------------------------------- +void CVehicleController::UpdateSteering( const vehicle_controlparams_t &controls, float flDeltaTime, float flSpeed ) +{ + // Steering - IVP steering is in radians. + float flSteeringAngle = CalcSteering( flDeltaTime, flSpeed, controls.steering, controls.bAnalogSteering ); + m_pCarSystem->do_steering( DEG2RAD( flSteeringAngle ), controls.bAnalogSteering ); + m_currentState.steeringAngle = flSteeringAngle; +} + +//----------------------------------------------------------------------------- +// Purpose: Update the powerslide state (wheel materials). +//----------------------------------------------------------------------------- +void CVehicleController::UpdatePowerslide( const vehicle_controlparams_t &controls, bool bPowerslide, float flSpeed ) +{ + // Only allow skidding if it is allowed by the vehicle type. + if ( !m_vehicleData.steering.isSkidAllowed ) + return; + + // Check to see if the vehicle is occupied. + if ( !m_bOccupied ) + return; + + // Set the powerslide left/right. + bool bPowerslideLeft = bPowerslide && controls.handbrakeLeft; + bool bPowerslideRight = bPowerslide && controls.handbrakeRight; + + int iWheel = 0; + unsigned int newTireType = VEHICLE_TIRE_NORMAL; + if ( bPowerslideLeft || bPowerslideRight ) + { + newTireType = VEHICLE_TIRE_POWERSLIDE; + } + else if ( bPowerslide ) + { + newTireType = VEHICLE_TIRE_BRAKING; + } + + if ( newTireType != m_nTireType ) + { + for ( int iAxle = 0; iAxle < m_vehicleData.axleCount; ++iAxle ) + { + int materialIndex = m_vehicleData.axles[iAxle].wheels.materialIndex; + if ( newTireType == VEHICLE_TIRE_POWERSLIDE && ( m_vehicleData.axles[iAxle].wheels.skidMaterialIndex != - 1 ) ) + { + materialIndex = m_vehicleData.axles[iAxle].wheels.skidMaterialIndex; + } + else if ( newTireType == VEHICLE_TIRE_BRAKING && ( m_vehicleData.axles[iAxle].wheels.brakeMaterialIndex != -1 ) ) + { + materialIndex = m_vehicleData.axles[iAxle].wheels.brakeMaterialIndex; + } + + for ( int iAxleWheel = 0; iAxleWheel < m_vehicleData.wheelsPerAxle; ++iAxleWheel, ++iWheel ) + { + m_pWheels[iWheel]->SetMaterialIndex( materialIndex ); + } + + m_nTireType = newTireType; + } + } + + // Push the car a little. + float flFrontAccel = 0.0f; + float flRearAccel = 0.0f; + if ( flSpeed > 0 && (bPowerslideLeft != bPowerslideRight) ) + { + // NOTE: positive acceleration is to the left + float powerSlide = RemapValClamped( flSpeed, m_vehicleData.steering.speedSlow, m_vehicleData.steering.speedFast, 0, 1 ); + float powerSlideAccel = ConvertDistanceToIVP( m_vehicleData.steering.powerSlideAccel); + if ( bPowerslideLeft ) + { + flFrontAccel = powerSlideAccel * powerSlide; + flRearAccel = -powerSlideAccel * powerSlide; + } + else + { + flFrontAccel = -powerSlideAccel * powerSlide; + flRearAccel = powerSlideAccel * powerSlide; + } + } + m_pCarSystem->set_powerslide( flFrontAccel, flRearAccel ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleController::UpdateEngine( const vehicle_controlparams_t &controls, float flDeltaTime, + float flThrottle, float flBrake, bool bHandbrake, bool bPowerslide ) +{ + bool bTorqueBoost = UpdateEngineTurboStart( controls, flDeltaTime ); + + CalcEngine( flThrottle, flBrake, bHandbrake, controls.steering, bTorqueBoost ); + + UpdateEngineTurboFinish(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CVehicleController::UpdateEngineTurboStart( const vehicle_controlparams_t &controls, float flDeltaTime ) +{ + bool bTorqueBoost = false; + if ( controls.boost > 0 ) + { + if ( m_vehicleData.engine.torqueBoost ) + { + // Turbo will be applied at the engine level. + bTorqueBoost = true; + m_pCarSystem->activate_booster( 0.0f, m_vehicleData.engine.boostDuration, m_vehicleData.engine.boostDelay ); + } + else + { + // Activate the turbo force booster - applied to vehicle body. + m_pCarSystem->activate_booster( m_vehicleData.engine.boostForce * controls.boost, m_vehicleData.engine.boostDuration, m_vehicleData.engine.boostDelay ); + } + } + + m_pCarSystem->update_booster( flDeltaTime ); + m_currentState.boostDelay = m_pCarSystem->get_booster_delay(); + m_currentState.isTorqueBoosting = bTorqueBoost; + + return bTorqueBoost; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleController::UpdateEngineTurboFinish( void ) +{ + if ( m_vehicleData.engine.boostDuration + m_vehicleData.engine.boostDelay > 0 ) // watch out for div by zero + { + if ( m_currentState.boostDelay > 0 ) + { + m_currentState.boostTimeLeft = 100 - 100 * ( m_currentState.boostDelay / ( m_vehicleData.engine.boostDuration + m_vehicleData.engine.boostDelay ) ); + } + else + { + m_currentState.boostTimeLeft = 100; // ready to go any time + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Update the handbrake. +//----------------------------------------------------------------------------- +void CVehicleController::UpdateHandbrake( const vehicle_controlparams_t &controls, float flThrottle, bool bHandbrake, bool bPowerslide ) +{ + // Get the current vehicle speed. + m_currentState.speed = ConvertDistanceToHL( m_pCarSystem->get_body_speed() ); + if ( !bPowerslide ) + { + // HACK! Allowing you to overcome gravity at low throttle. + if ( ( flThrottle < 0.0f && m_currentState.speed > THROTTLE_OPPOSING_FORCE_EPSILON ) || + ( flThrottle > 0.0f && m_currentState.speed < -THROTTLE_OPPOSING_FORCE_EPSILON ) ) + { + bHandbrake = true; + } + } + + if ( bHandbrake ) + { + // HACKHACK: only allow the handbrake when the wheels have contact with something + // otherwise they will affect the car in an undesirable way + bHandbrake = false; + for ( int iWheel = 0; iWheel < m_wheelCount; ++iWheel ) + { + if ( m_pWheels[iWheel]->GetContactPoint(NULL, NULL) ) + { + bHandbrake = true; + break; + } + } + } + + bool currentHandbrake = (m_vehicleFlags & FVEHICLE_HANDBRAKE_ON) ? true : false; + if ( bHandbrake != currentHandbrake ) + { + if ( bHandbrake ) + { + m_vehicleFlags |= FVEHICLE_HANDBRAKE_ON; + } + else + { + m_vehicleFlags &= ~FVEHICLE_HANDBRAKE_ON; + } + + for ( int iWheel = 0; iWheel < m_wheelCount; ++iWheel ) + { + m_pCarSystem->fix_wheel( ( IVP_POS_WHEEL )iWheel, bHandbrake ? IVP_TRUE : IVP_FALSE ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleController::UpdateSkidding( bool bHandbrake ) +{ + m_currentState.skidSpeed = 0.0f; + m_currentState.skidMaterial = 0; + m_currentState.wheelsInContact = m_wheelCount; + m_currentState.wheelsNotInContact = 0; + if ( m_vehicleData.steering.isSkidAllowed ) + { + // Estimate rot speed based on current speed and the front wheels radius + float flAbsSpeed = fabs( m_currentState.speed ); + + Vector contact; + Vector velocity; + int surfaceProps; + m_currentState.wheelsInContact = 0; + m_currentState.wheelsNotInContact = 0; + + for( int iWheel = 0; iWheel < m_wheelCount; ++iWheel ) + { + if ( GetWheelContactPoint( iWheel, &contact, &surfaceProps ) ) + { + // NOTE: The wheel should be translating by the negative of the speed a point in contact with the surface + // is moving. So the net velocity on the surface is zero if that wheel is 100% engaged in driving the car + // any velocity in excess of this gets compared against the threshold for skidding + m_pWheels[iWheel]->GetVelocityAtPoint( contact, &velocity ); + float speed = velocity.Length(); + if ( speed > m_currentState.skidSpeed || m_currentState.skidSpeed <= 0.0f ) + { + m_currentState.skidSpeed = speed; + m_currentState.skidMaterial = surfaceProps; + } + m_currentState.wheelsInContact++; + } + else + { + m_currentState.wheelsNotInContact++; + } + } + // Check for locked wheels. + if ( bHandbrake && ( flAbsSpeed > 30 ) ) + { + m_currentState.skidSpeed = flAbsSpeed; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Apply extra forces to the vehicle. The downward force, counter- +// torque etc. +//----------------------------------------------------------------------------- +void CVehicleController::UpdateExtraForces( void ) +{ + // Extra downward force. + IVP_Cache_Object *co = m_pCarBody->GetObject()->get_cache_object(); + float y_val = co->m_world_f_object.get_elem( IVP_INDEX_Y, IVP_INDEX_Y ); + if ( fabs( y_val ) < 0.05 ) + { + m_pCarSystem->change_body_downforce( m_vehicleData.body.tiltForce * m_gravityLength * m_bodyMass ); + } + else + { + m_pCarSystem->change_body_downforce( 0.0 ); + } + co->remove_reference(); + + // Counter-torque. + if ( m_nVehicleType == VEHICLE_TYPE_CAR_WHEELS ) + { + m_pCarSystem->update_body_countertorque(); + } + + // if the car has a global angular velocity limit, apply that constraint + AngularImpulse angVel; + m_pCarBody->GetVelocity( NULL, &angVel ); + if ( m_vehicleData.body.maxAngularVelocity > 0 && angVel.Length() > m_vehicleData.body.maxAngularVelocity ) + { + VectorNormalize(angVel); + angVel *= m_vehicleData.body.maxAngularVelocity; + m_pCarBody->SetVelocityInstantaneous( NULL, &angVel ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Update the physical position of the wheels for raycast vehicles. +// NOTE: Raycast boat doesn't have wheels. +//----------------------------------------------------------------------------- +void CVehicleController::UpdateWheelPositions( void ) +{ + if ( m_nVehicleType == VEHICLE_TYPE_CAR_RAYCAST ) + { + m_pCarSystem->update_wheel_positions(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CVehicleController::CalcSteering( float dt, float speed, float steering, bool bAnalog ) +{ + float degrees = RemapValClamped( speed, m_vehicleData.steering.speedSlow, m_vehicleData.steering.speedFast, m_vehicleData.steering.degreesSlow, m_vehicleData.steering.degreesFast ); + float speedGame = MPH_TO_GAMEVEL(speed); + if ( speedGame > m_vehicleData.engine.maxSpeed ) + { + degrees = RemapValClamped( speedGame, m_vehicleData.engine.maxSpeed, m_vehicleData.engine.boostMaxSpeed, m_vehicleData.steering.degreesFast, m_vehicleData.steering.degreesBoost ); + } + if ( m_vehicleData.steering.steeringExponent != 0 ) + { + float sign = steering < 0 ? -1 : 1; + float absSteering = fabs(steering); + if ( bAnalog ) + { + // analog steering is directly mapped, not integrated, so go ahead and map the full range using the exponent + // then clamp to the output cone - keeps stick position:turn rate constant + // NOTE: Also hardcode exponent to 2 because we can't add a script entry at this point + float output = pow(absSteering, 2.0f) * sign * m_vehicleData.steering.degreesSlow; + return clamp(output, -degrees, degrees ); + } + // digital steering is integrated, keep time to full turn rate constant + return pow(absSteering, m_vehicleData.steering.steeringExponent) * sign * degrees; + } + return steering * degrees; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleController::CalcEngineTransmission( float flThrottle ) +{ + // Automatic Transmission? + if ( !m_vehicleData.engine.isAutoTransmission ) + return; + + // Calculate the average rotational speed of the vehicle's wheels. + float flAvgRotSpeed = 0.0; + for( int iWheel = 0; iWheel < m_wheelCount; ++iWheel ) + { + float flRotSpeed = fabs( m_pCarSystem->get_wheel_angular_velocity( IVP_POS_WHEEL( iWheel ) ) ); + flAvgRotSpeed += flRotSpeed; + } + flAvgRotSpeed *= 0.5f / ( float )IVP_PI / m_wheelCount; + + float flEstEngineRPM = flAvgRotSpeed * m_vehicleData.engine.axleRatio * m_vehicleData.engine.gearRatio[m_currentState.gear] * 60; + + // Only shift up when going forward (throttling). + if ( flThrottle > 0.0f ) + { + // Shift up?, top gear is gearcount-1 (0 based) + while ( ( flEstEngineRPM > m_vehicleData.engine.shiftUpRPM ) && ( m_currentState.gear < m_vehicleData.engine.gearCount-1 ) ) + { + m_currentState.gear++; + flEstEngineRPM = flAvgRotSpeed * m_vehicleData.engine.axleRatio * m_vehicleData.engine.gearRatio[m_currentState.gear] * 60; + } + } + + // Downshift? + while ( ( flEstEngineRPM < m_vehicleData.engine.shiftDownRPM ) && ( m_currentState.gear > 0 ) ) + { + m_currentState.gear--; + flEstEngineRPM = flAvgRotSpeed * m_vehicleData.engine.axleRatio * m_vehicleData.engine.gearRatio[m_currentState.gear] * 60; + } + + m_currentState.engineRPM = flEstEngineRPM; +} + +//----------------------------------------------------------------------------- +// Purpose: +// throttle goes forward and backward, [-1, 1] +// brake_val [0..1] +//----------------------------------------------------------------------------- +void CVehicleController::CalcEngine( float throttle, float brake_val, bool handbrake, float steeringVal, bool torqueBoost ) +{ + // Update the engine transmission. + CalcEngineTransmission( throttle ); + + // Get the speed of the vehicle. + float flAbsSpeed = fabs( m_currentState.speed ); + + // Speed governor + if ( IsPC() ) + { + float maxSpeed = torqueBoost ? m_vehicleData.engine.boostMaxSpeed : m_vehicleData.engine.maxSpeed; + maxSpeed = max(1.f,maxSpeed); // make sure this is non-zero before the divide + if ( (throttle > 0) && (flAbsSpeed > maxSpeed) ) + { + float frac = flAbsSpeed / maxSpeed; + if ( frac > m_vehicleData.engine.autobrakeSpeedGain ) + { + throttle = 0; + brake_val = (frac - 1.0f) * m_vehicleData.engine.autobrakeSpeedFactor; + if ( m_currentState.wheelsInContact == 0 ) + { + brake_val = 0; + } + } + throttle *= 0.1f; + } + } + else // consoles + { + if ( ( throttle > 0 ) && ( ( !torqueBoost && flAbsSpeed > (m_vehicleData.engine.maxSpeed * throttle) ) || + ( torqueBoost && flAbsSpeed > m_vehicleData.engine.boostMaxSpeed) ) ) + { + throttle *= 0.1f; + } + } + + // Check for reverse - both of these "governors" or horrible and need to be redone before we ship! + if ( ( throttle < 0 ) && ( !torqueBoost && ( flAbsSpeed > m_vehicleData.engine.maxRevSpeed ) ) ) + { + throttle *= 0.1f; + } + + if ( throttle != 0.0 ) + { + m_vehicleFlags &= ~FVEHICLE_THROTTLE_STOPPED; + // calculate the force that propels the car + const float watt_per_hp = 745.0f; + const float seconds_per_minute = 60.0f; + + float wheel_force_by_throttle = throttle * + m_vehicleData.engine.horsepower * (watt_per_hp * seconds_per_minute) * + m_vehicleData.engine.gearRatio[m_currentState.gear] * m_vehicleData.engine.axleRatio / + (m_vehicleData.engine.maxRPM * m_wheelRadius * (2 * IVP_PI)); + + if ( m_currentState.engineRPM >= m_vehicleData.engine.maxRPM ) + { + wheel_force_by_throttle = 0; + } + + int wheelIndex = 0; + for ( int i = 0; i < m_vehicleData.axleCount; i++ ) + { + float axleFactor = m_vehicleData.axles[i].torqueFactor * m_torqueScale; + + float boostFactor = 0.5f; + if ( torqueBoost && IsBoosting() ) + { + // reduce the boost at low speeds and high turns since this usually just makes the tires spin + // this means you only get the full boost when travelling in a straight line at high speed + float speedFactor = RemapValClamped( flAbsSpeed, 0, m_vehicleData.engine.maxSpeed, 0.1f, 1.0f ); + float turnFactor = 1.0f - (fabs(steeringVal) * 0.95f); + float dampedBoost = m_vehicleData.engine.boostForce * speedFactor * turnFactor; + if ( dampedBoost > boostFactor ) + { + boostFactor = dampedBoost; + } + //Msg("Boost applied %.2f, speed %.2f, turn %.2f\n", boostFactor, speedFactor, turnFactor ); + } + float axleTorque = boostFactor * wheel_force_by_throttle * axleFactor * ConvertDistanceToIVP( m_vehicleData.axles[i].wheels.radius ); + + for ( int w = 0; w < m_vehicleData.wheelsPerAxle; w++, wheelIndex++ ) + { + float torqueVal = axleTorque; + m_pCarSystem->change_wheel_torque((IVP_POS_WHEEL)wheelIndex, torqueVal); + } + } + } + else if ( brake_val != 0 ) + { + m_vehicleFlags &= ~FVEHICLE_THROTTLE_STOPPED; + + // Brake to slow down the wheel. + float wheel_force_by_brake = brake_val * m_gravityLength * ( m_bodyMass + m_totalWheelMass ); + + float sign = m_currentState.speed >= 0.0f ? -1.0f : 1.0f; + int wheelIndex = 0; + for ( int i = 0; i < m_vehicleData.axleCount; i++ ) + { + float torque_val = 0.5 * sign * wheel_force_by_brake * m_vehicleData.axles[i].brakeFactor * ConvertDistanceToIVP( m_vehicleData.axles[i].wheels.radius ); + for ( int w = 0; w < m_vehicleData.wheelsPerAxle; w++, wheelIndex++ ) + { + m_pCarSystem->change_wheel_torque( ( IVP_POS_WHEEL )wheelIndex, torque_val ); + } + } + } + else if ( !(m_vehicleFlags & FVEHICLE_THROTTLE_STOPPED) ) + { + m_vehicleFlags |= FVEHICLE_THROTTLE_STOPPED; + + for ( int w = 0; w < m_wheelCount; w++ ) + { + m_pCarSystem->change_wheel_torque((IVP_POS_WHEEL)w, 0); + } + } + + // Update the throttle - primarily for the airboat! + m_pCarSystem->update_throttle( throttle ); +} + +//----------------------------------------------------------------------------- +// Purpose: Get debug rendering data from the ipion physics system. +//----------------------------------------------------------------------------- +void CVehicleController::GetCarSystemDebugData( vehicle_debugcarsystem_t &debugCarSystem ) +{ + IVP_CarSystemDebugData_t carSystemDebugData; + memset(&carSystemDebugData,0,sizeof(carSystemDebugData)); + m_pCarSystem->GetCarSystemDebugData( carSystemDebugData ); + + // Raycast car wheel trace data. + for ( int iWheel = 0; iWheel < VEHICLE_DEBUGRENDERDATA_MAX_WHEELS; ++iWheel ) + { + debugCarSystem.vecWheelRaycasts[iWheel][0].x = carSystemDebugData.wheelRaycasts[iWheel][0].k[0]; + debugCarSystem.vecWheelRaycasts[iWheel][0].y = carSystemDebugData.wheelRaycasts[iWheel][0].k[1]; + debugCarSystem.vecWheelRaycasts[iWheel][0].z = carSystemDebugData.wheelRaycasts[iWheel][0].k[2]; + + debugCarSystem.vecWheelRaycasts[iWheel][1].x = carSystemDebugData.wheelRaycasts[iWheel][1].k[0]; + debugCarSystem.vecWheelRaycasts[iWheel][1].y = carSystemDebugData.wheelRaycasts[iWheel][1].k[1]; + debugCarSystem.vecWheelRaycasts[iWheel][1].z = carSystemDebugData.wheelRaycasts[iWheel][1].k[2]; + + debugCarSystem.vecWheelRaycastImpacts[iWheel] = debugCarSystem.vecWheelRaycasts[iWheel][0] + ( carSystemDebugData.wheelRaycastImpacts[iWheel] * + ( debugCarSystem.vecWheelRaycasts[iWheel][1] - debugCarSystem.vecWheelRaycasts[iWheel][0] ) ); + } + + ConvertPositionToHL( carSystemDebugData.backActuatorLeft, debugCarSystem.vecAxlePos[0] ); + ConvertPositionToHL( carSystemDebugData.backActuatorRight, debugCarSystem.vecAxlePos[1] ); + ConvertPositionToHL( carSystemDebugData.frontActuatorLeft, debugCarSystem.vecAxlePos[2] ); + // vecAxlePos only has three elements so this line is illegal. The mapping of actuators + // to axles seems dodgy anyway. + //ConvertPositionToHL( carSystemDebugData.frontActuatorRight, debugCarSystem.vecAxlePos[3] ); +} + + +//----------------------------------------------------------------------------- +// Save/load +//----------------------------------------------------------------------------- +void CVehicleController::WriteToTemplate( vphysics_save_cvehiclecontroller_t &controllerTemplate ) +{ + // Get rid of the handbrake flag. The car keeps the flag and will reset it fixing wheels, + // else the system thinks it already fixed the wheels on load and the car roles. + m_vehicleFlags &= ~FVEHICLE_HANDBRAKE_ON; + + controllerTemplate.m_pCarBody = m_pCarBody; + controllerTemplate.m_wheelCount = m_wheelCount; + controllerTemplate.m_wheelRadius = m_wheelRadius; + controllerTemplate.m_bodyMass = m_bodyMass; + controllerTemplate.m_totalWheelMass = m_totalWheelMass; + controllerTemplate.m_gravityLength = m_gravityLength; + controllerTemplate.m_torqueScale = m_torqueScale; + controllerTemplate.m_vehicleFlags = m_vehicleFlags; + controllerTemplate.m_nTireType = m_nTireType; + controllerTemplate.m_nVehicleType = m_nVehicleType; + controllerTemplate.m_bTraceData = m_bTraceData; + controllerTemplate.m_bOccupied = m_bOccupied; + controllerTemplate.m_bEngineDisable = m_bEngineDisable; + memcpy( &controllerTemplate.m_currentState, &m_currentState, sizeof(m_currentState) ); + memcpy( &controllerTemplate.m_vehicleData, &m_vehicleData, sizeof(m_vehicleData) ); + for (int i = 0; i < VEHICLE_MAX_WHEEL_COUNT; ++i ) + { + controllerTemplate.m_pWheels[i] = m_pWheels[i]; + ConvertPositionToHL( m_wheelPosition_Bs[i], controllerTemplate.m_wheelPosition_Bs[i] ); + ConvertPositionToHL( m_tracePosition_Bs[i], controllerTemplate.m_tracePosition_Bs[i] ); + } + m_flVelocity[0] = m_flVelocity[1] = m_flVelocity[2] = 0.0f; + if ( m_pCarBody ) + { + IVP_U_Float_Point &speed = m_pCarBody->GetObject()->get_core()->speed; + controllerTemplate.m_flVelocity[0] = speed.k[0]; + controllerTemplate.m_flVelocity[1] = speed.k[1]; + controllerTemplate.m_flVelocity[2] = speed.k[2]; + } + +} + +// JAY: Keep this around for now while we still have a bunch of games saved with the old +// vehicle controls. We won't ship this, but it lets us debug +#define OLD_SAVED_GAME 1 + +#if OLD_SAVED_GAME +#define SET_DEFAULT(x,y) { if ( x == 0 ) x = y; } +#endif + +void CVehicleController::InitFromTemplate( CPhysicsEnvironment *pEnv, void *pGameData, + IPhysicsGameTrace *pGameTrace, const vphysics_save_cvehiclecontroller_t &controllerTemplate ) +{ + m_pEnv = pEnv; + m_pGameTrace = pGameTrace; + m_pCarBody = controllerTemplate.m_pCarBody; + m_wheelCount = controllerTemplate.m_wheelCount; + m_wheelRadius = controllerTemplate.m_wheelRadius; + m_bodyMass = controllerTemplate.m_bodyMass; + m_totalWheelMass = controllerTemplate.m_totalWheelMass; + m_gravityLength = controllerTemplate.m_gravityLength; + m_torqueScale = controllerTemplate.m_torqueScale; + m_vehicleFlags = controllerTemplate.m_vehicleFlags; + m_nTireType = controllerTemplate.m_nTireType; + m_nVehicleType = controllerTemplate.m_nVehicleType; + m_bTraceData = controllerTemplate.m_bTraceData; + m_bOccupied = controllerTemplate.m_bOccupied; + m_bEngineDisable = controllerTemplate.m_bEngineDisable; + m_pCarSystem = NULL; + memcpy( &m_currentState, &controllerTemplate.m_currentState, sizeof(m_currentState) ); + memcpy( &m_vehicleData, &controllerTemplate.m_vehicleData, sizeof(m_vehicleData) ); + memcpy( &m_flVelocity, controllerTemplate.m_flVelocity, sizeof(m_flVelocity) ); + +#if OLD_SAVED_GAME + SET_DEFAULT( m_torqueScale, 1.0 ); + SET_DEFAULT( m_vehicleData.steering.steeringRateSlow, 4.5 ); + SET_DEFAULT( m_vehicleData.steering.steeringRateFast, 0.5 ); + SET_DEFAULT( m_vehicleData.steering.steeringRestRateSlow, 3.0 ); + SET_DEFAULT( m_vehicleData.steering.steeringRestRateFast, 1.8 ); + SET_DEFAULT( m_vehicleData.steering.speedSlow, m_vehicleData.engine.maxSpeed*0.25 ); + SET_DEFAULT( m_vehicleData.steering.speedFast, m_vehicleData.engine.maxSpeed*0.75 ); + SET_DEFAULT( m_vehicleData.steering.degreesSlow, 50 ); + SET_DEFAULT( m_vehicleData.steering.degreesFast, 18 ); + SET_DEFAULT( m_vehicleData.steering.degreesBoost, 10 ); + + + SET_DEFAULT( m_vehicleData.steering.turnThrottleReduceSlow, 0.3 ); + SET_DEFAULT( m_vehicleData.steering.turnThrottleReduceFast, 3 ); + SET_DEFAULT( m_vehicleData.steering.brakeSteeringRateFactor, 6 ); + SET_DEFAULT( m_vehicleData.steering.throttleSteeringRestRateFactor, 2 ); + SET_DEFAULT( m_vehicleData.steering.boostSteeringRestRateFactor, 1 ); + SET_DEFAULT( m_vehicleData.steering.boostSteeringRateFactor, 1 ); + SET_DEFAULT( m_vehicleData.steering.powerSlideAccel, 200 ); + + SET_DEFAULT( m_vehicleData.engine.autobrakeSpeedGain, 1.0 ); + SET_DEFAULT( m_vehicleData.engine.autobrakeSpeedFactor, 2.0 ); +#endif + + for (int i = 0; i < VEHICLE_MAX_WHEEL_COUNT; ++i ) + { + m_pWheels[i] = controllerTemplate.m_pWheels[i]; + ConvertPositionToIVP( controllerTemplate.m_wheelPosition_Bs[i], m_wheelPosition_Bs[i] ); + ConvertPositionToIVP( controllerTemplate.m_tracePosition_Bs[i], m_tracePosition_Bs[i] ); + } + + CreateIVPObjects( ); + + // HACKHACK: vehicle wheels don't have valid friction at startup, clearing the body's angular velocity keeps + // this fact from affecting the vehicle dynamics in any noticeable way + // using growFriction will re-establish the contact point with moveable objects, but the friction that + // occurs afterward is not the same across the save even when that is extended to include static objects + if ( m_pCarBody ) + { + // clear angVel + m_pCarBody->SetVelocity( NULL, &vec3_origin ); + m_pCarBody->GetObject()->get_core()->speed_change.set( m_flVelocity[0], m_flVelocity[1], m_flVelocity[2] ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleController::OnVehicleEnter( void ) +{ + m_bOccupied = true; + + if ( m_nVehicleType == VEHICLE_TYPE_AIRBOAT_RAYCAST ) + { + float flDampSpeed = 0.0f; + float flDampRotSpeed = 0.0f; + m_pCarBody->SetDamping( &flDampSpeed, &flDampRotSpeed ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleController::OnVehicleExit( void ) +{ + m_bOccupied = false; + + // Reset the vehicle tires when exiting the vehicle. + if ( m_vehicleData.steering.isSkidAllowed ) + { + int iWheel = 0; + for ( int iAxle = 0; iAxle < m_vehicleData.axleCount; ++iAxle ) + { + for ( int iAxleWheel = 0; iAxleWheel < m_vehicleData.wheelsPerAxle; ++iAxleWheel, ++iWheel ) + { + // Change back to normal tires. + if ( m_nTireType != VEHICLE_TIRE_NORMAL ) + { + m_pWheels[iWheel]->SetMaterialIndex( m_vehicleData.axles[iAxle].wheels.materialIndex ); + } + + m_pCarSystem->fix_wheel( ( IVP_POS_WHEEL )iWheel, IVP_TRUE ); + } + } + m_nTireType = VEHICLE_TIRE_NORMAL; + m_currentState.skidSpeed = 0.0f; + } + + if ( m_nVehicleType == VEHICLE_TYPE_AIRBOAT_RAYCAST ) + { + float flDampSpeed = 1.0f; + float flDampRotSpeed = 1.0f; + m_pCarBody->SetDamping( &flDampSpeed, &flDampRotSpeed ); + } + + SetEngineDisabled( false ); +} + +//----------------------------------------------------------------------------- +// Class factory +//----------------------------------------------------------------------------- +IPhysicsVehicleController *CreateVehicleController( CPhysicsEnvironment *pEnv, CPhysicsObject *pBodyObject, const vehicleparams_t ¶ms, unsigned int nVehicleType, IPhysicsGameTrace *pGameTrace ) +{ + CVehicleController *pController = new CVehicleController( params, pEnv, nVehicleType, pGameTrace ); + pController->InitCarSystem( pBodyObject ); + return pController; +} + +bool SavePhysicsVehicleController( const physsaveparams_t ¶ms, CVehicleController *pVehicleController ) +{ + vphysics_save_cvehiclecontroller_t controllerTemplate; + memset( &controllerTemplate, 0, sizeof(controllerTemplate) ); + + pVehicleController->WriteToTemplate( controllerTemplate ); + params.pSave->WriteAll( &controllerTemplate ); + + return true; +} + +bool RestorePhysicsVehicleController( const physrestoreparams_t ¶ms, CVehicleController **ppVehicleController ) +{ + *ppVehicleController = new CVehicleController; + + vphysics_save_cvehiclecontroller_t controllerTemplate; + memset( &controllerTemplate, 0, sizeof(controllerTemplate) ); + params.pRestore->ReadAll( &controllerTemplate ); + + (*ppVehicleController)->InitFromTemplate( static_cast<CPhysicsEnvironment *>(params.pEnvironment), + params.pGameData, params.pGameTrace, controllerTemplate ); + + return true; +} + + |