summaryrefslogtreecommitdiff
path: root/vphysics/physics_airboat.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'vphysics/physics_airboat.cpp')
-rw-r--r--vphysics/physics_airboat.cpp1796
1 files changed, 1796 insertions, 0 deletions
diff --git a/vphysics/physics_airboat.cpp b/vphysics/physics_airboat.cpp
new file mode 100644
index 0000000..1437fa4
--- /dev/null
+++ b/vphysics/physics_airboat.cpp
@@ -0,0 +1,1796 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The airboat, a sporty nimble water craft.
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "physics_airboat.h"
+#include "cmodel.h"
+#include <ivp_ray_solver.hxx>
+
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#ifdef _X360
+ #define AIRBOAT_STEERING_RATE_MIN 0.000225f
+ #define AIRBOAT_STEERING_RATE_MAX (10.0f * AIRBOAT_STEERING_RATE_MIN)
+ #define AIRBOAT_STEERING_INTERVAL 1.5f
+#else
+ #define AIRBOAT_STEERING_RATE_MIN 0.00045f
+ #define AIRBOAT_STEERING_RATE_MAX (5.0f * AIRBOAT_STEERING_RATE_MIN)
+ #define AIRBOAT_STEERING_INTERVAL 0.5f
+#endif //_X360
+
+#define AIRBOAT_ROT_DRAG 0.00004f
+#define AIRBOAT_ROT_DAMPING 0.001f
+
+// Mass-independent thrust values
+#define AIRBOAT_THRUST_MAX 11.0f // N / kg
+#define AIRBOAT_THRUST_MAX_REVERSE 7.5f // N / kg
+
+// Mass-independent drag values
+#define AIRBOAT_WATER_DRAG_LEFT_RIGHT 0.6f
+#define AIRBOAT_WATER_DRAG_FORWARD_BACK 0.005f
+#define AIRBOAT_WATER_DRAG_UP_DOWN 0.0025f
+
+#define AIRBOAT_GROUND_DRAG_LEFT_RIGHT 2.0
+#define AIRBOAT_GROUND_DRAG_FORWARD_BACK 1.0
+#define AIRBOAT_GROUND_DRAG_UP_DOWN 0.8
+
+#define AIRBOAT_DRY_FRICTION_SCALE 0.6f // unitless, reduces our friction on all surfaces other than water
+
+#define AIRBOAT_RAYCAST_DIST 0.35f // m (~14in)
+#define AIRBOAT_RAYCAST_DIST_WATER_LOW 0.1f // m (~4in)
+#define AIRBOAT_RAYCAST_DIST_WATER_HIGH 0.35f // m (~16in)
+
+// Amplitude of wave noise. Blend from max to min as speed increases.
+#define AIRBOAT_WATER_NOISE_MIN 0.01 // m (~0.4in)
+#define AIRBOAT_WATER_NOISE_MAX 0.03 // m (~1.2in)
+
+// Frequency of wave noise. Blend from min to max as speed increases.
+#define AIRBOAT_WATER_FREQ_MIN 1.5
+#define AIRBOAT_WATER_FREQ_MAX 1.5
+
+// Phase difference in wave noise between left and right pontoons
+// Blend from max to min as speed increases.
+#define AIRBOAT_WATER_PHASE_MIN 0.0 // s
+#define AIRBOAT_WATER_PHASE_MAX 1.5 // s
+
+
+#define AIRBOAT_GRAVITY 9.81f // m/s2
+
+// Pontoon indices
+enum
+{
+ AIRBOAT_PONTOON_FRONT_LEFT = 0,
+ AIRBOAT_PONTOON_FRONT_RIGHT,
+ AIRBOAT_PONTOON_REAR_LEFT,
+ AIRBOAT_PONTOON_REAR_RIGHT,
+};
+
+
+class IVP_Ray_Solver_Template;
+class IVP_Ray_Hit;
+class IVP_Event_Sim;
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+class CAirboatFrictionData : public IPhysicsCollisionData
+{
+public:
+ CAirboatFrictionData()
+ {
+ m_vecPoint.Init( 0, 0, 0 );
+ m_vecNormal.Init( 0, 0, 0 );
+ m_vecVelocity.Init( 0, 0, 0 );
+ }
+
+ virtual void GetSurfaceNormal( Vector &out )
+ {
+ out = m_vecPoint;
+ }
+
+ virtual void GetContactPoint( Vector &out )
+ {
+ out = m_vecNormal;
+ }
+
+ virtual void GetContactSpeed( Vector &out )
+ {
+ out = m_vecVelocity;
+ }
+
+public:
+ Vector m_vecPoint;
+ Vector m_vecNormal;
+ Vector m_vecVelocity;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CPhysics_Airboat::CPhysics_Airboat( IVP_Environment *pEnv, const IVP_Template_Car_System *pCarSystem,
+ IPhysicsGameTrace *pGameTrace )
+{
+ InitRaycastCarBody( pCarSystem );
+ InitRaycastCarEnvironment( pEnv, pCarSystem );
+ InitRaycastCarWheels( pCarSystem );
+ InitRaycastCarAxes( pCarSystem );
+
+ InitAirboat( pCarSystem );
+ m_pGameTrace = pGameTrace;
+
+ m_SteeringAngle = 0;
+ m_bSteeringReversed = false;
+
+ m_flThrust = 0;
+
+ m_bAirborne = false;
+ m_flAirTime = 0;
+ m_bWeakJump = false;
+
+ m_flPitchErrorPrev = 0;
+ m_flRollErrorPrev = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Deconstructor
+//-----------------------------------------------------------------------------
+CPhysics_Airboat::~CPhysics_Airboat()
+{
+ m_pAirboatBody->get_environment()->get_controller_manager()->remove_controller_from_environment( this, IVP_TRUE );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Setup the car system wheels.
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::InitAirboat( const IVP_Template_Car_System *pCarSystem )
+{
+ for ( int iWheel = 0; iWheel < pCarSystem->n_wheels; ++iWheel )
+ {
+ m_pWheels[iWheel] = pCarSystem->car_wheel[iWheel];
+ m_pWheels[iWheel]->enable_collision_detection( IVP_FALSE );
+ }
+
+ CPhysicsObject* pBodyObject = static_cast<CPhysicsObject*>(pCarSystem->car_body->client_data);
+
+ pBodyObject->EnableGravity( false );
+
+ // We do our own buoyancy simulation.
+ pBodyObject->SetCallbackFlags( pBodyObject->GetCallbackFlags() & ~CALLBACK_DO_FLUID_SIMULATION );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the raycast wheel.
+//-----------------------------------------------------------------------------
+IPhysicsObject *CPhysics_Airboat::GetWheel( int index )
+{
+ Assert( index >= 0 );
+ Assert( index < n_wheels );
+
+ return ( IPhysicsObject* )m_pWheels[index]->client_data;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::SetWheelFriction( int iWheel, float flFriction )
+{
+ change_friction_of_wheel( IVP_POS_WHEEL( iWheel ), flFriction );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns an amount to add to the front pontoon raycasts to simulate wave noise.
+// Input : nPontoonIndex - Which pontoon we're dealing with (0 or 1).
+// flSpeedRatio - Speed as a ratio of max speed [0..1]
+//-----------------------------------------------------------------------------
+float CPhysics_Airboat::ComputeFrontPontoonWaveNoise( int nPontoonIndex, float flSpeedRatio )
+{
+ // Add in sinusoidal noise cause by undulating water. Reduce the amplitude of the noise at higher speeds.
+ IVP_FLOAT flNoiseScale = RemapValClamped( 1.0 - flSpeedRatio, 0, 1, AIRBOAT_WATER_NOISE_MIN, AIRBOAT_WATER_NOISE_MAX );
+
+ // Apply a phase shift between left and right pontoons to simulate waves passing under the boat.
+ IVP_FLOAT flPhaseShift = 0;
+ if ( flSpeedRatio < 0.3 )
+ {
+ // BUG: this allows a discontinuity in the waveform - use two superimposed sine waves instead?
+ flPhaseShift = nPontoonIndex * AIRBOAT_WATER_PHASE_MAX;
+ }
+
+ // Increase the wave frequency as speed increases.
+ IVP_FLOAT flFrequency = RemapValClamped( flSpeedRatio, 0, 1, AIRBOAT_WATER_FREQ_MIN, AIRBOAT_WATER_FREQ_MAX );
+
+ //Msg( "Wave amp=%f, freq=%f, phase=%f\n", flNoiseScale, flFrequency, flPhaseShift );
+ return flNoiseScale * sin( flFrequency * ( m_pCore->environment->get_current_time().get_seconds() + flPhaseShift ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:: Convert data to HL2 measurements, and test direction of raycast.
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::pre_raycasts_gameside( int nRaycastCount, IVP_Ray_Solver_Template *pRays,
+ Ray_t *pGameRays, IVP_Raycast_Airboat_Impact *pImpacts )
+{
+ IVP_FLOAT flForwardSpeedRatio = clamp( m_vecLocalVelocity.k[2] / 10.0f, 0.f, 1.0f );
+ //Msg( "flForwardSpeedRatio = %f\n", flForwardSpeedRatio );
+
+ IVP_FLOAT flSpeed = ( IVP_FLOAT )m_pCore->speed.real_length();
+ IVP_FLOAT flSpeedRatio = clamp( flSpeed / 15.0f, 0.f, 1.0f );
+ if ( !m_flThrust )
+ {
+ flForwardSpeedRatio *= 0.5;
+ }
+
+ // This is a little weird. We adjust the front pontoon ray lengths based on forward velocity,
+ // but ONLY if both pontoons are in the water, which we won't know until we do the raycast.
+ // So we do most of the work here, and cache some of the results to use them later.
+ Vector vecStart[4];
+ Vector vecDirection[4];
+ Vector vecZero( 0.0f, 0.0f, 0.0f );
+
+ int nFrontPontoonsInWater = 0;
+
+ int iRaycast;
+ for ( iRaycast = 0; iRaycast < nRaycastCount; ++iRaycast )
+ {
+ // Setup the ray.
+ ConvertPositionToHL( pRays[iRaycast].ray_start_point, vecStart[iRaycast] );
+ ConvertDirectionToHL( pRays[iRaycast].ray_normized_direction, vecDirection[iRaycast] );
+
+ float flRayLength = IVP2HL( pRays[iRaycast].ray_length );
+
+ // Check to see if that point is in water.
+ pImpacts[iRaycast].bInWater = IVP_FALSE;
+ if ( m_pGameTrace->VehiclePointInWater( vecStart[iRaycast] ) )
+ {
+ vecDirection[iRaycast].Negate();
+ pImpacts[iRaycast].bInWater = IVP_TRUE;
+ }
+
+ Vector vecEnd = vecStart[iRaycast] + ( vecDirection[iRaycast] * flRayLength );
+
+ // Adjust the trace if the pontoon is in the water.
+ if ( m_pGameTrace->VehiclePointInWater( vecEnd ) )
+ {
+ // Reduce the ray length in the water.
+ pRays[iRaycast].ray_length = AIRBOAT_RAYCAST_DIST_WATER_LOW;
+
+ if ( iRaycast < 2 )
+ {
+ nFrontPontoonsInWater++;
+
+ // Front pontoons.
+ // Add a little sinusoidal noise to simulate waves.
+ IVP_FLOAT flNoise = ComputeFrontPontoonWaveNoise( iRaycast, flSpeedRatio );
+ pRays[iRaycast].ray_length += flNoise;
+ }
+ else
+ {
+ // Recalculate the end position in HL coordinates.
+ flRayLength = IVP2HL( pRays[iRaycast].ray_length );
+ vecEnd = vecStart[iRaycast] + ( vecDirection[iRaycast] * flRayLength );
+ }
+ }
+
+ pGameRays[iRaycast].Init( vecStart[iRaycast], vecEnd, vecZero, vecZero );
+ }
+
+ // If both front pontoons are in the water, add in a bit of lift proportional to our
+ // forward speed. We can't do this to only one of the front pontoons because it causes
+ // some twist if we do.
+ // FIXME: this does some redundant work (computes the wave noise again)
+ if ( nFrontPontoonsInWater == 2 )
+ {
+ for ( int i = 0; i < 2; i++ )
+ {
+ // Front pontoons.
+ // Raise it higher out of the water as we go faster forward.
+ pRays[i].ray_length = RemapValClamped( flForwardSpeedRatio, 0, 1, AIRBOAT_RAYCAST_DIST_WATER_LOW, AIRBOAT_RAYCAST_DIST_WATER_HIGH );
+
+ // Add a little sinusoidal noise to simulate waves.
+ IVP_FLOAT flNoise = ComputeFrontPontoonWaveNoise( i, flSpeedRatio );
+ pRays[i].ray_length += flNoise;
+
+ // Recalculate the end position in HL coordinates.
+ float flRayLength = IVP2HL( pRays[i].ray_length );
+ Vector vecEnd = vecStart[i] + ( vecDirection[i] * flRayLength );
+
+ pGameRays[i].Init( vecStart[i], vecEnd, vecZero, vecZero );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CPhysics_Airboat::GetWaterDepth( Ray_t *pGameRay, IPhysicsObject *pPhysAirboat )
+{
+ float flDepth = 0.0f;
+
+ trace_t trace;
+
+ Ray_t waterRay;
+ Vector vecStart = pGameRay->m_Start;
+ Vector vecEnd( vecStart.x, vecStart.y, vecStart.z + 1000.0f );
+ Vector vecZero( 0.0f, 0.0f, 0.0f );
+ waterRay.Init( vecStart, vecEnd, vecZero, vecZero );
+ m_pGameTrace->VehicleTraceRayWithWater( waterRay, pPhysAirboat->GetGameData(), &trace );
+
+ flDepth = 1000.0f * trace.fractionleftsolid;
+
+ return flDepth;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Performs traces to figure out what is at each of the raycast points
+// and fills out the pImpacts array with that information.
+// Input : nRaycastCount - Number of elements in the arrays pointed to by pRays
+// and pImpacts.
+// pRays - Holds the rays to trace with.
+// pImpacts - Receives the trace results.
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::do_raycasts_gameside( int nRaycastCount, IVP_Ray_Solver_Template *pRays,
+ IVP_Raycast_Airboat_Impact *pImpacts )
+{
+ Assert( nRaycastCount >= 0 );
+ Assert( nRaycastCount <= IVP_RAYCAST_AIRBOAT_MAX_WHEELS );
+
+ Ray_t gameRays[IVP_RAYCAST_AIRBOAT_MAX_WHEELS];
+ pre_raycasts_gameside( nRaycastCount, pRays, gameRays, pImpacts );
+
+ // Do the raycasts and set impact data.
+ trace_t trace;
+ for ( int iRaycast = 0; iRaycast < nRaycastCount; ++iRaycast )
+ {
+ // Trace.
+ if ( pImpacts[iRaycast].bInWater )
+ {
+ // The start position is underwater. Trace up to find the water surface.
+ IPhysicsObject *pPhysAirboat = static_cast<IPhysicsObject*>( m_pAirboatBody->client_data );
+ m_pGameTrace->VehicleTraceRay( gameRays[iRaycast], pPhysAirboat->GetGameData(), &trace );
+ pImpacts[iRaycast].flDepth = GetWaterDepth( &gameRays[iRaycast], pPhysAirboat );
+ }
+ else
+ {
+ // Trace down to find the ground or water.
+ IPhysicsObject *pPhysAirboat = static_cast<IPhysicsObject*>( m_pAirboatBody->client_data );
+ m_pGameTrace->VehicleTraceRayWithWater( gameRays[iRaycast], pPhysAirboat->GetGameData(), &trace );
+ }
+
+ ConvertPositionToIVP( gameRays[iRaycast].m_Start + gameRays[iRaycast].m_StartOffset, m_CarSystemDebugData.wheelRaycasts[iRaycast][0] );
+ ConvertPositionToIVP( gameRays[iRaycast].m_Start + gameRays[iRaycast].m_StartOffset + gameRays[iRaycast].m_Delta, m_CarSystemDebugData.wheelRaycasts[iRaycast][1] );
+ m_CarSystemDebugData.wheelRaycastImpacts[iRaycast] = trace.fraction * gameRays[iRaycast].m_Delta.Length();
+
+ // Set impact data.
+ pImpacts[iRaycast].bImpactWater = IVP_FALSE;
+ pImpacts[iRaycast].bImpact = IVP_FALSE;
+ if ( trace.fraction != 1.0f )
+ {
+ pImpacts[iRaycast].bImpact = IVP_TRUE;
+
+ // Set water surface flag.
+ pImpacts[iRaycast].flDepth = 0.0f;
+ if ( trace.contents & MASK_WATER )
+ {
+ pImpacts[iRaycast].bImpactWater = IVP_TRUE;
+ }
+
+ // Save impact surface data.
+ ConvertPositionToIVP( trace.endpos, pImpacts[iRaycast].vecImpactPointWS );
+ ConvertDirectionToIVP( trace.plane.normal, pImpacts[iRaycast].vecImpactNormalWS );
+
+ // Save surface properties.
+ const surfacedata_t *pSurfaceData = physprops->GetSurfaceData( trace.surface.surfaceProps );
+
+ pImpacts[iRaycast].nSurfaceProps = trace.surface.surfaceProps;
+
+ if (pImpacts[iRaycast].vecImpactNormalWS.k[1] < -0.707)
+ {
+ // dampening is 1/t, where t is how long it takes to come to a complete stop
+ pImpacts[iRaycast].flDampening = pSurfaceData->physics.dampening;
+ pImpacts[iRaycast].flFriction = pSurfaceData->physics.friction;
+ }
+ else
+ {
+ // This surface is too vertical -- no friction or damping from it.
+ pImpacts[iRaycast].flDampening = pSurfaceData->physics.dampening;
+ pImpacts[iRaycast].flFriction = pSurfaceData->physics.friction;
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Entry point for airboat simulation.
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::do_simulation_controller( IVP_Event_Sim *pEventSim, IVP_U_Vector<IVP_Core> * )
+{
+ IVP_Ray_Solver_Template raySolverTemplates[IVP_RAYCAST_AIRBOAT_MAX_WHEELS];
+ IVP_Raycast_Airboat_Impact impacts[IVP_RAYCAST_AIRBOAT_MAX_WHEELS];
+
+ // Cache some data into members here so we only do the work once.
+ m_pCore = m_pAirboatBody->get_core();
+ const IVP_U_Matrix *matWorldFromCore = m_pCore->get_m_world_f_core_PSI();
+
+ // Cache the speed.
+ m_flSpeed = ( IVP_FLOAT )m_pCore->speed.real_length();
+
+ // Cache the local velocity vector.
+ matWorldFromCore->vimult3(&m_pCore->speed, &m_vecLocalVelocity);
+
+ // Raycasts.
+ PreRaycasts( raySolverTemplates, matWorldFromCore, impacts );
+ do_raycasts_gameside( n_wheels, raySolverTemplates, impacts );
+ if ( !PostRaycasts( raySolverTemplates, matWorldFromCore, impacts ) )
+ return;
+
+ UpdateAirborneState( impacts, pEventSim );
+
+ // Enumerate the controllers attached to us.
+ //for (int i = m_pCore->controllers_of_core.len() - 1; i >= 0; i--)
+ //{
+ // IVP_Controller *pController = m_pCore->controllers_of_core.element_at(i);
+ //}
+
+ // Pontoons. Buoyancy or ground impacts.
+ DoSimulationPontoons( impacts, pEventSim );
+
+ // Drag due to water and ground friction.
+ DoSimulationDrag( impacts, pEventSim );
+
+ // Turbine (fan).
+ DoSimulationTurbine( pEventSim );
+
+ // Steering.
+ DoSimulationSteering( pEventSim );
+
+ // Anti-pitch.
+ DoSimulationKeepUprightPitch( impacts, pEventSim );
+
+ // Anti-roll.
+ DoSimulationKeepUprightRoll( impacts, pEventSim );
+
+ // Additional gravity based on speed.
+ DoSimulationGravity( pEventSim );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Initialize the rays to be cast from the vehicle wheel positions to
+// the "ground."
+// Input : pRaySolverTemplates -
+// matWorldFromCore -
+// pImpacts -
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::PreRaycasts( IVP_Ray_Solver_Template *pRaySolverTemplates,
+ const IVP_U_Matrix *matWorldFromCore,
+ IVP_Raycast_Airboat_Impact *pImpacts )
+{
+ int nPontoonPoints = n_wheels;
+ for ( int iPoint = 0; iPoint < nPontoonPoints; ++iPoint )
+ {
+ IVP_Raycast_Airboat_Wheel *pPontoonPoint = get_wheel( IVP_POS_WHEEL( iPoint ) );
+ if ( pPontoonPoint )
+ {
+ // Fill the in the ray solver template for the current wheel.
+ IVP_Ray_Solver_Template &raySolverTemplate = pRaySolverTemplates[iPoint];
+
+ // Transform the wheel "start" position from vehicle core-space to world-space. This is
+ // the raycast starting position.
+ matWorldFromCore->vmult4( &pPontoonPoint->raycast_start_cs, &raySolverTemplate.ray_start_point );
+
+ // Transform the shock (spring) direction from vehicle core-space to world-space. This is
+ // the raycast direction.
+ matWorldFromCore->vmult3( &pPontoonPoint->raycast_dir_cs, &pImpacts[iPoint].raycast_dir_ws );
+ raySolverTemplate.ray_normized_direction.set( &pImpacts[iPoint].raycast_dir_ws );
+
+ // Set the length of the ray cast.
+ raySolverTemplate.ray_length = AIRBOAT_RAYCAST_DIST;
+
+ // Set the ray solver template flags. This defines which objects you wish to
+ // collide against in the physics environment.
+ raySolverTemplate.ray_flags = IVP_RAY_SOLVER_ALL;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Determines whether we are airborne and whether we just performed a
+// weak or strong jump. Weak jumps are jumps at below a threshold speed,
+// and disable the turbine and pitch controller.
+// Input : pImpacts -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::UpdateAirborneState( IVP_Raycast_Airboat_Impact *pImpacts, IVP_Event_Sim *pEventSim )
+{
+ int nCount = CountSurfaceContactPoints(pImpacts);
+ if (!nCount)
+ {
+ if (!m_bAirborne)
+ {
+ m_bAirborne = true;
+ m_flAirTime = 0;
+
+ IVP_FLOAT flSpeed = ( IVP_FLOAT )m_pCore->speed.real_length();
+ if (flSpeed < 11.0f)
+ {
+ //Msg("*** WEAK JUMP at %f!!!\n", flSpeed);
+ m_bWeakJump = true;
+ }
+ else
+ {
+ //Msg("Strong JUMP at %f\n", flSpeed);
+ }
+ }
+ else
+ {
+ m_flAirTime += pEventSim->delta_time;
+ }
+ }
+ else
+ {
+ m_bAirborne = false;
+ m_bWeakJump = false;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CPhysics_Airboat::PostRaycasts( IVP_Ray_Solver_Template *pRaySolverTemplates, const IVP_U_Matrix *matWorldFromCore,
+ IVP_Raycast_Airboat_Impact *pImpacts )
+{
+ bool bReturn = true;
+
+ int nPontoonPoints = n_wheels;
+ for( int iPoint = 0; iPoint < nPontoonPoints; ++iPoint )
+ {
+ // Get data at raycast position.
+ IVP_Raycast_Airboat_Wheel *pPontoonPoint = get_wheel( IVP_POS_WHEEL( iPoint ) );
+ IVP_Raycast_Airboat_Impact *pImpact = &pImpacts[iPoint];
+ IVP_Ray_Solver_Template *pRaySolver = &pRaySolverTemplates[iPoint];
+ if ( !pPontoonPoint || !pImpact || !pRaySolver )
+ continue;
+
+ // Copy the ray length back, it may have changed.
+ pPontoonPoint->raycast_length = pRaySolver->ray_length;
+
+ // Test for inverted raycast direction.
+ if ( pImpact->bInWater )
+ {
+ pImpact->raycast_dir_ws.set_multiple( &pImpact->raycast_dir_ws, -1 );
+ }
+
+ // Impact.
+ if ( pImpact->bImpact )
+ {
+ // Save impact distance.
+ IVP_U_Point vecDelta;
+ vecDelta.subtract( &pImpact->vecImpactPointWS, &pRaySolver->ray_start_point );
+ pPontoonPoint->raycast_dist = vecDelta.real_length();
+
+ // Get the inverse portion of the surface normal in the direction of the ray cast (shock - used in the shock simulation code for the sign
+ // and percentage of force applied to the shock).
+ pImpact->inv_normal_dot_dir = 1.1f / ( IVP_Inline_Math::fabsd( pImpact->raycast_dir_ws.dot_product( &pImpact->vecImpactNormalWS ) ) + 0.1f );
+
+ // Set the wheel friction - ground friction (if any) + wheel friction.
+ pImpact->friction_value = pImpact->flFriction * pPontoonPoint->friction_of_wheel;
+ }
+ // No impact.
+ else
+ {
+ pPontoonPoint->raycast_dist = pPontoonPoint->raycast_length;
+
+ pImpact->inv_normal_dot_dir = 1.0f;
+ pImpact->moveable_object_hit_by_ray = NULL;
+ pImpact->vecImpactNormalWS.set_multiple( &pImpact->raycast_dir_ws, -1 );
+ pImpact->friction_value = 1.0f;
+ }
+
+ // Set the new wheel position (the impact point or the full ray distance). Make this from the wheel not the ray trace position.
+ pImpact->vecImpactPointWS.add_multiple( &pRaySolver->ray_start_point, &pImpact->raycast_dir_ws, pPontoonPoint->raycast_dist );
+
+ // Get the speed (velocity) at the impact point.
+ m_pCore->get_surface_speed_ws( &pImpact->vecImpactPointWS, &pImpact->surface_speed_wheel_ws );
+ pImpact->projected_surface_speed_wheel_ws.set_orthogonal_part( &pImpact->surface_speed_wheel_ws, &pImpact->vecImpactNormalWS );
+
+ matWorldFromCore->vmult3( &pPontoonPoint->axis_direction_cs, &pImpact->axis_direction_ws );
+ pImpact->projected_axis_direction_ws.set_orthogonal_part( &pImpact->axis_direction_ws, &pImpact->vecImpactNormalWS );
+ if ( pImpact->projected_axis_direction_ws.normize() == IVP_FAULT )
+ {
+ DevMsg( "CPhysics_Airboat::do_simulation_controller projected_axis_direction_ws.normize failed\n" );
+ bReturn = false;
+ }
+ }
+
+ return bReturn;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::DoSimulationPontoons( IVP_Raycast_Airboat_Impact *pImpacts, IVP_Event_Sim *pEventSim )
+{
+ int nPontoonPoints = n_wheels;
+ for ( int iPoint = 0; iPoint < nPontoonPoints; ++iPoint )
+ {
+ IVP_Raycast_Airboat_Wheel *pPontoonPoint = get_wheel( IVP_POS_WHEEL( iPoint ) );
+ if ( !pPontoonPoint )
+ continue;
+
+ if ( pImpacts[iPoint].bImpact )
+ {
+ DoSimulationPontoonsGround( pPontoonPoint, &pImpacts[iPoint], pEventSim );
+ }
+ else if ( pImpacts[iPoint].bInWater )
+ {
+ DoSimulationPontoonsWater( pPontoonPoint, &pImpacts[iPoint], pEventSim );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle pontoons on ground.
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::DoSimulationPontoonsGround( IVP_Raycast_Airboat_Wheel *pPontoonPoint,
+ IVP_Raycast_Airboat_Impact *pImpact, IVP_Event_Sim *pEventSim )
+{
+ // Check to see if we hit anything, otherwise the no force on this point.
+ IVP_DOUBLE flDiff = pPontoonPoint->raycast_dist - pPontoonPoint->raycast_length;
+ if ( flDiff >= 0 )
+ return;
+
+ IVP_FLOAT flSpringConstant, flSpringRelax, flSpringCompress;
+ flSpringConstant = pPontoonPoint->spring_constant;
+ flSpringRelax = pPontoonPoint->spring_damp_relax;
+ flSpringCompress = pPontoonPoint->spring_damp_compress;
+
+ IVP_DOUBLE flForce = -flDiff * flSpringConstant;
+ IVP_FLOAT flInvNormalDotDir = clamp(pImpact->inv_normal_dot_dir, 0.0f, 3.0f);
+ flForce *= flInvNormalDotDir;
+
+ IVP_U_Float_Point vecSpeedDelta;
+ vecSpeedDelta.subtract( &pImpact->projected_surface_speed_wheel_ws, &pImpact->surface_speed_wheel_ws );
+
+ IVP_DOUBLE flSpeed = vecSpeedDelta.dot_product( &pImpact->raycast_dir_ws );
+ if ( flSpeed > 0 )
+ {
+ flForce -= flSpringRelax * flSpeed;
+ }
+ else
+ {
+ flForce -= flSpringCompress * flSpeed;
+ }
+
+ if ( flForce < 0 )
+ {
+ flForce = 0.0f;
+ }
+
+ // NOTE: Spring constants are all mass-independent, so no need to multiply by mass here.
+ IVP_DOUBLE flImpulse = flForce * pEventSim->delta_time;
+
+ IVP_U_Float_Point vecImpulseWS;
+ vecImpulseWS.set_multiple( &pImpact->vecImpactNormalWS, flImpulse );
+ m_pCore->push_core_ws( &pImpact->vecImpactPointWS, &vecImpulseWS );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle pontoons on water.
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::DoSimulationPontoonsWater( IVP_Raycast_Airboat_Wheel *pPontoonPoint,
+ IVP_Raycast_Airboat_Impact *pImpact, IVP_Event_Sim *pEventSim )
+{
+ #define AIRBOAT_BUOYANCY_SCALAR 1.6f
+ #define PONTOON_AREA_2D 2.8f // 2 pontoons x 16 in x 136 in = 4352 sq inches = 2.8 sq meters
+ #define PONTOON_HEIGHT 0.41f // 16 inches high = 0.41 meters
+
+ float flDepth = clamp( pImpact->flDepth, 0.f, PONTOON_HEIGHT );
+ //Msg("depth: %f\n", pImpact->flDepth);
+
+ // Depth is in inches, so multiply by 0.0254 meters/inch
+ IVP_FLOAT flSubmergedVolume = PONTOON_AREA_2D * flDepth * 0.0254;
+
+ // Buoyancy forces are equal to the mass of the water displaced, which is 1000 kg/m^3
+ // There are 4 pontoon points, so each one can exert 1/4th of the total buoyancy force.
+ IVP_FLOAT flForce = AIRBOAT_BUOYANCY_SCALAR * 0.25f * m_pCore->get_mass() * flSubmergedVolume * 1000.0f;
+ IVP_DOUBLE flImpulse = flForce * pEventSim->delta_time;
+
+ IVP_U_Float_Point vecImpulseWS;
+ vecImpulseWS.set( 0, -1, 0 );
+ vecImpulseWS.mult( flImpulse );
+ m_pCore->push_core_ws( &pImpact->vecImpactPointWS, &vecImpulseWS );
+
+// Vector vecPoint;
+// Vector vecDir(0, 0, 1);
+//
+// ConvertPositionToHL( pImpact->vecImpactPointWS, vecPoint );
+// CPhysicsEnvironment *pEnv = (CPhysicsEnvironment *)m_pAirboatBody->get_core()->environment->client_data;
+// IVPhysicsDebugOverlay *debugoverlay = pEnv->GetDebugOverlay();
+// debugoverlay->AddLineOverlay(vecPoint, vecPoint + vecDir * 128, 255, 0, 255, false, 10.0 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::PerformFrictionNotification( float flEliminatedEnergy, float dt, int nSurfaceProp, IPhysicsCollisionData *pCollisionData )
+{
+ CPhysicsObject *pPhysAirboat = static_cast<CPhysicsObject*>( m_pAirboatBody->client_data );
+ if ( ( pPhysAirboat->CallbackFlags() & CALLBACK_GLOBAL_FRICTION ) == 0 )
+ return;
+
+ IPhysicsCollisionEvent *pEventHandler = pPhysAirboat->GetVPhysicsEnvironment()->GetCollisionEventHandler();
+ if ( !pEventHandler )
+ return;
+
+ // scrape with an estimate for the energy per unit mass
+ // This assumes that the game is interested in some measure of vibration
+ // for sound effects. This also assumes that more massive objects require
+ // more energy to vibrate.
+ flEliminatedEnergy *= dt / pPhysAirboat->GetMass();
+ if ( flEliminatedEnergy > 0.05f )
+ {
+ pEventHandler->Friction( pPhysAirboat, flEliminatedEnergy, pPhysAirboat->GetMaterialIndexInternal(), nSurfaceProp, pCollisionData );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Drag due to water and ground friction.
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::DoSimulationDrag( IVP_Raycast_Airboat_Impact *pImpacts,
+ IVP_Event_Sim *pEventSim )
+{
+ const IVP_U_Matrix *matWorldFromCore = m_pCore->get_m_world_f_core_PSI();
+ IVP_FLOAT flSpeed = ( IVP_FLOAT )m_pCore->speed.real_length();
+
+ // Used to make airboat sliding sounds
+ CAirboatFrictionData frictionData;
+ ConvertDirectionToHL( m_pCore->speed, frictionData.m_vecVelocity );
+
+ // Count the pontoons in the water.
+ int nPontoonPoints = n_wheels;
+ int nPointsInWater = 0;
+ int nPointsOnGround = 0;
+ float flGroundFriction = 0;
+ float flAverageDampening = 0.0f;
+ int *pSurfacePropCount = (int *)stackalloc( n_wheels * sizeof(int) );
+ int *pSurfaceProp = (int *)stackalloc( n_wheels * sizeof(int) );
+ memset( pSurfacePropCount, 0, n_wheels * sizeof(int) );
+ memset( pSurfaceProp, 0xFF, n_wheels * sizeof(int) );
+ int nSurfacePropCount = 0;
+ int nMaxSurfacePropIdx = 0;
+ for( int iPoint = 0; iPoint < nPontoonPoints; ++iPoint )
+ {
+ // Get data at raycast position.
+ IVP_Raycast_Airboat_Impact *pImpact = &pImpacts[iPoint];
+ if ( !pImpact || !pImpact->bImpact )
+ continue;
+
+ if ( pImpact->bImpactWater )
+ {
+ flAverageDampening += pImpact->flDampening;
+ nPointsInWater++;
+ }
+ else
+ {
+ flGroundFriction += pImpact->flFriction;
+ nPointsOnGround++;
+
+ // This logic is used to determine which surface prop we hit the most.
+ int i;
+ for ( i = 0; i < nSurfacePropCount; ++i )
+ {
+ if ( pSurfaceProp[i] == pImpact->nSurfaceProps )
+ break;
+ }
+
+ if ( i == nSurfacePropCount )
+ {
+ ++nSurfacePropCount;
+ }
+ pSurfaceProp[i] = pImpact->nSurfaceProps;
+ if ( ++pSurfacePropCount[i] > pSurfacePropCount[nMaxSurfacePropIdx] )
+ {
+ nMaxSurfacePropIdx = i;
+ }
+
+ Vector frictionPoint, frictionNormal;
+ ConvertPositionToHL( pImpact->vecImpactPointWS, frictionPoint );
+ ConvertDirectionToHL( pImpact->vecImpactNormalWS, frictionNormal );
+ frictionData.m_vecPoint += frictionPoint;
+ frictionData.m_vecNormal += frictionNormal;
+ }
+ }
+
+ int nSurfaceProp = pSurfaceProp[nMaxSurfacePropIdx];
+ if ( nPointsOnGround > 0 )
+ {
+ frictionData.m_vecPoint /= nPointsOnGround;
+ frictionData.m_vecNormal /= nPointsOnGround;
+ VectorNormalize( frictionData.m_vecNormal );
+ }
+
+ if ( nPointsInWater > 0 )
+ {
+ flAverageDampening /= nPointsInWater;
+ }
+
+ //IVP_FLOAT flDebugSpeed = ( IVP_FLOAT )m_pCore->speed.real_length();
+ //Msg("(water=%d/land=%d) speed=%f (%f %f %f)\n", nPointsInWater, nPointsOnGround, flDebugSpeed, vecAirboatDirLS.k[0], vecAirboatDirLS.k[1], vecAirboatDirLS.k[2]);
+
+ if ( nPointsInWater )
+ {
+ // Apply the drag force opposite to the direction of motion in local space.
+ IVP_U_Float_Point vecAirboatNegDirLS;
+ vecAirboatNegDirLS.set_negative( &m_vecLocalVelocity );
+
+ // Water drag is directional -- the pontoons resist left/right motion much more than forward/back.
+ IVP_U_Float_Point vecDragLS;
+ vecDragLS.set( AIRBOAT_WATER_DRAG_LEFT_RIGHT * vecAirboatNegDirLS.k[0],
+ AIRBOAT_WATER_DRAG_UP_DOWN * vecAirboatNegDirLS.k[1],
+ AIRBOAT_WATER_DRAG_FORWARD_BACK * vecAirboatNegDirLS.k[2] );
+
+ vecDragLS.mult( flSpeed * m_pCore->get_mass() * pEventSim->delta_time );
+ // dvs TODO: apply flAverageDampening here
+
+ // Convert the drag force to world space and apply the drag.
+ IVP_U_Float_Point vecDragWS;
+ matWorldFromCore->vmult3(&vecDragLS, &vecDragWS);
+ m_pCore->center_push_core_multiple_ws( &vecDragWS );
+ }
+
+ //
+ // Calculate ground friction drag:
+ //
+ if ( nPointsOnGround && ( flSpeed > 0 ))
+ {
+ // Calculate the average friction across all contact points.
+ flGroundFriction /= (float)nPointsOnGround;
+
+ // Apply the drag force opposite to the direction of motion.
+ IVP_U_Float_Point vecAirboatNegDir;
+ vecAirboatNegDir.set_negative( &m_pCore->speed );
+
+ IVP_FLOAT flFrictionDrag = m_pCore->get_mass() * AIRBOAT_GRAVITY * AIRBOAT_DRY_FRICTION_SCALE * flGroundFriction;
+ flFrictionDrag /= flSpeed;
+
+ IPhysicsObject *pPhysAirboat = static_cast<IPhysicsObject*>( m_pAirboatBody->client_data );
+ float flEliminatedEnergy = pPhysAirboat->GetEnergy();
+
+ // Apply the drag force opposite to the direction of motion in local space.
+ IVP_U_Float_Point vecAirboatNegDirLS;
+ vecAirboatNegDirLS.set_negative( &m_vecLocalVelocity );
+
+ // Ground drag is directional -- the pontoons resist left/right motion much more than forward/back.
+ IVP_U_Float_Point vecDragLS;
+ vecDragLS.set( AIRBOAT_GROUND_DRAG_LEFT_RIGHT * vecAirboatNegDirLS.k[0],
+ AIRBOAT_GROUND_DRAG_UP_DOWN * vecAirboatNegDirLS.k[1],
+ AIRBOAT_GROUND_DRAG_FORWARD_BACK * vecAirboatNegDirLS.k[2] );
+
+ vecDragLS.mult( flFrictionDrag * pEventSim->delta_time );
+ // dvs TODO: apply flAverageDampening here
+
+ // Convert the drag force to world space and apply the drag.
+ IVP_U_Float_Point vecDragWS;
+ matWorldFromCore->vmult3(&vecDragLS, &vecDragWS);
+ m_pCore->center_push_core_multiple_ws( &vecDragWS );
+
+ // Figure out how much energy was eliminated by friction.
+ flEliminatedEnergy -= pPhysAirboat->GetEnergy();
+ PerformFrictionNotification( flEliminatedEnergy, pEventSim->delta_time, nSurfaceProp, &frictionData );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::DoSimulationTurbine( IVP_Event_Sim *pEventSim )
+{
+ // Reduce the turbine power during weak jumps to avoid unrealistic air control.
+ // Also, reduce reverse thrust while airborne.
+ float flThrust = m_flThrust;
+ if ((m_bWeakJump) || (m_bAirborne && (flThrust < 0)))
+ {
+ flThrust *= 0.5;
+ }
+
+ // Get the forward vector in world-space.
+ IVP_U_Float_Point vecForwardWS;
+ const IVP_U_Matrix *matWorldFromCore = m_pCore->get_m_world_f_core_PSI();
+ matWorldFromCore->get_col( IVP_COORDINATE_INDEX( index_z ), &vecForwardWS );
+
+ //Msg("thrust: %f\n", m_flThrust);
+ if ( ( vecForwardWS.k[1] < -0.5 ) && ( flThrust > 0 ) )
+ {
+ // Driving up a slope. Reduce upward thrust to prevent ludicrous climbing of steep surfaces.
+ float flFactor = 1 + vecForwardWS.k[1];
+ //Msg("FWD: y=%f, factor=%f\n", vecForwardWS.k[1], flFactor);
+ flThrust *= flFactor;
+ }
+ else if ( ( vecForwardWS.k[1] > 0.5 ) && ( flThrust < 0 ) )
+ {
+ // Reversing up a slope. Reduce upward thrust to prevent ludicrous climbing of steep surfaces.
+ float flFactor = 1 - vecForwardWS.k[1];
+ //Msg("REV: y=%f, factor=%f\n", vecForwardWS.k[1], flFactor);
+ flThrust *= flFactor;
+ }
+
+ // Forward (Front/Back) force
+ IVP_U_Float_Point vecImpulse;
+ vecImpulse.set_multiple( &vecForwardWS, flThrust * m_pCore->get_mass() * pEventSim->delta_time );
+
+ m_pCore->center_push_core_multiple_ws( &vecImpulse );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::DoSimulationSteering( IVP_Event_Sim *pEventSim )
+{
+ // Calculate the steering direction: forward or reverse.
+ // Don't mess with the steering direction while we're steering, unless thrust is applied.
+ // This prevents the steering from reversing because we started drifting backwards.
+ if ( ( m_SteeringAngle == 0 ) || ( m_flThrust != 0 ) )
+ {
+ if ( !m_bAnalogSteering )
+ {
+ // If we're applying reverse thrust, steering is always reversed.
+ if ( m_flThrust < 0 )
+ {
+ m_bSteeringReversed = true;
+ }
+ // Else if we are applying forward thrust or moving forward, use forward steering.
+ else if ( ( m_flThrust > 0 ) || ( m_vecLocalVelocity.k[2] > 0 ) )
+ {
+ m_bSteeringReversed = false;
+ }
+ }
+ else
+ {
+ // Create a dead zone through the middle of the joystick where we don't reverse thrust.
+ // If we're applying reverse thrust, steering is always reversed.
+ if ( m_flThrust < -2.0f )
+ {
+ m_bSteeringReversed = true;
+ }
+ // Else if we are applying forward thrust or moving forward, use forward steering.
+ else if ( ( m_flThrust > 2.0f ) || ( m_vecLocalVelocity.k[2] > 0 ) )
+ {
+ m_bSteeringReversed = false;
+ }
+ }
+ }
+
+ // Calculate the steering force.
+ IVP_FLOAT flForceSteering = 0.0f;
+ if ( fabsf( m_SteeringAngle ) > 0.01 )
+ {
+ // Get the sign of the steering force.
+ IVP_FLOAT flSteeringSign = m_SteeringAngle < 0.0f ? -1.0f : 1.0f;
+ if ( m_bSteeringReversed )
+ {
+ flSteeringSign *= -1.0f;
+ }
+
+ // If we changed steering sign or went from not steering to steering, reset the steer time
+ // to blend the new steering force in over time.
+ IVP_FLOAT flPrevSteeringSign = m_flPrevSteeringAngle < 0.0f ? -1.0f : 1.0f;
+ if ( ( fabs( m_flPrevSteeringAngle ) < 0.01 ) || ( flSteeringSign != flPrevSteeringSign ) )
+ {
+ m_flSteerTime = 0;
+ }
+
+ float flSteerScale = 0.f;
+ if ( !m_bAnalogSteering )
+ {
+ // Ramp the steering force up over two seconds.
+ flSteerScale = RemapValClamped( m_flSteerTime, 0, AIRBOAT_STEERING_INTERVAL, AIRBOAT_STEERING_RATE_MIN, AIRBOAT_STEERING_RATE_MAX );
+ }
+ else // consoles
+ {
+ // Analog steering
+ flSteerScale = RemapValClamped( fabs(m_SteeringAngle), 0, AIRBOAT_STEERING_INTERVAL, AIRBOAT_STEERING_RATE_MIN, AIRBOAT_STEERING_RATE_MAX );
+ }
+
+ flForceSteering = flSteerScale * m_pCore->get_mass() * pEventSim->i_delta_time;
+ flForceSteering *= -flSteeringSign;
+
+ m_flSteerTime += pEventSim->delta_time;
+ }
+
+ //Msg("steer force=%f\n", flForceSteering);
+
+ m_flPrevSteeringAngle = m_SteeringAngle * ( m_bSteeringReversed ? -1.0 : 1.0 );
+
+ // Get the sign of the drag forces.
+ IVP_FLOAT flRotSpeedSign = m_pCore->rot_speed.k[1] < 0.0f ? -1.0f : 1.0f;
+
+ // Apply drag proportional to the square of the angular velocity.
+ IVP_FLOAT flRotationalDrag = AIRBOAT_ROT_DRAG * m_pCore->rot_speed.k[1] * m_pCore->rot_speed.k[1] * m_pCore->get_mass() * pEventSim->i_delta_time;
+ flRotationalDrag *= flRotSpeedSign;
+
+ // Apply dampening proportional to angular velocity.
+ IVP_FLOAT flRotationalDamping = AIRBOAT_ROT_DAMPING * fabs(m_pCore->rot_speed.k[1]) * m_pCore->get_mass() * pEventSim->i_delta_time;
+ flRotationalDamping *= flRotSpeedSign;
+
+ // Calculate the net rotational force.
+ IVP_FLOAT flForceRotational = flForceSteering + flRotationalDrag + flRotationalDamping;
+
+ // Apply it.
+ IVP_U_Float_Point vecRotImpulse;
+ vecRotImpulse.set( 0, -1, 0 );
+ vecRotImpulse.mult( flForceRotational );
+ m_pCore->rot_push_core_cs( &vecRotImpulse );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds extra gravity unless we are performing a strong jump.
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::DoSimulationGravity( IVP_Event_Sim *pEventSim )
+{
+ return;
+
+ if ( !m_bAirborne || m_bWeakJump )
+ {
+ IVP_U_Float_Point vecGravity;
+ vecGravity.set( 0, AIRBOAT_GRAVITY / 2.0f, 0 );
+ vecGravity.mult( m_pCore->get_mass() * pEventSim->delta_time );
+ m_pCore->center_push_core_multiple_ws( &vecGravity );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the number of pontoon raycast points that were found to contact
+// the ground or water.
+//-----------------------------------------------------------------------------
+int CPhysics_Airboat::CountSurfaceContactPoints( IVP_Raycast_Airboat_Impact *pImpacts )
+{
+ int nContacts = 0;
+ int nPontoonPoints = n_wheels;
+ for ( int iPoint = 0; iPoint < nPontoonPoints; iPoint++ )
+ {
+ // Get data at raycast position.
+ IVP_Raycast_Airboat_Impact *pImpact = &pImpacts[iPoint];
+ if ( !pImpact )
+ continue;
+
+ if ( pImpact->bImpact )
+ {
+ nContacts++;
+ }
+ }
+
+ return nContacts;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Prevents us from nosing down dramatically during jumps, which
+// increases our maximum jump distance.
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::DoSimulationKeepUprightPitch( IVP_Raycast_Airboat_Impact *pImpacts, IVP_Event_Sim *pEventSim )
+{
+ // Disable pitch control during weak jumps. This reduces the unreal 'floaty' sensation.
+ if (m_bWeakJump)
+ {
+ return;
+ }
+
+ // Reference vector in core space.
+ // Pitch back by 10 degrees while airborne.
+ IVP_U_Float_Point vecUpCS;
+ vecUpCS.set( 0, -cos(DEG2RAD(10)), sin(DEG2RAD(10)));
+
+ // Calculate the goal vector in core space. We will try to align the reference
+ // vector with the goal vector.
+ IVP_U_Float_Point vecGoalAxisWS;
+ vecGoalAxisWS.set( 0, -1, 0 );
+ const IVP_U_Matrix *matWorldFromCore = m_pCore->get_m_world_f_core_PSI();
+ IVP_U_Float_Point vecGoalAxisCS;
+ matWorldFromCore->vimult3( &vecGoalAxisWS, &vecGoalAxisCS );
+
+ // Eliminate roll control
+ vecGoalAxisCS.k[0] = vecUpCS.k[0];
+ vecGoalAxisCS.normize();
+
+ // Get an axis to rotate around.
+ IVP_U_Float_Point vecRotAxisCS;
+ vecRotAxisCS.calc_cross_product( &vecUpCS, &vecGoalAxisCS );
+
+ // Get the amount that we need to rotate.
+ // atan2() is well defined, so do a Dot & Cross instead of asin(Cross)
+ IVP_FLOAT cosine = vecUpCS.dot_product( &vecGoalAxisCS );
+ IVP_FLOAT sine = vecRotAxisCS.real_length_plus_normize();
+ IVP_FLOAT angle = atan2( sine, cosine );
+
+ //Msg("angle: %.2f, axis: (%.2f %.2f %.2f)\n", RAD2DEG(angle), vecRotAxisCS.k[0], vecRotAxisCS.k[1], vecRotAxisCS.k[2]);
+
+ // Don't keep upright if any pontoons are contacting a surface.
+ if ( CountSurfaceContactPoints( pImpacts ) > 0 )
+ {
+ m_flPitchErrorPrev = angle;
+ return;
+ }
+
+ // Don't do any correction if we're within 15 degrees of the goal orientation.
+ //if ( fabs( angle ) < DEG2RAD( 15 ) )
+ //{
+ // m_flPitchErrorPrev = angle;
+ // return;
+ //}
+
+ //Msg("CORRECTING\n");
+
+ // Generate an angular impulse describing the rotation.
+ IVP_U_Float_Point vecAngularImpulse;
+ vecAngularImpulse.set_multiple( &vecRotAxisCS, m_pCore->get_mass() * ( 0.1f * angle + 0.04f * pEventSim->i_delta_time * ( angle - m_flPitchErrorPrev ) ) );
+
+ // Save the last error value for calculating the derivative.
+ m_flPitchErrorPrev = angle;
+
+ // Clamp the impulse at a maximum length.
+ IVP_FLOAT len = vecAngularImpulse.real_length_plus_normize();
+ if ( len > ( DEG2RAD( 1.5 ) * m_pCore->get_mass() ) )
+ {
+ len = DEG2RAD( 1.5 ) * m_pCore->get_mass();
+ }
+ vecAngularImpulse.mult( len );
+
+ // Apply the rotation.
+ m_pCore->rot_push_core_cs( &vecAngularImpulse );
+
+#if DRAW_AIRBOAT_KEEP_UPRIGHT_PITCH_VECTORS
+ CPhysicsEnvironment *pEnv = (CPhysicsEnvironment *)m_pAirboatBody->get_core()->environment->client_data;
+ IVPhysicsDebugOverlay *debugoverlay = pEnv->GetDebugOverlay();
+
+ IVP_U_Float_Point vecPosIVP = m_pCore->get_position_PSI();
+ Vector vecPosHL;
+ ConvertPositionToHL(vecPosIVP, vecPosHL);
+
+ Vector vecGoalAxisHL;
+ ConvertDirectionToHL(vecGoalAxisWS, vecGoalAxisHL);
+
+ IVP_U_Float_Point vecUpWS;
+ matWorldFromCore->vmult3( &vecUpCS, &vecUpWS );
+ Vector vecCurHL;
+ ConvertDirectionToHL(vecUpWS, vecCurHL);
+
+ static IVP_FLOAT flLastLen = 0;
+ IVP_FLOAT flDebugLen = vecAngularImpulse.real_length();
+ if ( flLastLen && ( fabs( flDebugLen - flLastLen ) > DEG2RAD( 1 ) * m_pCore->get_mass() ) )
+ {
+ debugoverlay->AddLineOverlay(vecPosHL, vecPosHL + Vector(0, 0, 10) * flDebugLen, 255, 0, 255, false, 100.0 );
+ }
+ else
+ {
+ debugoverlay->AddLineOverlay(vecPosHL, vecPosHL + Vector(0, 0, 10) * flDebugLen, 255, 255, 255, false, 100.0 );
+ }
+ debugoverlay->AddLineOverlay(vecPosHL + Vector(0, 0, 10) * flDebugLen, vecPosHL + Vector(0, 0, 10) * flDebugLen + vecGoalAxisHL * 10, 0, 255, 0, false, 100.0 );
+ debugoverlay->AddLineOverlay(vecPosHL + Vector(0, 0, 10) * flDebugLen, vecPosHL + Vector(0, 0, 10) * flDebugLen + vecCurHL * 10, 255, 0, 0, false, 100.0 );
+ flLastLen = flDebugLen;
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Roll stabilizer when airborne.
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::DoSimulationKeepUprightRoll( IVP_Raycast_Airboat_Impact *pImpacts, IVP_Event_Sim *pEventSim )
+{
+ // Reference vector in core space.
+ // Pitch back by 10 degrees while airborne.
+ IVP_U_Float_Point vecUpCS;
+ vecUpCS.set( 0, -cos(DEG2RAD(10)), sin(DEG2RAD(10)));
+
+ // Calculate the goal vector in core space. We will try to align the reference
+ // vector with the goal vector.
+ IVP_U_Float_Point vecGoalAxisWS;
+ vecGoalAxisWS.set( 0, -1, 0 );
+ const IVP_U_Matrix *matWorldFromCore = m_pCore->get_m_world_f_core_PSI();
+ IVP_U_Float_Point vecGoalAxisCS;
+ matWorldFromCore->vimult3( &vecGoalAxisWS, &vecGoalAxisCS );
+
+ // Eliminate pitch control
+ vecGoalAxisCS.k[1] = vecUpCS.k[1];
+ vecGoalAxisCS.normize();
+
+ // Get an axis to rotate around.
+ IVP_U_Float_Point vecRotAxisCS;
+ vecRotAxisCS.calc_cross_product( &vecUpCS, &vecGoalAxisCS );
+
+ // Get the amount that we need to rotate.
+ // atan2() is well defined, so do a Dot & Cross instead of asin(Cross)
+ IVP_FLOAT cosine = vecUpCS.dot_product( &vecGoalAxisCS );
+ IVP_FLOAT sine = vecRotAxisCS.real_length_plus_normize();
+ IVP_FLOAT angle = atan2( sine, cosine );
+
+ //Msg("angle: %.2f, axis: (%.2f %.2f %.2f)\n", RAD2DEG(angle), vecRotAxisCS.k[0], vecRotAxisCS.k[1], vecRotAxisCS.k[2]);
+
+ // Don't keep upright if any pontoons are contacting a surface.
+ if ( CountSurfaceContactPoints( pImpacts ) > 0 )
+ {
+ m_flRollErrorPrev = angle;
+ return;
+ }
+
+ // Don't do any correction if we're within 10 degrees of the goal orientation.
+ if ( fabs( angle ) < DEG2RAD( 10 ) )
+ {
+ m_flRollErrorPrev = angle;
+ return;
+ }
+
+ //Msg("CORRECTING\n");
+
+ // Generate an angular impulse describing the rotation.
+ IVP_U_Float_Point vecAngularImpulse;
+ vecAngularImpulse.set_multiple( &vecRotAxisCS, m_pCore->get_mass() * ( 0.2f * angle + 0.3f * pEventSim->i_delta_time * ( angle - m_flRollErrorPrev ) ) );
+
+ // Save the last error value for calculating the derivative.
+ m_flRollErrorPrev = angle;
+
+ // Clamp the impulse at a maximum length.
+ IVP_FLOAT len = vecAngularImpulse.real_length_plus_normize();
+ if ( len > ( DEG2RAD( 2 ) * m_pCore->get_mass() ) )
+ {
+ len = DEG2RAD( 2 ) * m_pCore->get_mass();
+ }
+ vecAngularImpulse.mult( len );
+ m_pCore->rot_push_core_cs( &vecAngularImpulse );
+
+ // Debugging visualization.
+#if DRAW_AIRBOAT_KEEP_UPRIGHT_ROLL_VECTORS
+ CPhysicsEnvironment *pEnv = (CPhysicsEnvironment *)m_pAirboatBody->get_core()->environment->client_data;
+ IVPhysicsDebugOverlay *debugoverlay = pEnv->GetDebugOverlay();
+
+ IVP_U_Float_Point vecPosIVP = m_pCore->get_position_PSI();
+ Vector vecPosHL;
+ ConvertPositionToHL(vecPosIVP, vecPosHL);
+
+ Vector vecGoalAxisHL;
+ ConvertDirectionToHL(vecGoalAxisWS, vecGoalAxisHL);
+
+ IVP_U_Float_Point vecUpWS;
+ matWorldFromCore->vmult3( &vecUpCS, &vecUpWS );
+ Vector vecCurHL;
+ ConvertDirectionToHL(vecUpWS, vecCurHL);
+
+ static IVP_FLOAT flLastLen = 0;
+ IVP_FLOAT flDebugLen = vecAngularImpulse.real_length();
+ if ( flLastLen && ( fabs( flDebugLen - flLastLen ) > ( DEG2RAD( 0.25 ) * m_pCore->get_mass() ) )
+ {
+ debugoverlay->AddLineOverlay(vecPosHL, vecPosHL + Vector(0, 0, 10) * flDebugLen, 255, 0, 255, false, 100.0 );
+ }
+ else
+ {
+ debugoverlay->AddLineOverlay(vecPosHL, vecPosHL + Vector(0, 0, 10) * flDebugLen, 255, 255, 255, false, 100.0 );
+ }
+ debugoverlay->AddLineOverlay(vecPosHL + Vector(0, 0, 10) * flDebugLen, vecPosHL + Vector(0, 0, 10) * flDebugLen + vecGoalAxisHL * 10, 0, 255, 0, false, 100.0 );
+ debugoverlay->AddLineOverlay(vecPosHL + Vector(0, 0, 10) * flDebugLen, vecPosHL + Vector(0, 0, 10) * flDebugLen + vecCurHL * 10, 255, 0, 0, false, 100.0 );
+ flLastLen = flDebugLen;
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : wheel_nr -
+// s_angle -
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::do_steering_wheel(IVP_POS_WHEEL wheel_nr, IVP_FLOAT s_angle)
+{
+ IVP_Raycast_Airboat_Wheel *wheel = get_wheel(wheel_nr);
+
+ wheel->axis_direction_cs.set_to_zero();
+ wheel->axis_direction_cs.k[ index_x ] = 1.0f;
+ wheel->axis_direction_cs.rotate( IVP_COORDINATE_INDEX(index_y), s_angle);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pos -
+// spring_constant -
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::change_spring_constant(IVP_POS_WHEEL pos, IVP_FLOAT spring_constant)
+{
+ IVP_Raycast_Airboat_Wheel *wheel = get_wheel(pos);
+ wheel->spring_constant = spring_constant;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pos -
+// spring_dampening -
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::change_spring_dampening(IVP_POS_WHEEL pos, IVP_FLOAT spring_dampening)
+{
+ IVP_Raycast_Airboat_Wheel *wheel = get_wheel(pos);
+ wheel->spring_damp_relax = spring_dampening;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pos -
+// spring_dampening -
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::change_spring_dampening_compression(IVP_POS_WHEEL pos, IVP_FLOAT spring_dampening)
+{
+ IVP_Raycast_Airboat_Wheel *wheel = get_wheel(pos);
+ wheel->spring_damp_compress = spring_dampening;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pos -
+// pre_tension_length -
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::change_spring_pre_tension(IVP_POS_WHEEL pos, IVP_FLOAT pre_tension_length)
+{
+ IVP_Raycast_Airboat_Wheel *wheel = get_wheel(pos);
+ wheel->spring_len = gravity_y_direction * (wheel->distance_orig_hp_to_hp - pre_tension_length);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pos -
+// spring_length -
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::change_spring_length(IVP_POS_WHEEL pos, IVP_FLOAT spring_length)
+{
+ IVP_Raycast_Airboat_Wheel *wheel = get_wheel(pos);
+ wheel->spring_len = spring_length;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pos -
+// torque -
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::change_wheel_torque(IVP_POS_WHEEL pos, IVP_FLOAT torque)
+{
+ IVP_Raycast_Airboat_Wheel *wheel = get_wheel(pos);
+ wheel->torque = torque;
+
+ // Wake the physics object if need be!
+ m_pAirboatBody->get_environment()->get_controller_manager()->ensure_controller_in_simulation( this );
+}
+
+IVP_FLOAT CPhysics_Airboat::get_wheel_torque(IVP_POS_WHEEL pos)
+{
+ return get_wheel(pos)->torque;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Throttle input is -1 to 1.
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::update_throttle( IVP_FLOAT flThrottle )
+{
+ // Forward
+ if ( fabs( flThrottle ) < 0.01f )
+ {
+ m_flThrust = 0.0f;
+ }
+ else if ( flThrottle > 0.0f )
+ {
+ m_flThrust = AIRBOAT_THRUST_MAX * flThrottle;
+ }
+ else if ( flThrottle < 0.0f )
+ {
+ m_flThrust = AIRBOAT_THRUST_MAX_REVERSE * flThrottle;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pos -
+// stop_wheel -
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::fix_wheel(IVP_POS_WHEEL pos, IVP_BOOL stop_wheel)
+{
+ IVP_Raycast_Airboat_Wheel *wheel = get_wheel(pos);
+ wheel->wheel_is_fixed = stop_wheel;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pos -
+// friction -
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::change_friction_of_wheel( IVP_POS_WHEEL pos, IVP_FLOAT friction )
+{
+ IVP_Raycast_Airboat_Wheel *wheel = get_wheel(pos);
+ wheel->friction_of_wheel = friction;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pos -
+// stabi_constant -
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::change_stabilizer_constant(IVP_POS_AXIS pos, IVP_FLOAT stabi_constant)
+{
+ IVP_Raycast_Airboat_Axle *pAxle = get_axle( pos );
+ pAxle->stabilizer_constant = stabi_constant;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : fast_turn_factor_ -
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::change_fast_turn_factor( IVP_FLOAT fast_turn_factor_ )
+{
+ //fast_turn_factor = fast_turn_factor_;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : force -
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::change_body_downforce(IVP_FLOAT force)
+{
+ down_force = force;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : IVP_CONTROLLER_PRIORITY
+//-----------------------------------------------------------------------------
+IVP_CONTROLLER_PRIORITY CPhysics_Airboat::get_controller_priority()
+{
+ return IVP_CP_CONSTRAINTS_MAX;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : steering_angle_in -
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::do_steering( IVP_FLOAT steering_angle_in, bool bAnalog )
+{
+ // Check for a change.
+ if ( m_SteeringAngle == steering_angle_in)
+ return;
+
+ MEM_ALLOC_CREDIT();
+
+ // Set the new steering angle.
+ m_bAnalogSteering = bAnalog;
+ m_SteeringAngle = steering_angle_in;
+
+ // Make sure the simulation is awake - we just go input.
+ m_pAirboatBody->get_environment()->get_controller_manager()->ensure_controller_in_simulation( this );
+
+ // Steer each wheel.
+ for ( int iWheel = 0; iWheel < wheels_per_axis; ++iWheel )
+ {
+ do_steering_wheel( IVP_POS_WHEEL( iWheel ), m_SteeringAngle );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pos -
+// Output : IVP_DOUBLE
+//-----------------------------------------------------------------------------
+IVP_DOUBLE CPhysics_Airboat::get_wheel_angular_velocity(IVP_POS_WHEEL pos)
+{
+ IVP_Raycast_Airboat_Wheel *wheel = get_wheel(pos);
+ return wheel->wheel_angular_velocity;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : index -
+// Output : IVP_DOUBLE
+//-----------------------------------------------------------------------------
+IVP_DOUBLE CPhysics_Airboat::get_body_speed(IVP_COORDINATE_INDEX index)
+{
+ // return (IVP_FLOAT)car_body->get_geom_center_speed();
+ IVP_U_Float_Point *vec_ws = &m_pAirboatBody->get_core()->speed;
+ // works well as we do not use merged cores
+ const IVP_U_Matrix *mat_ws = m_pAirboatBody->get_core()->get_m_world_f_core_PSI();
+ IVP_U_Point orientation;
+ mat_ws->get_col(index, &orientation);
+
+ return orientation.dot_product(vec_ws);
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+IVP_DOUBLE CPhysics_Airboat::get_orig_front_wheel_distance()
+{
+ IVP_U_Float_Point *left_wheel_cs = &this->get_wheel(IVP_FRONT_LEFT)->hp_cs;
+ IVP_U_Float_Point *right_wheel_cs = &this->get_wheel(IVP_FRONT_RIGHT)->hp_cs;
+
+ IVP_DOUBLE dist = left_wheel_cs->k[this->index_x] - right_wheel_cs->k[this->index_x];
+
+ return IVP_Inline_Math::fabsd(dist); // was fabs, which was a sml call
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+IVP_DOUBLE CPhysics_Airboat::get_orig_axles_distance()
+{
+ IVP_U_Float_Point *front_wheel_cs = &this->get_wheel(IVP_FRONT_LEFT)->hp_cs;
+ IVP_U_Float_Point *rear_wheel_cs = &this->get_wheel(IVP_REAR_LEFT)->hp_cs;
+
+ IVP_DOUBLE dist = front_wheel_cs->k[this->index_z] - rear_wheel_cs->k[this->index_z];
+
+ return IVP_Inline_Math::fabsd(dist); // was fabs, which was a sml call
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *array_of_skid_info_out -
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::get_skid_info( IVP_Wheel_Skid_Info *array_of_skid_info_out)
+{
+ for ( int w = 0; w < n_wheels; w++)
+ {
+ IVP_Wheel_Skid_Info &info = array_of_skid_info_out[w];
+ //IVP_Constraint_Car_Object *wheel = car_constraint_solver->wheel_objects.element_at(w);
+ info.last_contact_position_ws.set_to_zero(); // = wheel->last_contact_position_ws;
+ info.last_skid_value = 0.0f; // wheel->last_skid_value;
+ info.last_skid_time = 0.0f; //wheel->last_skid_time;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::InitRaycastCarEnvironment( IVP_Environment *pEnvironment,
+ const IVP_Template_Car_System *pCarSystemTemplate )
+{
+ // Copies of the car system template component indices and handedness.
+ index_x = pCarSystemTemplate->index_x;
+ index_y = pCarSystemTemplate->index_y;
+ index_z = pCarSystemTemplate->index_z;
+ is_left_handed = pCarSystemTemplate->is_left_handed;
+
+ IVP_Standard_Gravity_Controller *pGravityController = new IVP_Standard_Gravity_Controller();
+ IVP_U_Point vecGravity( 0.0f, AIRBOAT_GRAVITY, 0.0f );
+ pGravityController->grav_vec.set( &vecGravity );
+
+ BEGIN_IVP_ALLOCATION();
+
+ m_pAirboatBody->get_core()->add_core_controller( pGravityController );
+
+ // Add this controller to the physics environment and setup the objects gravity.
+ pEnvironment->get_controller_manager()->announce_controller_to_environment( this );
+
+ END_IVP_ALLOCATION();
+
+ extra_gravity = pCarSystemTemplate->extra_gravity_force_value;
+
+ // This works because gravity is still int the same direction, just smaller.
+ if ( pEnvironment->get_gravity()->k[index_y] > 0 )
+ {
+ gravity_y_direction = 1.0f;
+ }
+ else
+ {
+ gravity_y_direction = -1.0f;
+ }
+ normized_gravity_ws.set( pEnvironment->get_gravity() );
+ normized_gravity_ws.normize();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::InitRaycastCarBody( const IVP_Template_Car_System *pCarSystemTemplate )
+{
+ // Car body attributes.
+ n_wheels = pCarSystemTemplate->n_wheels;
+ n_axis = pCarSystemTemplate->n_axis;
+ wheels_per_axis = n_wheels / n_axis;
+
+ // Add the car body "core" to the list of raycast car controller "cores."
+ m_pAirboatBody = pCarSystemTemplate->car_body;
+ this->vector_of_cores.add( m_pAirboatBody->get_core() );
+
+ // Init extra downward force applied to car.
+ down_force_vertical_offset = pCarSystemTemplate->body_down_force_vertical_offset;
+ down_force = 0.0f;
+
+ // Initialize.
+ for ( int iAxis = 0; iAxis < 3; ++iAxis )
+ {
+ m_pAirboatBody->get_core()->rot_speed.k[iAxis] = 0.0f;
+ m_pAirboatBody->get_core()->speed.k[iAxis] = 0.0f;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::InitRaycastCarWheels( const IVP_Template_Car_System *pCarSystemTemplate )
+{
+ IVP_U_Matrix m_core_f_object;
+ m_pAirboatBody->calc_m_core_f_object( &m_core_f_object );
+
+ // Initialize the car wheel system.
+ for ( int iWheel = 0; iWheel < n_wheels; iWheel++ )
+ {
+ // Get and clear out memory for the current raycast wheel.
+ IVP_Raycast_Airboat_Wheel *pRaycastWheel = get_wheel( IVP_POS_WHEEL( iWheel ) );
+ P_MEM_CLEAR( pRaycastWheel );
+
+ // Put the wheel in car space.
+ m_core_f_object.vmult4( &pCarSystemTemplate->wheel_pos_Bos[iWheel], &pRaycastWheel->hp_cs );
+ m_core_f_object.vmult4( &pCarSystemTemplate->trace_pos_Bos[iWheel], &pRaycastWheel->raycast_start_cs );
+
+ // Add in the raycast start offset.
+ pRaycastWheel->raycast_length = AIRBOAT_RAYCAST_DIST;
+ pRaycastWheel->raycast_dir_cs.set_to_zero();
+ pRaycastWheel->raycast_dir_cs.k[index_y] = gravity_y_direction;
+
+ // Spring (Shocks) data.
+ pRaycastWheel->spring_len = -pCarSystemTemplate->spring_pre_tension[iWheel];
+
+ pRaycastWheel->spring_direction_cs.set_to_zero();
+ pRaycastWheel->spring_direction_cs.k[index_y] = gravity_y_direction;
+
+ pRaycastWheel->spring_constant = pCarSystemTemplate->spring_constant[iWheel];
+ pRaycastWheel->spring_damp_relax = pCarSystemTemplate->spring_dampening[iWheel];
+ pRaycastWheel->spring_damp_compress = pCarSystemTemplate->spring_dampening_compression[iWheel];
+
+ // Wheel data.
+ pRaycastWheel->friction_of_wheel = 1.0f;//pCarSystemTemplate->friction_of_wheel[iWheel];
+ pRaycastWheel->wheel_radius = pCarSystemTemplate->wheel_radius[iWheel];
+ pRaycastWheel->inv_wheel_radius = 1.0f / pCarSystemTemplate->wheel_radius[iWheel];
+
+ do_steering_wheel( IVP_POS_WHEEL( iWheel ), 0.0f );
+
+ pRaycastWheel->wheel_is_fixed = IVP_FALSE;
+ pRaycastWheel->max_rotation_speed = pCarSystemTemplate->wheel_max_rotation_speed[iWheel>>1];
+
+ pRaycastWheel->wheel_is_fixed = IVP_TRUE;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::InitRaycastCarAxes( const IVP_Template_Car_System *pCarSystemTemplate )
+{
+ m_SteeringAngle = -1.0f; // make sure next call is not optimized
+ this->do_steering( 0.0f, false ); // make sure next call gets through
+
+ for ( int iAxis = 0; iAxis < n_axis; iAxis++ )
+ {
+ IVP_Raycast_Airboat_Axle *pAxle = get_axle( IVP_POS_AXIS( iAxis ) );
+ pAxle->stabilizer_constant = pCarSystemTemplate->stabilizer_constant[iAxis];
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Debug data for use in vphysics and the engine to visualize car data.
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::SetCarSystemDebugData( const IVP_CarSystemDebugData_t &carSystemDebugData )
+{
+ // Wheels (raycast data only!)
+ for ( int iWheel = 0; iWheel < IVP_RAYCAST_AIRBOAT_MAX_WHEELS; ++iWheel )
+ {
+ m_CarSystemDebugData.wheelRaycasts[iWheel][0] = carSystemDebugData.wheelRaycasts[iWheel][0];
+ m_CarSystemDebugData.wheelRaycasts[iWheel][1] = carSystemDebugData.wheelRaycasts[iWheel][1];
+ m_CarSystemDebugData.wheelRaycastImpacts[iWheel] = carSystemDebugData.wheelRaycastImpacts[iWheel];
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Debug data for use in vphysics and the engine to visualize car data.
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::GetCarSystemDebugData( IVP_CarSystemDebugData_t &carSystemDebugData )
+{
+ // Wheels (raycast data only!)
+ for ( int iWheel = 0; iWheel < IVP_RAYCAST_AIRBOAT_MAX_WHEELS; ++iWheel )
+ {
+ carSystemDebugData.wheelRaycasts[iWheel][0] = m_CarSystemDebugData.wheelRaycasts[iWheel][0];
+ carSystemDebugData.wheelRaycasts[iWheel][1] = m_CarSystemDebugData.wheelRaycasts[iWheel][1];
+ carSystemDebugData.wheelRaycastImpacts[iWheel] = m_CarSystemDebugData.wheelRaycastImpacts[iWheel];
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : IVP_U_Vector<IVP_Core>
+//-----------------------------------------------------------------------------
+IVP_U_Vector<IVP_Core> *CPhysics_Airboat::get_associated_controlled_cores( void )
+{
+ return &vector_of_cores;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *core -
+//-----------------------------------------------------------------------------
+void CPhysics_Airboat::core_is_going_to_be_deleted_event( IVP_Core *core )
+{
+ P_DELETE_THIS(this);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : i -
+// Output : IVP_Raycast_Airboat_Axle
+//-----------------------------------------------------------------------------
+IVP_Raycast_Airboat_Axle *CPhysics_Airboat::get_axle( IVP_POS_AXIS i )
+{
+ return &m_aAirboatAxles[i];
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : i -
+// Output : IVP_Raycast_Airboat_Wheel
+//-----------------------------------------------------------------------------
+IVP_Raycast_Airboat_Wheel *CPhysics_Airboat::get_wheel( IVP_POS_WHEEL i )
+{
+ return &m_aAirboatWheels[i];
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+IVP_Controller_Raycast_Airboat_Vector_of_Cores_1::IVP_Controller_Raycast_Airboat_Vector_of_Cores_1():
+ IVP_U_Vector<IVP_Core>( &elem_buffer[0],1 )
+{
+}
+