//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "precache_register.h" #include "FX_Sparks.h" #include "iefx.h" #include "c_te_effect_dispatch.h" #include "particles_ez.h" #include "decals.h" #include "engine/IEngineSound.h" #include "fx_quad.h" #include "tier0/vprof.h" #include "fx.h" #include "fx_water.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" PRECACHE_REGISTER_BEGIN( GLOBAL, PrecacheEffectSplash ) PRECACHE( MATERIAL, "effects/splash1" ) PRECACHE( MATERIAL, "effects/splash2" ) PRECACHE( MATERIAL, "effects/splash4" ) PRECACHE( MATERIAL, "effects/slime1" ) PRECACHE_REGISTER_END() #define SPLASH_MIN_SPEED 50.0f #define SPLASH_MAX_SPEED 100.0f ConVar cl_show_splashes( "cl_show_splashes", "1" ); static Vector s_vecSlimeColor( 46.0f/255.0f, 90.0f/255.0f, 36.0f/255.0f ); // Each channel does not contribute to the luminosity equally, as represented here #define RED_CHANNEL_CONTRIBUTION 0.30f #define GREEN_CHANNEL_CONTRIBUTION 0.59f #define BLUE_CHANNEL_CONTRIBUTION 0.11f //----------------------------------------------------------------------------- // Purpose: Returns a normalized tint and luminosity for a specified color // Input : &color - normalized input color to extract information from // *tint - normalized tint of that color // *luminosity - normalized luminosity of that color //----------------------------------------------------------------------------- void UTIL_GetNormalizedColorTintAndLuminosity( const Vector &color, Vector *tint, float *luminosity ) { // Give luminosity if requested if ( luminosity != NULL ) { // Each channel contributes differently than the others *luminosity = ( color.x * RED_CHANNEL_CONTRIBUTION ) + ( color.y * GREEN_CHANNEL_CONTRIBUTION ) + ( color.z * BLUE_CHANNEL_CONTRIBUTION ); } // Give tint if requested if ( tint != NULL ) { if ( color == vec3_origin ) { *tint = vec3_origin; } else { float maxComponent = MAX( color.x, MAX( color.y, color.z ) ); *tint = color / maxComponent; } } } //----------------------------------------------------------------------------- // Purpose: // Input : &origin - // &normal - // scale - //----------------------------------------------------------------------------- void FX_WaterRipple( const Vector &origin, float scale, Vector *pColor, float flLifetime, float flAlpha ) { VPROF_BUDGET( "FX_WaterRipple", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); trace_t tr; Vector color = pColor ? *pColor : Vector( 0.8f, 0.8f, 0.75f ); Vector startPos = origin + Vector(0,0,8); Vector endPos = origin + Vector(0,0,-64); UTIL_TraceLine( startPos, endPos, MASK_WATER, NULL, COLLISION_GROUP_NONE, &tr ); if ( tr.fraction < 1.0f ) { QAngle vecAngles; // we flip the z and the x to match the orientation of how the impact particles are authored // all impact particles are authored with the effect going "up" (0, 0, 1) VectorAngles( Vector( tr.plane.normal.z, tr.plane.normal.y, tr.plane.normal.x ), vecAngles ); DispatchParticleEffect( "water_splash_02_surface2", tr.endpos, vecAngles, NULL ); } } PRECACHE_REGISTER_BEGIN( SHARED_SYSTEM, FX_WaterRipple ) PRECACHE( PARTICLE_SYSTEM, "water_splash_02_surface2" ) //PRECACHE( MATERIAL, "effects/splashwake1" ) PRECACHE_REGISTER_END() //----------------------------------------------------------------------------- // Purpose: // Input : &origin - // &normal - //----------------------------------------------------------------------------- void FX_GunshotSplashVisuals( const Vector &origin, const Vector &normal, float scale ) { VPROF_BUDGET( "FX_GunshotSplash", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); if ( cl_show_splashes.GetBool() == false ) return; QAngle vecAngles; // we flip the z and the x to match the orientation of how the impact particles are authored // all impact particles are authored with the effect going "up" (0, 0, 1) VectorAngles( Vector( normal.z, normal.y, normal.x ), vecAngles ); if ( scale < 4.0f ) { DispatchParticleEffect( "water_splash_01", origin, vecAngles ); } else if ( scale < 8.0f ) { DispatchParticleEffect( "water_splash_02", origin, vecAngles ); } else { DispatchParticleEffect( "water_splash_03", origin, vecAngles ); } } void FX_GunshotSplashSound( const Vector &origin, const Vector &normal, float scale ) { //Play a sound CLocalPlayerFilter filter; EmitSound_t ep; ep.m_nChannel = CHAN_VOICE; ep.m_pSoundName = "Physics.WaterSplash"; ep.m_flVolume = 1.0f; ep.m_SoundLevel = SNDLVL_NORM; ep.m_pOrigin = &origin; C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, ep ); } PRECACHE_REGISTER_BEGIN( SHARED_SYSTEM, FX_GunshotSplash ) PRECACHE( PARTICLE_SYSTEM, "water_splash_01" ) PRECACHE( PARTICLE_SYSTEM, "water_splash_02" ) PRECACHE( PARTICLE_SYSTEM, "water_splash_03" ) //PRECACHE( MATERIAL, "effects/splash2" ) PRECACHE( GAMESOUND, "Physics.WaterSplash" ) PRECACHE_REGISTER_END() //----------------------------------------------------------------------------- // Purpose: // Input : &origin - // &normal - //----------------------------------------------------------------------------- void FX_GunshotSplash( const Vector &origin, const Vector &normal, float scale ) { VPROF_BUDGET( "FX_GunshotSplash", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); if ( cl_show_splashes.GetBool() == false ) return; FX_GunshotSplashVisuals( origin, normal, scale ); FX_GunshotSplashSound( origin, normal, scale ); } //----------------------------------------------------------------------------- // Purpose: // Input : &origin - // &normal - // scale - // *pColor - //----------------------------------------------------------------------------- void FX_GunshotSlimeSplash( const Vector &origin, const Vector &normal, float scale ) { if ( cl_show_splashes.GetBool() == false ) return; VPROF_BUDGET( "FX_GunshotSlimeSplash", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); QAngle vecAngles; // we flip the z and the x to match the orientation of how the impact particles are authored // all impact particles are authored with the effect going "up" (0, 0, 1) VectorAngles( Vector( normal.z, normal.y, normal.x ), vecAngles ); if ( scale < 2.0f ) { DispatchParticleEffect( "slime_splash_01", origin, vecAngles ); } else if ( scale < 4.0f ) { DispatchParticleEffect( "slime_splash_02", origin, vecAngles ); } else { DispatchParticleEffect( "slime_splash_03", origin, vecAngles ); } //Play a sound CLocalPlayerFilter filter; EmitSound_t ep; ep.m_nChannel = CHAN_VOICE; ep.m_pSoundName = "Physics.WaterSplash"; ep.m_flVolume = 1.0f; ep.m_SoundLevel = SNDLVL_NORM; ep.m_pOrigin = &origin; C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, ep ); } PRECACHE_REGISTER_BEGIN( SHARED_SYSTEM, FX_GunshotSlimeSplash ) PRECACHE( PARTICLE_SYSTEM, "slime_splash_01" ) PRECACHE( PARTICLE_SYSTEM, "slime_splash_02" ) PRECACHE( PARTICLE_SYSTEM, "slime_splash_03" ) PRECACHE( GAMESOUND, "Physics.WaterSplash" ) PRECACHE_REGISTER_END() //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void SplashCallback( const CEffectData &data ) { Vector normal; AngleVectors( data.m_vAngles, &normal ); if ( data.m_fFlags & FX_WATER_IN_SLIME ) { FX_GunshotSlimeSplash( data.m_vOrigin, Vector(0,0,1), data.m_flScale ); } else { FX_GunshotSplash( data.m_vOrigin, Vector(0,0,1), data.m_flScale ); } } DECLARE_CLIENT_EFFECT_BEGIN( watersplash, SplashCallback ) PRECACHE( SHARED, "FX_GunshotSlimeSplash" ) PRECACHE( SHARED, "FX_GunshotSplash" ) DECLARE_CLIENT_EFFECT_END() //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void SplashQuietCallback( const CEffectData &data ) { Vector normal; AngleVectors( data.m_vAngles, &normal ); if ( data.m_fFlags & FX_WATER_IN_SLIME ) { FX_GunshotSlimeSplash( data.m_vOrigin, Vector(0,0,1), data.m_flScale ); } else { FX_GunshotSplashVisuals( data.m_vOrigin, Vector(0,0,1), data.m_flScale ); } } DECLARE_CLIENT_EFFECT_BEGIN( watersplashquiet, SplashQuietCallback ) DECLARE_CLIENT_EFFECT_END() //----------------------------------------------------------------------------- // Purpose: // Input : &data - //----------------------------------------------------------------------------- void GunshotSplashCallback( const CEffectData &data ) { if ( data.m_fFlags & FX_WATER_IN_SLIME ) { FX_GunshotSlimeSplash( data.m_vOrigin, Vector(0,0,1), data.m_flScale ); } else { FX_GunshotSplash( data.m_vOrigin, Vector(0,0,1), data.m_flScale ); } } DECLARE_CLIENT_EFFECT_BEGIN( gunshotsplash, GunshotSplashCallback ) PRECACHE( SHARED, "FX_GunshotSlimeSplash" ) PRECACHE( SHARED, "FX_GunshotSplash" ) DECLARE_CLIENT_EFFECT_END() //----------------------------------------------------------------------------- // Purpose: // Input : &data - //----------------------------------------------------------------------------- void RippleCallback( const CEffectData &data ) { float flScale = data.m_flScale / 8.0f; Vector color; float luminosity; // Get our lighting information FX_GetSplashLighting( data.m_vOrigin + ( Vector(0,0,1) * 4.0f ), &color, &luminosity ); FX_WaterRipple( data.m_vOrigin, flScale, &color, 1.5f, luminosity ); } DECLARE_CLIENT_EFFECT_BEGIN( waterripple, RippleCallback ) PRECACHE( SHARED, "FX_WaterRipple" ) DECLARE_CLIENT_EFFECT_END() //----------------------------------------------------------------------------- // Purpose: // Input : *pDebugName - // Output : WaterDebrisEffect* //----------------------------------------------------------------------------- WaterDebrisEffect* WaterDebrisEffect::Create( const char *pDebugName ) { return new WaterDebrisEffect( pDebugName ); } //----------------------------------------------------------------------------- // Purpose: // Input : *pParticle - // timeDelta - // Output : float //----------------------------------------------------------------------------- float WaterDebrisEffect::UpdateAlpha( const SimpleParticle *pParticle ) { return ( ((float)pParticle->m_uchStartAlpha/255.0f) * sin( M_PI * (pParticle->m_flLifetime / pParticle->m_flDieTime) ) ); } //----------------------------------------------------------------------------- // Purpose: // Input : *pParticle - // timeDelta - // Output : float //----------------------------------------------------------------------------- float CSplashParticle::UpdateRoll( SimpleParticle *pParticle, float timeDelta ) { pParticle->m_flRoll += pParticle->m_flRollDelta * timeDelta; pParticle->m_flRollDelta += pParticle->m_flRollDelta * ( timeDelta * -4.0f ); //Cap the minimum roll if ( fabs( pParticle->m_flRollDelta ) < 0.5f ) { pParticle->m_flRollDelta = ( pParticle->m_flRollDelta > 0.0f ) ? 0.5f : -0.5f; } return pParticle->m_flRoll; } //----------------------------------------------------------------------------- // Purpose: // Input : *pParticle - // timeDelta - //----------------------------------------------------------------------------- void CSplashParticle::UpdateVelocity( SimpleParticle *pParticle, float timeDelta ) { //Decellerate static float dtime; static float decay; if ( dtime != timeDelta ) { dtime = timeDelta; float expected = 3.0f; decay = exp( log( 0.0001f ) * dtime / expected ); } pParticle->m_vecVelocity *= decay; pParticle->m_vecVelocity[2] -= ( 800.0f * timeDelta ); } //----------------------------------------------------------------------------- // Purpose: // Input : *pParticle - // Output : float //----------------------------------------------------------------------------- float CSplashParticle::UpdateAlpha( const SimpleParticle *pParticle ) { if ( m_bUseClipHeight ) { float flAlpha = pParticle->m_uchStartAlpha / 255.0f; return flAlpha * RemapValClamped(pParticle->m_Pos.z, m_flClipHeight, m_flClipHeight - ( UpdateScale( pParticle ) * 0.5f ), 1.0f, 0.0f ); } return (pParticle->m_uchStartAlpha/255.0f) + ( (float)(pParticle->m_uchEndAlpha/255.0f) - (float)(pParticle->m_uchStartAlpha/255.0f) ) * (pParticle->m_flLifetime / pParticle->m_flDieTime); } //----------------------------------------------------------------------------- // Purpose: // Input : &clipPlane - //----------------------------------------------------------------------------- void CSplashParticle::SetClipHeight( float flClipHeight ) { m_bUseClipHeight = true; m_flClipHeight = flClipHeight; } //----------------------------------------------------------------------------- // Purpose: // Input : *pIterator - //----------------------------------------------------------------------------- void CSplashParticle::SimulateParticles( CParticleSimulateIterator *pIterator ) { float timeDelta = pIterator->GetTimeDelta(); SimpleParticle *pParticle = (SimpleParticle*)pIterator->GetFirst(); while ( pParticle ) { //Update velocity UpdateVelocity( pParticle, timeDelta ); pParticle->m_Pos += pParticle->m_vecVelocity * timeDelta; // Clip by height if requested if ( m_bUseClipHeight ) { // See if we're below, and therefore need to clip if ( pParticle->m_Pos.z + UpdateScale( pParticle ) < m_flClipHeight ) { pIterator->RemoveParticle( pParticle ); pParticle = (SimpleParticle*)pIterator->GetNext(); continue; } } //Should this particle die? pParticle->m_flLifetime += timeDelta; UpdateRoll( pParticle, timeDelta ); if ( pParticle->m_flLifetime >= pParticle->m_flDieTime ) pIterator->RemoveParticle( pParticle ); pParticle = (SimpleParticle*)pIterator->GetNext(); } }