//========= 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(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( m_pCarSystem )->GetWheel( index ); } else if ( m_nVehicleType == VEHICLE_TYPE_AIRBOAT_RAYCAST && m_pCarSystem ) { return static_cast( m_pCarSystem )->GetWheel( index ); } return NULL; } void CVehicleController::SetWheelFriction(int wheelIndex, float friction) { CPhysics_Airboat *pAirboat = static_cast( 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(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(params.pEnvironment), params.pGameData, params.pGameTrace, controllerTemplate ); return true; }