You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
484 lines
14 KiB
484 lines
14 KiB
//========= 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"
|
|
|
|
#ifndef DOTA_DLL
|
|
PRECACHE_REGISTER_BEGIN( GLOBAL, PrecacheEffectSplash )
|
|
PRECACHE( MATERIAL, "effects/splash1" )
|
|
PRECACHE( MATERIAL, "effects/splash2" )
|
|
PRECACHE( MATERIAL, "effects/splash4" )
|
|
PRECACHE( MATERIAL, "effects/slime1" )
|
|
PRECACHE_REGISTER_END()
|
|
#endif
|
|
|
|
|
|
#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 )
|
|
{
|
|
|
|
#if defined( _GAMECONSOLE )
|
|
|
|
// We don't want to generate ripples too close together on the console because it kills perf.
|
|
static float sNextRippleTime = 0.0f;
|
|
static float MIN_TIME_BETWEEN_RIPPLES = 0.05f;
|
|
|
|
float curTime = gpGlobals->curtime;
|
|
float nextRipple = curTime + MIN_TIME_BETWEEN_RIPPLES;
|
|
|
|
bool movedBackInTime = nextRipple < sNextRippleTime;
|
|
// If we've "moved back in time" then curtime propably got reset because we're in a new game.
|
|
// Since sNextRippleTime is static, we need to make sure to reset when curtime gets reset for the game.
|
|
if ( curTime < sNextRippleTime && !movedBackInTime )
|
|
{
|
|
return;
|
|
}
|
|
sNextRippleTime = nextRipple;
|
|
|
|
#endif
|
|
|
|
|
|
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 );
|
|
}
|
|
}
|
|
|
|
#ifndef DOTA_DLL
|
|
PRECACHE_REGISTER_BEGIN( SHARED_SYSTEM, FX_WaterRipple )
|
|
PRECACHE( PARTICLE_SYSTEM, "water_splash_02_surface2" )
|
|
//PRECACHE( MATERIAL, "effects/splashwake1" )
|
|
PRECACHE_REGISTER_END()
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 )
|
|
#ifndef DOTA_DLL
|
|
PRECACHE( PARTICLE_SYSTEM, "slime_splash_01" )
|
|
PRECACHE( PARTICLE_SYSTEM, "slime_splash_02" )
|
|
PRECACHE( PARTICLE_SYSTEM, "slime_splash_03" )
|
|
PRECACHE( GAMESOUND, "Physics.WaterSplash" )
|
|
#endif
|
|
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();
|
|
}
|
|
}
|