//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Client side implementation of the airboat. // // - Dampens motion of driver's view to reduce nausea. // - Autocenters driver's view after a period of inactivity. // - Controls headlights. // - Controls curve parameters for pitch/roll blending. // //=============================================================================// #include "cbase.h" #include "c_prop_vehicle.h" #include "datacache/imdlcache.h" #include "flashlighteffect.h" #include "movevars_shared.h" #include "ammodef.h" #include "SpriteTrail.h" #include "beamdraw.h" #include "enginesprite.h" #include "fx_quad.h" #include "fx.h" #include "fx_water.h" #include "engine/ivdebugoverlay.h" #include "view.h" #include "clienteffectprecachesystem.h" #include "c_basehlplayer.h" #include "vgui_controls/Controls.h" #include "vgui/ISurface.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" ConVar r_AirboatViewBlendTo( "r_AirboatViewBlendTo", "1", FCVAR_CHEAT ); ConVar r_AirboatViewBlendToScale( "r_AirboatViewBlendToScale", "0.03", FCVAR_CHEAT ); ConVar r_AirboatViewBlendToTime( "r_AirboatViewBlendToTime", "1.5", FCVAR_CHEAT ); ConVar cl_draw_airboat_wake( "cl_draw_airboat_wake", "1", FCVAR_CHEAT ); // Curve parameters for pitch/roll blending. // NOTE: Must restart (or create a new airboat) after changing these cvars! ConVar r_AirboatRollCurveZero( "r_AirboatRollCurveZero", "90.0", FCVAR_CHEAT ); // Roll less than this is clamped to zero. ConVar r_AirboatRollCurveLinear( "r_AirboatRollCurveLinear", "120.0", FCVAR_CHEAT ); // Roll greater than this is mapped directly. // Spline in between. ConVar r_AirboatPitchCurveZero( "r_AirboatPitchCurveZero", "25.0", FCVAR_CHEAT ); // Pitch less than this is clamped to zero. ConVar r_AirboatPitchCurveLinear( "r_AirboatPitchCurveLinear", "60.0", FCVAR_CHEAT ); // Pitch greater than this is mapped directly. // Spline in between. ConVar airboat_joy_response_move( "airboat_joy_response_move", "1" ); // Quadratic steering response #define AIRBOAT_DELTA_LENGTH_MAX 12.0f // 1 foot #define AIRBOAT_FRAMETIME_MIN 1e-6 #define HEADLIGHT_DISTANCE 1000 #define MAX_WAKE_POINTS 16 #define WAKE_POINT_MASK (MAX_WAKE_POINTS-1) #define WAKE_LIFETIME 0.5f //============================================================================= // // Client-side Airboat Class // class C_PropAirboat : public C_PropVehicleDriveable { DECLARE_CLASS( C_PropAirboat, C_PropVehicleDriveable ); public: DECLARE_CLIENTCLASS(); DECLARE_INTERPOLATION(); DECLARE_DATADESC(); C_PropAirboat(); ~C_PropAirboat(); public: // C_BaseEntity virtual void Simulate(); // IClientVehicle virtual void UpdateViewAngles( C_BasePlayer *pLocalPlayer, CUserCmd *pCmd ); virtual void OnEnteredVehicle( C_BasePlayer *pPlayer ); virtual int GetPrimaryAmmoType() const; virtual int GetPrimaryAmmoClip() const; virtual bool PrimaryAmmoUsesClips() const; virtual int GetPrimaryAmmoCount() const; virtual int GetJoystickResponseCurve() const; int DrawModel( int flags ); // Draws crosshair in the forward direction of the boat void DrawHudElements( ); private: void DrawPropWake( Vector origin, float speed ); void DrawPontoonSplash( Vector position, Vector direction, float speed ); void DrawPontoonWake( Vector startPos, Vector wakeDir, float wakeLength, float speed); void DampenEyePosition( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles ); void DampenForwardMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime ); void DampenUpMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime ); void ComputePDControllerCoefficients( float *pCoefficientsOut, float flFrequency, float flDampening, float flDeltaTime ); void UpdateHeadlight( void ); void UpdateWake( void ); int DrawWake( void ); void DrawSegment( const BeamSeg_t &beamSeg, const Vector &vNormal ); TrailPoint_t *GetTrailPoint( int n ) { int nIndex = (n + m_nFirstStep) & WAKE_POINT_MASK; return &m_vecSteps[nIndex]; } private: Vector m_vecLastEyePos; Vector m_vecLastEyeTarget; Vector m_vecEyeSpeed; Vector m_vecTargetSpeed; float m_flViewAngleDeltaTime; bool m_bHeadlightIsOn; int m_nAmmoCount; CHeadlightEffect *m_pHeadlight; int m_nExactWaterLevel; TrailPoint_t m_vecSteps[MAX_WAKE_POINTS]; int m_nFirstStep; int m_nStepCount; float m_flUpdateTime; TimedEvent m_SplashTime; CMeshBuilder m_Mesh; Vector m_vecPhysVelocity; }; IMPLEMENT_CLIENTCLASS_DT( C_PropAirboat, DT_PropAirboat, CPropAirboat ) RecvPropBool( RECVINFO( m_bHeadlightIsOn ) ), RecvPropInt( RECVINFO( m_nAmmoCount ) ), RecvPropInt( RECVINFO( m_nExactWaterLevel ) ), RecvPropInt( RECVINFO( m_nWaterLevel ) ), RecvPropVector( RECVINFO( m_vecPhysVelocity ) ), END_RECV_TABLE() BEGIN_DATADESC( C_PropAirboat ) DEFINE_FIELD( m_vecLastEyePos, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_vecLastEyeTarget, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_vecEyeSpeed, FIELD_VECTOR ), DEFINE_FIELD( m_vecTargetSpeed, FIELD_VECTOR ), //DEFINE_FIELD( m_flViewAngleDeltaTime, FIELD_FLOAT ), END_DATADESC() //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- C_PropAirboat::C_PropAirboat() { m_vecEyeSpeed.Init(); m_flViewAngleDeltaTime = 0.0f; m_pHeadlight = NULL; m_ViewSmoothingData.flPitchCurveZero = r_AirboatPitchCurveZero.GetFloat(); m_ViewSmoothingData.flPitchCurveLinear = r_AirboatPitchCurveLinear.GetFloat(); m_ViewSmoothingData.flRollCurveZero = r_AirboatRollCurveZero.GetFloat(); m_ViewSmoothingData.flRollCurveLinear = r_AirboatRollCurveLinear.GetFloat(); m_ViewSmoothingData.rollLockData.flLockInterval = 1.5; m_ViewSmoothingData.rollLockData.flUnlockBlendInterval = 1.0; m_ViewSmoothingData.pitchLockData.flLockInterval = 1.5; m_ViewSmoothingData.pitchLockData.flUnlockBlendInterval = 1.0; m_nFirstStep = 0; m_nStepCount = 0; m_SplashTime.Init( 60 ); } //----------------------------------------------------------------------------- // Purpose: Deconstructor //----------------------------------------------------------------------------- C_PropAirboat::~C_PropAirboat() { if (m_pHeadlight) { delete m_pHeadlight; } } //----------------------------------------------------------------------------- // Draws the ammo for the airboat //----------------------------------------------------------------------------- int C_PropAirboat::GetPrimaryAmmoType() const { if ( m_nAmmoCount < 0 ) return -1; int nAmmoType = GetAmmoDef()->Index( "AirboatGun" ); return nAmmoType; } int C_PropAirboat::GetPrimaryAmmoCount() const { return m_nAmmoCount; } bool C_PropAirboat::PrimaryAmmoUsesClips() const { return false; } int C_PropAirboat::GetPrimaryAmmoClip() const { return -1; } //----------------------------------------------------------------------------- // The airboat prefers a more peppy response curve for joystick control. //----------------------------------------------------------------------------- int C_PropAirboat::GetJoystickResponseCurve() const { return airboat_joy_response_move.GetInt(); } //----------------------------------------------------------------------------- // Draws crosshair in the forward direction of the boat //----------------------------------------------------------------------------- void C_PropAirboat::DrawHudElements( ) { BaseClass::DrawHudElements(); MDLCACHE_CRITICAL_SECTION(); CHudTexture *pIcon = gHUD.GetIcon( IsX360() ? "crosshair_default" : "plushair" ); if ( pIcon != NULL ) { float x, y; Vector screen; int vx, vy, vw, vh; vgui::surface()->GetFullscreenViewport( vx, vy, vw, vh ); float screenWidth = vw; float screenHeight = vh; x = screenWidth/2; y = screenHeight/2; int eyeAttachmentIndex = LookupAttachment( "vehicle_driver_eyes" ); Vector vehicleEyeOrigin; QAngle vehicleEyeAngles; GetAttachment( eyeAttachmentIndex, vehicleEyeOrigin, vehicleEyeAngles ); // Only worry about yaw. vehicleEyeAngles.x = vehicleEyeAngles.z = 0.0f; Vector vecForward; AngleVectors( vehicleEyeAngles, &vecForward ); VectorMA( vehicleEyeOrigin, 100.0f, vecForward, vehicleEyeOrigin ); ScreenTransform( vehicleEyeOrigin, screen ); x += 0.5 * screen[0] * screenWidth + 0.5; y -= 0.5 * screen[1] * screenHeight + 0.5; x -= pIcon->Width() / 2; y -= pIcon->Height() / 2; pIcon->DrawSelf( x, y, gHUD.m_clrNormal ); } } //----------------------------------------------------------------------------- // Purpose: Blend view angles. //----------------------------------------------------------------------------- void C_PropAirboat::UpdateViewAngles( C_BasePlayer *pLocalPlayer, CUserCmd *pCmd ) { if ( r_AirboatViewBlendTo.GetInt() ) { // // Autocenter the view after a period of no mouse movement while throttling. // bool bResetViewAngleTime = false; if ( ( pCmd->mousedx != 0 || pCmd->mousedy != 0 ) || ( fabsf( m_flThrottle ) < 0.01f ) ) { if ( IsX360() ) { // Only reset this if there isn't an autoaim target! C_BaseHLPlayer *pLocalHLPlayer = (C_BaseHLPlayer *)pLocalPlayer; if ( pLocalHLPlayer ) { // Get the autoaim target. CBaseEntity *pTarget = pLocalHLPlayer->m_HL2Local.m_hAutoAimTarget.Get(); if( !pTarget ) { bResetViewAngleTime = true; } } } else { bResetViewAngleTime = true; } } if( bResetViewAngleTime ) { m_flViewAngleDeltaTime = 0.0f; } else { m_flViewAngleDeltaTime += gpGlobals->frametime; } if ( m_flViewAngleDeltaTime > r_AirboatViewBlendToTime.GetFloat() ) { // Blend the view angles. int eyeAttachmentIndex = LookupAttachment( "vehicle_driver_eyes" ); Vector vehicleEyeOrigin; QAngle vehicleEyeAngles; GetAttachmentLocal( eyeAttachmentIndex, vehicleEyeOrigin, vehicleEyeAngles ); QAngle outAngles; InterpolateAngles( pCmd->viewangles, vehicleEyeAngles, outAngles, r_AirboatViewBlendToScale.GetFloat() ); pCmd->viewangles = outAngles; } } BaseClass::UpdateViewAngles( pLocalPlayer, pCmd ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_PropAirboat::DampenEyePosition( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles ) { // Get the frametime. (Check to see if enough time has passed to warrent dampening). float flFrameTime = gpGlobals->frametime; if ( flFrameTime < AIRBOAT_FRAMETIME_MIN ) { vecVehicleEyePos = m_vecLastEyePos; DampenUpMotion( vecVehicleEyePos, vecVehicleEyeAngles, 0.0f ); return; } // Keep static the sideways motion. // Dampen forward/backward motion. DampenForwardMotion( vecVehicleEyePos, vecVehicleEyeAngles, flFrameTime ); // Blend up/down motion. DampenUpMotion( vecVehicleEyePos, vecVehicleEyeAngles, flFrameTime ); } //----------------------------------------------------------------------------- // Use the controller as follows: // speed += ( pCoefficientsOut[0] * ( targetPos - currentPos ) + pCoefficientsOut[1] * ( targetSpeed - currentSpeed ) ) * flDeltaTime; //----------------------------------------------------------------------------- void C_PropAirboat::ComputePDControllerCoefficients( float *pCoefficientsOut, float flFrequency, float flDampening, float flDeltaTime ) { float flKs = 9.0f * flFrequency * flFrequency; float flKd = 4.5f * flFrequency * flDampening; float flScale = 1.0f / ( 1.0f + flKd * flDeltaTime + flKs * flDeltaTime * flDeltaTime ); pCoefficientsOut[0] = flKs * flScale; pCoefficientsOut[1] = ( flKd + flKs * flDeltaTime ) * flScale; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_PropAirboat::DampenForwardMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime ) { // vecVehicleEyePos = real eye position this frame // m_vecLastEyePos = eye position last frame // m_vecEyeSpeed = eye speed last frame // vecPredEyePos = predicted eye position this frame (assuming no acceleration - it will get that from the pd controller). // vecPredEyeSpeed = predicted eye speed Vector vecPredEyePos = m_vecLastEyePos + m_vecEyeSpeed * flFrameTime; Vector vecPredEyeSpeed = m_vecEyeSpeed; // m_vecLastEyeTarget = real eye position last frame (used for speed calculation). // Calculate the approximate speed based on the current vehicle eye position and the eye position last frame. Vector vecVehicleEyeSpeed = ( vecVehicleEyePos - m_vecLastEyeTarget ) / flFrameTime; m_vecLastEyeTarget = vecVehicleEyePos; if (vecVehicleEyeSpeed.Length() == 0.0) { return; } // Calculate the delta between the predicted eye position and speed and the current eye position and speed. Vector vecDeltaSpeed = vecVehicleEyeSpeed - vecPredEyeSpeed; Vector vecDeltaPos = vecVehicleEyePos - vecPredEyePos; // Forward vector. Vector vecForward; AngleVectors( vecVehicleEyeAngles, &vecForward ); float flDeltaLength = vecDeltaPos.Length(); if ( flDeltaLength > AIRBOAT_DELTA_LENGTH_MAX ) { // Clamp. float flDelta = flDeltaLength - AIRBOAT_DELTA_LENGTH_MAX; if ( flDelta > 40.0f ) { // This part is a bit of a hack to get rid of large deltas (at level load, etc.). m_vecLastEyePos = vecVehicleEyePos; m_vecEyeSpeed = vecVehicleEyeSpeed; } else { // Position clamp. float flRatio = AIRBOAT_DELTA_LENGTH_MAX / flDeltaLength; vecDeltaPos *= flRatio; Vector vecForwardOffset = vecForward * ( vecForward.Dot( vecDeltaPos ) ); vecVehicleEyePos -= vecForwardOffset; m_vecLastEyePos = vecVehicleEyePos; // Speed clamp. vecDeltaSpeed *= flRatio; float flCoefficients[2]; ComputePDControllerCoefficients( flCoefficients, r_AirboatViewDampenFreq.GetFloat(), r_AirboatViewDampenDamp.GetFloat(), flFrameTime ); m_vecEyeSpeed += ( ( flCoefficients[0] * vecDeltaPos + flCoefficients[1] * vecDeltaSpeed ) * flFrameTime ); } } else { // Generate an updated (dampening) speed for use in next frames position prediction. float flCoefficients[2]; ComputePDControllerCoefficients( flCoefficients, r_AirboatViewDampenFreq.GetFloat(), r_AirboatViewDampenDamp.GetFloat(), flFrameTime ); m_vecEyeSpeed += ( ( flCoefficients[0] * vecDeltaPos + flCoefficients[1] * vecDeltaSpeed ) * flFrameTime ); // Save off data for next frame. m_vecLastEyePos = vecPredEyePos; // Move eye forward/backward. Vector vecForwardOffset = vecForward * ( vecForward.Dot( vecDeltaPos ) ); vecVehicleEyePos -= vecForwardOffset; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_PropAirboat::DampenUpMotion( Vector &vecVehicleEyePos, QAngle &vecVehicleEyeAngles, float flFrameTime ) { // Get up vector. Vector vecUp; AngleVectors( vecVehicleEyeAngles, NULL, NULL, &vecUp ); vecUp.z = clamp( vecUp.z, 0.0f, vecUp.z ); vecVehicleEyePos.z += r_AirboatViewZHeight.GetFloat() * vecUp.z; // NOTE: Should probably use some damped equation here. } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_PropAirboat::OnEnteredVehicle( C_BasePlayer *pPlayer ) { int eyeAttachmentIndex = LookupAttachment( "vehicle_driver_eyes" ); Vector vehicleEyeOrigin; QAngle vehicleEyeAngles; GetAttachment( eyeAttachmentIndex, vehicleEyeOrigin, vehicleEyeAngles ); m_vecLastEyeTarget = vehicleEyeOrigin; m_vecLastEyePos = vehicleEyeOrigin; m_vecEyeSpeed = vec3_origin; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_PropAirboat::Simulate() { UpdateHeadlight(); UpdateWake(); BaseClass::Simulate(); } //----------------------------------------------------------------------------- // Purpose: Creates, destroys, and updates the headlight effect as needed. //----------------------------------------------------------------------------- void C_PropAirboat::UpdateHeadlight() { if (m_bHeadlightIsOn) { if (!m_pHeadlight) { // Turned on the headlight; create it. m_pHeadlight = new CHeadlightEffect(); if (!m_pHeadlight) return; m_pHeadlight->TurnOn(); } // The headlight is emitted from an attachment point so that it can move // as we turn the handlebars. int nHeadlightIndex = LookupAttachment( "vehicle_headlight" ); Vector vecLightPos; QAngle angLightDir; GetAttachment(nHeadlightIndex, vecLightPos, angLightDir); Vector vecLightDir, vecLightRight, vecLightUp; AngleVectors( angLightDir, &vecLightDir, &vecLightRight, &vecLightUp ); // Update the light with the new position and direction. m_pHeadlight->UpdateLight( vecLightPos, vecLightDir, vecLightRight, vecLightUp, HEADLIGHT_DISTANCE ); } else if (m_pHeadlight) { // Turned off the headlight; delete it. delete m_pHeadlight; m_pHeadlight = NULL; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_PropAirboat::UpdateWake( void ) { if ( gpGlobals->frametime <= 0.0f ) return; // Can't update too quickly if ( m_flUpdateTime > gpGlobals->curtime ) return; Vector screenPos = GetRenderOrigin(); screenPos.z = m_nExactWaterLevel; TrailPoint_t *pLast = m_nStepCount ? GetTrailPoint( m_nStepCount-1 ) : NULL; if ( ( pLast == NULL ) || ( pLast->m_vecScreenPos.DistToSqr( screenPos ) > 4.0f ) ) { // If we're over our limit, steal the last point and put it up front if ( m_nStepCount >= MAX_WAKE_POINTS ) { --m_nStepCount; ++m_nFirstStep; } // Save off its screen position, not its world position TrailPoint_t *pNewPoint = GetTrailPoint( m_nStepCount ); pNewPoint->m_vecScreenPos = screenPos + Vector( 0, 0, 2 ); pNewPoint->m_flDieTime = gpGlobals->curtime + WAKE_LIFETIME; pNewPoint->m_flWidthVariance = random->RandomFloat( -16, 16 ); if ( pLast ) { pNewPoint->m_flTexCoord = pLast->m_flTexCoord + pLast->m_vecScreenPos.DistTo( screenPos ); pNewPoint->m_flTexCoord = fmod( pNewPoint->m_flTexCoord, 1 ); } else { pNewPoint->m_flTexCoord = 0.0f; } ++m_nStepCount; } // Don't update again for a bit m_flUpdateTime = gpGlobals->curtime + ( 0.5f / (float) MAX_WAKE_POINTS ); } //----------------------------------------------------------------------------- // Purpose: // Input : &beamSeg - //----------------------------------------------------------------------------- void C_PropAirboat::DrawSegment( const BeamSeg_t &beamSeg, const Vector &vNormal ) { // Build the endpoints. Vector vPoint1, vPoint2; VectorMA( beamSeg.m_vPos, beamSeg.m_flWidth*0.5f, vNormal, vPoint1 ); VectorMA( beamSeg.m_vPos, -beamSeg.m_flWidth*0.5f, vNormal, vPoint2 ); // Specify the points. m_Mesh.Position3fv( vPoint1.Base() ); m_Mesh.Color4f( VectorExpand( beamSeg.m_vColor ), beamSeg.m_flAlpha ); m_Mesh.TexCoord2f( 0, 0, beamSeg.m_flTexCoord ); m_Mesh.TexCoord2f( 1, 0, beamSeg.m_flTexCoord ); m_Mesh.AdvanceVertex(); m_Mesh.Position3fv( vPoint2.Base() ); m_Mesh.Color4f( VectorExpand( beamSeg.m_vColor ), beamSeg.m_flAlpha ); m_Mesh.TexCoord2f( 0, 1, beamSeg.m_flTexCoord ); m_Mesh.TexCoord2f( 1, 1, beamSeg.m_flTexCoord ); m_Mesh.AdvanceVertex(); } //----------------------------------------------------------------------------- // Purpose: // Input : position - //----------------------------------------------------------------------------- void C_PropAirboat::DrawPontoonSplash( Vector origin, Vector direction, float speed ) { Vector offset; CSmartPtr pSimple = CSplashParticle::Create( "splish" ); pSimple->SetSortOrigin( origin ); SimpleParticle *pParticle; Vector color = Vector( 0.8f, 0.8f, 0.75f ); float colorRamp; float flScale = RemapVal( speed, 64, 256, 0.75f, 1.0f ); PMaterialHandle hMaterial; float tempDelta = gpGlobals->frametime; while( m_SplashTime.NextEvent( tempDelta ) ) { if ( random->RandomInt( 0, 1 ) ) { hMaterial = ParticleMgr()->GetPMaterial( "effects/splash1" ); } else { hMaterial = ParticleMgr()->GetPMaterial( "effects/splash2" ); } offset = RandomVector( -8.0f * flScale, 8.0f * flScale ); offset[2] = 0.0f; offset += origin; pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), hMaterial, offset ); if ( pParticle == NULL ) continue; pParticle->m_flLifetime = 0.0f; pParticle->m_flDieTime = 0.25f; pParticle->m_vecVelocity.Random( -0.4f, 0.4f ); pParticle->m_vecVelocity += (direction*5.0f+Vector(0,0,1)); VectorNormalize( pParticle->m_vecVelocity ); pParticle->m_vecVelocity *= speed + random->RandomFloat( -128.0f, 128.0f ); colorRamp = random->RandomFloat( 0.75f, 1.25f ); pParticle->m_uchColor[0] = MIN( 1.0f, color[0] * colorRamp ) * 255.0f; pParticle->m_uchColor[1] = MIN( 1.0f, color[1] * colorRamp ) * 255.0f; pParticle->m_uchColor[2] = MIN( 1.0f, color[2] * colorRamp ) * 255.0f; pParticle->m_uchStartSize = random->RandomFloat( 8, 16 ) * flScale; pParticle->m_uchEndSize = pParticle->m_uchStartSize * 2; pParticle->m_uchStartAlpha = 255; pParticle->m_uchEndAlpha = 0; pParticle->m_flRoll = random->RandomInt( 0, 360 ); pParticle->m_flRollDelta = random->RandomFloat( -4.0f, 4.0f ); } } //----------------------------------------------------------------------------- // Purpose: // Input : Vector startPos - // wakeDir - // wakeLength - //----------------------------------------------------------------------------- void C_PropAirboat::DrawPontoonWake( Vector startPos, Vector wakeDir, float wakeLength, float speed ) { #define WAKE_STEPS 6 Vector wakeStep = wakeDir * ( wakeLength / (float) WAKE_STEPS ); Vector origin; float scale; IMaterial *pMaterial = materials->FindMaterial( "effects/splashwake1", NULL, false ); CMatRenderContextPtr pRenderContext( materials ); IMesh* pMesh = pRenderContext->GetDynamicMesh( 0, 0, 0, pMaterial ); CMeshBuilder meshBuilder; meshBuilder.Begin( pMesh, MATERIAL_QUADS, WAKE_STEPS ); for ( int i = 0; i < WAKE_STEPS; i++ ) { origin = startPos + ( wakeStep * i ); origin[0] += random->RandomFloat( -4.0f, 4.0f ); origin[1] += random->RandomFloat( -4.0f, 4.0f ); origin[2] = m_nExactWaterLevel + 2.0f; float scaleRange = RemapVal( i, 0, WAKE_STEPS-1, 32, 64 ); scale = scaleRange + ( 8.0f * sin( gpGlobals->curtime * 5 * i ) ); float alpha = RemapValClamped( speed, 128, 600, 0.05f, 0.25f ); float color[4] = { 1.0f, 1.0f, 1.0f, alpha }; // Needs to be time based so it'll freeze when the game is frozen float yaw = random->RandomFloat( 0, 360 ); Vector rRight = ( Vector(1,0,0) * cos( DEG2RAD( yaw ) ) ) - ( Vector(0,1,0) * sin( DEG2RAD( yaw ) ) ); Vector rUp = ( Vector(1,0,0) * cos( DEG2RAD( yaw+90.0f ) ) ) - ( Vector(0,1,0) * sin( DEG2RAD( yaw+90.0f ) ) ); Vector point; meshBuilder.Color4fv (color); meshBuilder.TexCoord2f (0, 0, 1); VectorMA (origin, -scale, rRight, point); VectorMA (point, -scale, rUp, point); meshBuilder.Position3fv (point.Base()); meshBuilder.AdvanceVertex(); meshBuilder.Color4fv (color); meshBuilder.TexCoord2f (0, 0, 0); VectorMA (origin, scale, rRight, point); VectorMA (point, -scale, rUp, point); meshBuilder.Position3fv (point.Base()); meshBuilder.AdvanceVertex(); meshBuilder.Color4fv (color); meshBuilder.TexCoord2f (0, 1, 0); VectorMA (origin, scale, rRight, point); VectorMA (point, scale, rUp, point); meshBuilder.Position3fv (point.Base()); meshBuilder.AdvanceVertex(); meshBuilder.Color4fv (color); meshBuilder.TexCoord2f (0, 1, 1); VectorMA (origin, -scale, rRight, point); VectorMA (point, scale, rUp, point); meshBuilder.Position3fv (point.Base()); meshBuilder.AdvanceVertex(); } meshBuilder.End(); pMesh->Draw(); } //----------------------------------------------------------------------------- // Purpose: // Output : int //----------------------------------------------------------------------------- int C_PropAirboat::DrawWake( void ) { if ( cl_draw_airboat_wake.GetBool() == false ) return 0; // Make sure we're in water... if ( GetWaterLevel() == 0 ) return 0; //FIXME: For now, we don't draw slime this way if ( GetWaterLevel() == 2 ) return 0; bool bDriven = ( GetPassenger( VEHICLE_ROLE_DRIVER ) != NULL ); Vector vehicleDir = m_vecPhysVelocity; float vehicleSpeed = VectorNormalize( vehicleDir ); Vector vecPontoonFrontLeft; Vector vecPontoonFrontRight; Vector vecPontoonRearLeft; Vector vecPontoonRearRight; Vector vecSplashPoint; QAngle fooAngles; //FIXME: This lookup should be cached off // Get all attachments GetAttachment( LookupAttachment( "raytrace_fl" ), vecPontoonFrontLeft, fooAngles ); GetAttachment( LookupAttachment( "raytrace_fr" ), vecPontoonFrontRight, fooAngles ); GetAttachment( LookupAttachment( "raytrace_rl" ), vecPontoonRearLeft, fooAngles ); GetAttachment( LookupAttachment( "raytrace_rr" ), vecPontoonRearRight, fooAngles ); GetAttachment( LookupAttachment( "splash_pt" ), vecSplashPoint, fooAngles ); // Find the direction of the pontoons Vector vecLeftWakeDir = ( vecPontoonRearLeft - vecPontoonFrontLeft ); Vector vecRightWakeDir = ( vecPontoonRearRight - vecPontoonFrontRight ); // Find the pontoon's size float flWakeLeftLength = VectorNormalize( vecLeftWakeDir ); float flWakeRightLength = VectorNormalize( vecRightWakeDir ); vecPontoonFrontLeft.z = m_nExactWaterLevel; vecPontoonFrontRight.z = m_nExactWaterLevel; if ( bDriven && vehicleSpeed > 128.0f ) { DrawPontoonWake( vecPontoonFrontLeft, vecLeftWakeDir, flWakeLeftLength, vehicleSpeed ); DrawPontoonWake( vecPontoonFrontRight, vecRightWakeDir, flWakeRightLength, vehicleSpeed ); Vector vecSplashDir; Vector vForward; GetVectors( &vForward, NULL, NULL ); if ( m_vecPhysVelocity.x < -64.0f ) { vecSplashDir = vecLeftWakeDir - vForward; VectorNormalize( vecSplashDir ); // Don't do this if we're going backwards if ( m_vecPhysVelocity.y > 0.0f ) { DrawPontoonSplash( vecPontoonFrontLeft + ( vecLeftWakeDir * 1.0f ), vecSplashDir, m_vecPhysVelocity.y ); } } else if ( m_vecPhysVelocity.x > 64.0f ) { vecSplashDir = vecRightWakeDir + vForward; VectorNormalize( vecSplashDir ); // Don't do this if we're going backwards if ( m_vecPhysVelocity.y > 0.0f ) { DrawPontoonSplash( vecPontoonFrontRight + ( vecRightWakeDir * 1.0f ), vecSplashDir, m_vecPhysVelocity.y ); } } } // Must have at least one point if ( m_nStepCount <= 1 ) return 1; IMaterial *pMaterial = materials->FindMaterial( "effects/splashwake4", 0); //Bind the material CMatRenderContextPtr pRenderContext( materials ); IMesh *pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, pMaterial ); m_Mesh.Begin( pMesh, MATERIAL_TRIANGLE_STRIP, (m_nStepCount-1) * 2 ); TrailPoint_t *pLast = GetTrailPoint( m_nStepCount - 1 ); TrailPoint_t currentPoint; currentPoint.m_flDieTime = gpGlobals->curtime + 0.5f; currentPoint.m_vecScreenPos = GetAbsOrigin(); currentPoint.m_vecScreenPos[2] = m_nExactWaterLevel + 16; currentPoint.m_flTexCoord = pLast->m_flTexCoord + currentPoint.m_vecScreenPos.DistTo(pLast->m_vecScreenPos); currentPoint.m_flTexCoord = fmod( currentPoint.m_flTexCoord, 1 ); currentPoint.m_flWidthVariance = 0.0f; TrailPoint_t *pPrevPoint = NULL; Vector segDir, normal; for ( int i = 0; i <= m_nStepCount; ++i ) { // This makes it so that we're always drawing to the current location TrailPoint_t *pPoint = (i != m_nStepCount) ? GetTrailPoint(i) : ¤tPoint; float flLifePerc = RemapValClamped( ( pPoint->m_flDieTime - gpGlobals->curtime ), 0, WAKE_LIFETIME, 0.0f, 1.0f ); BeamSeg_t curSeg; curSeg.m_vColor.x = curSeg.m_vColor.y = curSeg.m_vColor.z = 1.0f; float flAlphaFade = flLifePerc; float alpha = RemapValClamped( fabs( m_vecPhysVelocity.y ), 128, 600, 0.0f, 1.0f ); curSeg.m_flAlpha = 0.25f; curSeg.m_flAlpha *= flAlphaFade * alpha; curSeg.m_vPos = pPoint->m_vecScreenPos; float widthBase = SimpleSplineRemapVal( fabs( m_vecPhysVelocity.y ), 128, 600, 32, 48 ); curSeg.m_flWidth = Lerp( flLifePerc, widthBase*6, widthBase ); curSeg.m_flWidth += pPoint->m_flWidthVariance; if ( curSeg.m_flWidth < 0.0f ) { curSeg.m_flWidth = 0.0f; } curSeg.m_flTexCoord = pPoint->m_flTexCoord; if ( pPrevPoint != NULL ) { segDir = ( pPrevPoint->m_vecScreenPos - pPoint->m_vecScreenPos ); VectorNormalize( segDir ); normal = CrossProduct( segDir, Vector( 0, 0, -1 ) ); DrawSegment( curSeg, normal ); } pPrevPoint = pPoint; } m_Mesh.End(); pMesh->Draw(); return 1; } //----------------------------------------------------------------------------- // Purpose: // Input : flags - // Output : int //----------------------------------------------------------------------------- int C_PropAirboat::DrawModel( int flags ) { if ( BaseClass::DrawModel( flags ) == false ) return 0; if ( !m_bReadyToDraw ) return 0; return DrawWake(); }