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.
2670 lines
82 KiB
2670 lines
82 KiB
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//
|
|
//===========================================================================//
|
|
#include "cbase.h"
|
|
#include "c_effects.h"
|
|
#include "c_tracer.h"
|
|
#include "view.h"
|
|
#include "particles_simple.h"
|
|
#include "env_wind_shared.h"
|
|
#include "engine/IEngineTrace.h"
|
|
#include "engine/ivmodelinfo.h"
|
|
#include "precipitation_shared.h"
|
|
#include "fx_water.h"
|
|
#include "c_world.h"
|
|
#include "iviewrender.h"
|
|
#include "engine/ivdebugoverlay.h"
|
|
#include "precache_register.h"
|
|
#include "collisionutils.h"
|
|
#include "tier0/vprof.h"
|
|
#include "viewrender.h"
|
|
#include "raytrace.h"
|
|
#ifdef INFESTED_DLL
|
|
#include "c_asw_player.h"
|
|
#include "c_asw_marine.h"
|
|
#include "asw_input.h"
|
|
#endif
|
|
#ifdef CSTRIKE15
|
|
#include "c_cs_player.h"
|
|
#endif
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
ConVar cl_winddir ( "cl_winddir", "0", FCVAR_CHEAT, "Weather effects wind direction angle" );
|
|
ConVar cl_windspeed ( "cl_windspeed", "0", FCVAR_CHEAT, "Weather effects wind speed scalar" );
|
|
|
|
Vector g_vSplashColor( 0.5, 0.5, 0.5 );
|
|
float g_flSplashScale = 0.15;
|
|
float g_flSplashLifetime = 0.5f;
|
|
float g_flSplashAlpha = 0.3f;
|
|
ConVar r_RainSplashPercentage( "r_RainSplashPercentage", "20", FCVAR_CHEAT ); // N% chance of a rain particle making a splash.
|
|
ConVar r_RainParticleDensity( "r_RainParticleDensity", "1", FCVAR_NONE, "Density of Particle Rain 0-1" );
|
|
ConVar r_RainAllowInSplitScreen( "r_RainAllowInSplitScreen", "0", FCVAR_NONE, "Allows rain in splitscreen" );
|
|
|
|
float GUST_INTERVAL_MIN = 1;
|
|
float GUST_INTERVAL_MAX = 2;
|
|
|
|
float GUST_LIFETIME_MIN = 1;
|
|
float GUST_LIFETIME_MAX = 3;
|
|
|
|
float MIN_SCREENSPACE_RAIN_WIDTH = 1;
|
|
|
|
ConVar r_RainHack( "r_RainHack", "0", FCVAR_CHEAT );
|
|
ConVar r_RainRadius( "r_RainRadius", "1500", FCVAR_CHEAT );
|
|
ConVar r_RainSideVel( "r_RainSideVel", "130", FCVAR_CHEAT, "How much sideways velocity rain gets." );
|
|
|
|
// Performance optimization by Certain Affinity
|
|
// calling IsInAir() for 800 particles was taking 4 ms
|
|
ConVar r_RainCheck( "r_RainCheck", "0", FCVAR_CHEAT, "Enable/disable IsInAir() check for rain drops?" );
|
|
|
|
ConVar r_RainSimulate( "r_RainSimulate", "1", FCVAR_CHEAT, "Enable/disable rain simulation." );
|
|
ConVar r_DrawRain( "r_DrawRain", "1", FCVAR_CHEAT, "Enable/disable rain rendering." );
|
|
ConVar r_RainProfile( "r_RainProfile", "0", FCVAR_CHEAT, "Enable/disable rain profiling." );
|
|
ConVar r_RainDebugDuration( "r_RainDebugDuration", "0", FCVAR_CHEAT, "Shows rain tracelines for this many seconds (0 disables)" );
|
|
|
|
|
|
//Precahce the effects
|
|
PRECACHE_REGISTER_BEGIN( GLOBAL, PrecachePrecipitation )
|
|
#ifndef PORTAL2
|
|
PRECACHE( MATERIAL, "particle/rain" )
|
|
PRECACHE( MATERIAL, "particle/snow" )
|
|
PRECACHE( PARTICLE_SYSTEM, "rain_storm" )
|
|
PRECACHE( PARTICLE_SYSTEM, "rain_storm_screen" )
|
|
PRECACHE( PARTICLE_SYSTEM, "rain_storm_outer" )
|
|
PRECACHE( PARTICLE_SYSTEM, "rain" )
|
|
PRECACHE( PARTICLE_SYSTEM, "rain_outer" )
|
|
PRECACHE( PARTICLE_SYSTEM, "ash" )
|
|
PRECACHE( PARTICLE_SYSTEM, "ash_outer" )
|
|
#ifdef INFESTED_DLL
|
|
PRECACHE( PARTICLE_SYSTEM, "asw_snow" )
|
|
PRECACHE( PARTICLE_SYSTEM, "asw_snow_outer" )
|
|
#else
|
|
PRECACHE( PARTICLE_SYSTEM, "snow" )
|
|
PRECACHE( PARTICLE_SYSTEM, "snow_outer" )
|
|
#endif
|
|
#endif // !PORTAL2
|
|
PRECACHE_REGISTER_END()
|
|
|
|
CUtlVector< RayTracingEnvironment* > g_RayTraceEnvironments;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Precipitation blocker entity
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Just receive the normal data table stuff
|
|
IMPLEMENT_CLIENTCLASS_DT(C_PrecipitationBlocker, DT_PrecipitationBlocker, CPrecipitationBlocker)
|
|
END_RECV_TABLE()
|
|
|
|
|
|
static CUtlVector< C_PrecipitationBlocker * > g_PrecipitationBlockers;
|
|
|
|
C_PrecipitationBlocker::C_PrecipitationBlocker()
|
|
{
|
|
g_PrecipitationBlockers.AddToTail( this );
|
|
}
|
|
|
|
C_PrecipitationBlocker::~C_PrecipitationBlocker()
|
|
{
|
|
g_PrecipitationBlockers.FindAndRemove( this );
|
|
}
|
|
|
|
bool ParticleIsBlocked( const Vector &end, const Vector &start )
|
|
{
|
|
for ( int i=0; i<g_PrecipitationBlockers.Count(); ++i )
|
|
{
|
|
C_PrecipitationBlocker *blocker = g_PrecipitationBlockers[i];
|
|
if ( blocker->CollisionProp()->IsPointInBounds( end ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Just receive the normal data table stuff
|
|
IMPLEMENT_CLIENTCLASS_DT(CClient_Precipitation, DT_Precipitation, CPrecipitation)
|
|
RecvPropInt( RECVINFO( m_nPrecipType ) ),
|
|
#ifdef INFESTED_DLL
|
|
RecvPropInt( RECVINFO( m_nSnowDustAmount ) ),
|
|
#endif
|
|
END_RECV_TABLE()
|
|
|
|
static ConVar r_SnowEnable( "r_SnowEnable", "1", FCVAR_CHEAT, "Snow Enable" );
|
|
static ConVar r_SnowParticles( "r_SnowParticles", "500", FCVAR_CHEAT, "Snow." );
|
|
static ConVar r_SnowInsideRadius( "r_SnowInsideRadius", "256", FCVAR_CHEAT, "Snow." );
|
|
static ConVar r_SnowOutsideRadius( "r_SnowOutsideRadius", "1024", FCVAR_CHEAT, "Snow." );
|
|
static ConVar r_SnowSpeedScale( "r_SnowSpeedScale", "1", FCVAR_CHEAT, "Snow." );
|
|
static ConVar r_SnowPosScale( "r_SnowPosScale", "1", FCVAR_CHEAT, "Snow." );
|
|
static ConVar r_SnowFallSpeed( "r_SnowFallSpeed", "1.5", FCVAR_CHEAT, "Snow fall speed scale." );
|
|
static ConVar r_SnowWindScale( "r_SnowWindScale", "0.0035", FCVAR_CHEAT, "Snow." );
|
|
static ConVar r_SnowDebugBox( "r_SnowDebugBox", "0", FCVAR_CHEAT, "Snow Debug Boxes." );
|
|
static ConVar r_SnowZoomOffset( "r_SnowZoomOffset", "384.0f", FCVAR_CHEAT, "Snow." );
|
|
static ConVar r_SnowZoomRadius( "r_SnowZoomRadius", "512.0f", FCVAR_CHEAT, "Snow." );
|
|
static ConVar r_SnowStartAlpha( "r_SnowStartAlpha", "25", FCVAR_CHEAT, "Snow." );
|
|
static ConVar r_SnowEndAlpha( "r_SnowEndAlpha", "255", FCVAR_CHEAT, "Snow." );
|
|
//static ConVar r_SnowColorRed( "r_SnowColorRed", "150", FCVAR_CHEAT, "Snow." );
|
|
//static ConVar r_SnowColorGreen( "r_SnowColorGreen", "175", FCVAR_CHEAT, "Snow." );
|
|
//static ConVar r_SnowColorBlue( "r_SnowColorBlue", "200", FCVAR_CHEAT, "Snow." );
|
|
static ConVar r_SnowStartSize( "r_SnowStartSize", "1", FCVAR_CHEAT, "Snow." );
|
|
static ConVar r_SnowEndSize( "r_SnowEndSize", "0", FCVAR_CHEAT, "Snow." );
|
|
static ConVar r_SnowRayLength( "r_SnowRayLength", "8192.0f", FCVAR_CHEAT, "Snow." );
|
|
static ConVar r_SnowRayRadius( "r_SnowRayRadius", "256", FCVAR_CHEAT, "Snow." );
|
|
static ConVar r_SnowRayEnable( "r_SnowRayEnable", "1", FCVAR_CHEAT, "Snow." );
|
|
|
|
void DrawPrecipitation()
|
|
{
|
|
for ( int i=0; i < g_Precipitations.Count(); i++ )
|
|
{
|
|
g_Precipitations[i]->Render();
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// determines if a weather particle has hit something other than air
|
|
//-----------------------------------------------------------------------------
|
|
static bool IsInAir( const Vector& position )
|
|
{
|
|
int contents = enginetrace->GetPointContents( position );
|
|
return (contents & CONTENTS_SOLID) == 0;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Globals
|
|
//-----------------------------------------------------------------------------
|
|
|
|
ConVar CClient_Precipitation::s_raindensity( "r_raindensity","0.001", FCVAR_CHEAT);
|
|
ConVar CClient_Precipitation::s_rainwidth( "r_rainwidth", "0.5", FCVAR_CHEAT );
|
|
ConVar CClient_Precipitation::s_rainlength( "r_rainlength", "0.1f", FCVAR_CHEAT );
|
|
ConVar CClient_Precipitation::s_rainspeed( "r_rainspeed", "600.0f", FCVAR_CHEAT );
|
|
ConVar r_rainalpha( "r_rainalpha", "0.4", FCVAR_CHEAT );
|
|
ConVar r_rainalphapow( "r_rainalphapow", "0.8", FCVAR_CHEAT );
|
|
|
|
|
|
Vector CClient_Precipitation::s_WindVector; // Stores the wind speed vector
|
|
|
|
|
|
void CClient_Precipitation::OnDataChanged( DataUpdateType_t updateType )
|
|
{
|
|
// Simulate every frame.
|
|
if ( updateType == DATA_UPDATE_CREATED )
|
|
{
|
|
SetNextClientThink( CLIENT_THINK_ALWAYS );
|
|
if ( m_nPrecipType == PRECIPITATION_TYPE_SNOWFALL )
|
|
{
|
|
SnowFallManagerCreate( this );
|
|
}
|
|
}
|
|
|
|
m_flDensity = RemapVal( GetRenderAlpha(), 0, 100, 0, 1 );
|
|
|
|
BaseClass::OnDataChanged( updateType );
|
|
}
|
|
|
|
|
|
void CClient_Precipitation::ClientThink()
|
|
{
|
|
Simulate( gpGlobals->frametime );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// Utility methods for the various simulation functions
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
inline bool CClient_Precipitation::SimulateRain( CPrecipitationParticle* pParticle, float dt )
|
|
{
|
|
if (GetRemainingLifetime( pParticle ) < 0.0f)
|
|
return false;
|
|
|
|
Vector vOldPos = pParticle->m_Pos;
|
|
|
|
// Update position
|
|
VectorMA( pParticle->m_Pos, dt, pParticle->m_Velocity,
|
|
pParticle->m_Pos );
|
|
|
|
if (cl_windspeed.GetFloat() > 0) // determines if s_WindVector is zeroes
|
|
{
|
|
// wind blows rain around
|
|
float drift = 5 / pParticle->m_Mass;
|
|
for ( int i = 0 ; i < 2 ; i++ ) // X and Y components
|
|
{
|
|
float vel = pParticle->m_Velocity[i];
|
|
float wind = s_WindVector[i];
|
|
if ( vel < wind )
|
|
{
|
|
vel = MIN( vel+drift, wind);
|
|
}
|
|
else if ( vel > wind )
|
|
{
|
|
vel = MAX( vel-drift, wind);
|
|
}
|
|
pParticle->m_Velocity[i] = vel;
|
|
}
|
|
}
|
|
|
|
// Left4Dead does not use rain splashes on water surfaces
|
|
// This change could allow rain into some solids, but the code
|
|
// already performed a ray-test to calculate the particle lifetime,
|
|
// so it should still be blocked by normal bsp surfaces.
|
|
if (r_RainCheck.GetInt() != 0)
|
|
{
|
|
// No longer in the air? punt.
|
|
if ( !IsInAir( pParticle->m_Pos ) )
|
|
{
|
|
// Possibly make a splash if we hit a water surface and it's in front of the view.
|
|
if ( m_Splashes.Count() < 20 )
|
|
{
|
|
if ( RandomInt( 0, 100 ) < r_RainSplashPercentage.GetInt() )
|
|
{
|
|
trace_t trace;
|
|
UTIL_TraceLine(vOldPos, pParticle->m_Pos, MASK_WATER, NULL, COLLISION_GROUP_NONE, &trace);
|
|
if( trace.fraction < 1 )
|
|
{
|
|
m_Splashes.AddToTail( trace.endpos );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tell the framework it's time to remove the particle from the list
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// We still want this particle
|
|
return true;
|
|
}
|
|
|
|
|
|
inline bool CClient_Precipitation::SimulateSnow( CPrecipitationParticle* pParticle, float dt )
|
|
{
|
|
if ( IsInAir( pParticle->m_Pos ) )
|
|
{
|
|
// Update position
|
|
VectorMA( pParticle->m_Pos, dt, pParticle->m_Velocity,
|
|
pParticle->m_Pos );
|
|
|
|
// wind blows rain around
|
|
for ( int i = 0 ; i < 2 ; i++ )
|
|
{
|
|
if ( pParticle->m_Velocity[i] < s_WindVector[i] )
|
|
{
|
|
pParticle->m_Velocity[i] += ( 5.0f / pParticle->m_Mass );
|
|
|
|
// accelerating flakes get a trail
|
|
pParticle->m_Ramp = 0.5f;
|
|
|
|
// clamp
|
|
if ( pParticle->m_Velocity[i] > s_WindVector[i] )
|
|
pParticle->m_Velocity[i] = s_WindVector[i];
|
|
}
|
|
else if (pParticle->m_Velocity[i] > s_WindVector[i] )
|
|
{
|
|
pParticle->m_Velocity[i] -= ( 5.0f / pParticle->m_Mass );
|
|
|
|
// accelerating flakes get a trail
|
|
pParticle->m_Ramp = 0.5f;
|
|
|
|
// clamp.
|
|
if ( pParticle->m_Velocity[i] < s_WindVector[i] )
|
|
pParticle->m_Velocity[i] = s_WindVector[i];
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Kill the particle immediately!
|
|
return false;
|
|
}
|
|
|
|
|
|
void CClient_Precipitation::Simulate( float dt )
|
|
{
|
|
if ( m_nPrecipType == PRECIPITATION_TYPE_PARTICLERAIN ||
|
|
m_nPrecipType == PRECIPITATION_TYPE_PARTICLEASH ||
|
|
m_nPrecipType == PRECIPITATION_TYPE_PARTICLERAINSTORM ||
|
|
m_nPrecipType == PRECIPITATION_TYPE_PARTICLESNOW )
|
|
{
|
|
CreateParticlePrecip();
|
|
return;
|
|
}
|
|
|
|
// NOTE: When client-side prechaching works, we need to remove this
|
|
Precache();
|
|
|
|
m_flHalfScreenWidth = (float)ScreenWidth() / 2;
|
|
|
|
// Our sim methods needs dt and wind vector
|
|
if ( dt )
|
|
{
|
|
ComputeWindVector( );
|
|
}
|
|
|
|
if ( m_nPrecipType == PRECIPITATION_TYPE_ASH )
|
|
{
|
|
CreateAshParticle();
|
|
return;
|
|
}
|
|
|
|
// The snow fall manager handles the simulation.
|
|
if ( m_nPrecipType == PRECIPITATION_TYPE_SNOWFALL )
|
|
return;
|
|
|
|
// calculate the max amount of time it will take this flake to fall.
|
|
// This works if we assume the wind doesn't have a z component
|
|
if ( r_RainHack.GetInt() )
|
|
m_Lifetime = (GetClientWorldEntity()->m_WorldMaxs[2] - GetClientWorldEntity()->m_WorldMins[2]) / m_Speed;
|
|
else
|
|
m_Lifetime = (WorldAlignMaxs()[2] - WorldAlignMins()[2]) / m_Speed;
|
|
|
|
|
|
if ( !r_RainSimulate.GetInt() )
|
|
return;
|
|
|
|
CFastTimer timer;
|
|
timer.Start();
|
|
|
|
// Emit new particles
|
|
EmitParticles( dt );
|
|
|
|
// Simulate all the particles.
|
|
int iNext;
|
|
if ( m_nPrecipType == PRECIPITATION_TYPE_RAIN )
|
|
{
|
|
for ( int i=m_Particles.Head(); i != m_Particles.InvalidIndex(); i=iNext )
|
|
{
|
|
iNext = m_Particles.Next( i );
|
|
if ( !SimulateRain( &m_Particles[i], dt ) )
|
|
m_Particles.Remove( i );
|
|
}
|
|
}
|
|
else if ( m_nPrecipType == PRECIPITATION_TYPE_SNOW )
|
|
{
|
|
for ( int i=m_Particles.Head(); i != m_Particles.InvalidIndex(); i=iNext )
|
|
{
|
|
iNext = m_Particles.Next( i );
|
|
if ( !SimulateSnow( &m_Particles[i], dt ) )
|
|
m_Particles.Remove( i );
|
|
}
|
|
}
|
|
|
|
if ( r_RainProfile.GetInt() )
|
|
{
|
|
timer.End();
|
|
engine->Con_NPrintf( 15, "Rain simulation: %du (%d tracers)", timer.GetDuration().GetMicroseconds(), m_Particles.Count() );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// tracer rendering
|
|
//-----------------------------------------------------------------------------
|
|
|
|
inline void CClient_Precipitation::RenderParticle( CPrecipitationParticle* pParticle, CMeshBuilder &mb )
|
|
{
|
|
float scale;
|
|
Vector start, delta;
|
|
|
|
if ( m_nPrecipType == PRECIPITATION_TYPE_PARTICLERAIN || m_nPrecipType == PRECIPITATION_TYPE_PARTICLEASH
|
|
|| m_nPrecipType == PRECIPITATION_TYPE_PARTICLERAINSTORM || m_nPrecipType == PRECIPITATION_TYPE_PARTICLESNOW )
|
|
return;
|
|
|
|
if ( m_nPrecipType == PRECIPITATION_TYPE_ASH )
|
|
return;
|
|
|
|
if ( m_nPrecipType == PRECIPITATION_TYPE_SNOWFALL )
|
|
return;
|
|
|
|
|
|
// make streaks 0.1 seconds long, but prevent from going past end
|
|
float lifetimeRemaining = GetRemainingLifetime( pParticle );
|
|
if (lifetimeRemaining >= GetLength())
|
|
scale = GetLength() * pParticle->m_Ramp;
|
|
else
|
|
scale = lifetimeRemaining * pParticle->m_Ramp;
|
|
|
|
// NOTE: We need to do everything in screen space
|
|
Vector3DMultiplyPosition( CurrentWorldToViewMatrix(), pParticle->m_Pos, start );
|
|
if ( start.z > -1 )
|
|
return;
|
|
|
|
Vector3DMultiply( CurrentWorldToViewMatrix(), pParticle->m_Velocity, delta );
|
|
|
|
// give a spiraling pattern to snow particles
|
|
if ( m_nPrecipType == PRECIPITATION_TYPE_SNOW )
|
|
{
|
|
Vector spiral, camSpiral;
|
|
float s, c;
|
|
|
|
if ( pParticle->m_Mass > 1.0f )
|
|
{
|
|
SinCos( gpGlobals->curtime * M_PI * (1+pParticle->m_Mass * 0.1f) +
|
|
pParticle->m_Mass * 5.0f, &s , &c );
|
|
|
|
// only spiral particles with a mass > 1, so some fall straight down
|
|
spiral[0] = 28 * c;
|
|
spiral[1] = 28 * s;
|
|
spiral[2] = 0.0f;
|
|
|
|
Vector3DMultiply( CurrentWorldToViewMatrix(), spiral, camSpiral );
|
|
|
|
// X and Y are measured in world space; need to convert to camera space
|
|
VectorAdd( start, camSpiral, start );
|
|
VectorAdd( delta, camSpiral, delta );
|
|
}
|
|
|
|
// shrink the trails on spiraling flakes.
|
|
pParticle->m_Ramp = 0.3f;
|
|
}
|
|
|
|
delta[0] *= scale;
|
|
delta[1] *= scale;
|
|
delta[2] *= scale;
|
|
|
|
// See c_tracer.* for this method
|
|
float flAlpha = r_rainalpha.GetFloat();
|
|
float flWidth = GetWidth();
|
|
|
|
float flScreenSpaceWidth = flWidth * m_flHalfScreenWidth / -start.z;
|
|
if ( flScreenSpaceWidth < MIN_SCREENSPACE_RAIN_WIDTH )
|
|
{
|
|
// Make the rain tracer at least the min size, but fade its alpha the smaller it gets.
|
|
flAlpha *= flScreenSpaceWidth / MIN_SCREENSPACE_RAIN_WIDTH;
|
|
flWidth = MIN_SCREENSPACE_RAIN_WIDTH * -start.z / m_flHalfScreenWidth;
|
|
}
|
|
flAlpha = pow( flAlpha, r_rainalphapow.GetFloat() );
|
|
|
|
float flColor[4] = { 1, 1, 1, flAlpha };
|
|
Tracer_Draw( &mb, start, delta, flWidth, flColor, 1 );
|
|
}
|
|
|
|
|
|
void CClient_Precipitation::CreateWaterSplashes()
|
|
{
|
|
for ( int i=0; i < m_Splashes.Count(); i++ )
|
|
{
|
|
Vector vSplash = m_Splashes[i];
|
|
|
|
if ( CurrentViewForward().Dot( vSplash - CurrentViewOrigin() ) > 1 )
|
|
{
|
|
FX_WaterRipple( vSplash, g_flSplashScale, &g_vSplashColor, g_flSplashLifetime, g_flSplashAlpha );
|
|
}
|
|
}
|
|
m_Splashes.Purge();
|
|
}
|
|
|
|
|
|
void CClient_Precipitation::Render()
|
|
{
|
|
ASSERT_LOCAL_PLAYER_RESOLVABLE();
|
|
int nSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
|
|
if ( !r_DrawRain.GetInt() )
|
|
return;
|
|
|
|
if ( m_nPrecipType == PRECIPITATION_TYPE_PARTICLERAIN || m_nPrecipType == PRECIPITATION_TYPE_PARTICLEASH
|
|
|| m_nPrecipType == PRECIPITATION_TYPE_PARTICLERAINSTORM || m_nPrecipType == PRECIPITATION_TYPE_PARTICLESNOW )
|
|
return;
|
|
|
|
// Don't render in monitors or in reflections or refractions.
|
|
if ( CurrentViewID() == VIEW_MONITOR )
|
|
return;
|
|
|
|
if ( view->GetDrawFlags() & (DF_RENDER_REFLECTION | DF_RENDER_REFRACTION) )
|
|
return;
|
|
|
|
if ( m_nPrecipType == PRECIPITATION_TYPE_ASH )
|
|
return;
|
|
|
|
if ( m_nPrecipType == PRECIPITATION_TYPE_SNOWFALL )
|
|
return;
|
|
|
|
// Create any queued up water splashes.
|
|
CreateWaterSplashes();
|
|
|
|
|
|
CFastTimer timer;
|
|
timer.Start();
|
|
|
|
CMatRenderContextPtr pRenderContext( materials );
|
|
|
|
// We want to do our calculations in view space.
|
|
VMatrix tempView;
|
|
pRenderContext->GetMatrix( MATERIAL_VIEW, &tempView );
|
|
pRenderContext->MatrixMode( MATERIAL_VIEW );
|
|
pRenderContext->LoadIdentity();
|
|
|
|
// Force the user clip planes to use the old view matrix
|
|
pRenderContext->EnableUserClipTransformOverride( true );
|
|
pRenderContext->UserClipTransform( tempView );
|
|
|
|
// Draw all the rain tracers.
|
|
pRenderContext->Bind( m_MatHandle );
|
|
IMesh *pMesh = pRenderContext->GetDynamicMesh();
|
|
if ( pMesh )
|
|
{
|
|
CMeshBuilder mb;
|
|
mb.Begin( pMesh, MATERIAL_QUADS, m_Particles.Count() );
|
|
|
|
for ( int i=m_Particles.Head(); i != m_Particles.InvalidIndex(); i=m_Particles.Next( i ) )
|
|
{
|
|
CPrecipitationParticle *p = &m_Particles[i];
|
|
if ( p->m_nSplitScreenPlayerSlot != nSlot )
|
|
continue;
|
|
RenderParticle( p, mb );
|
|
}
|
|
|
|
mb.End( false, true );
|
|
}
|
|
|
|
pRenderContext->EnableUserClipTransformOverride( false );
|
|
pRenderContext->MatrixMode( MATERIAL_VIEW );
|
|
pRenderContext->LoadMatrix( tempView );
|
|
|
|
if ( r_RainProfile.GetInt() )
|
|
{
|
|
timer.End();
|
|
engine->Con_NPrintf( 16, "Rain render : %du", timer.GetDuration().GetMicroseconds() );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Constructor, destructor
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CClient_Precipitation::CClient_Precipitation() : m_Remainder(0.0f)
|
|
{
|
|
m_nPrecipType = PRECIPITATION_TYPE_RAIN;
|
|
m_MatHandle = INVALID_MATERIAL_HANDLE;
|
|
m_flHalfScreenWidth = 1;
|
|
|
|
for ( int i = 0; i < MAX_SPLITSCREEN_PLAYERS; ++i )
|
|
{
|
|
m_pParticlePrecipInnerNear[ i ] = NULL;
|
|
m_pParticlePrecipInnerFar[ i ] = NULL;
|
|
m_pParticlePrecipOuter[ i ] = NULL;
|
|
m_bActiveParticlePrecipEmitter[ i ] = false;
|
|
}
|
|
|
|
g_Precipitations.AddToTail( this );
|
|
}
|
|
|
|
CClient_Precipitation::~CClient_Precipitation()
|
|
{
|
|
g_Precipitations.FindAndRemove( this );
|
|
SnowFallManagerDestroy();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Precache data
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#define SNOW_SPEED 80.0f
|
|
#define RAIN_SPEED 425.0f
|
|
|
|
#define RAIN_TRACER_WIDTH 0.35f
|
|
#define SNOW_TRACER_WIDTH 0.7f
|
|
|
|
void CClient_Precipitation::Precache( )
|
|
{
|
|
if ( !m_MatHandle )
|
|
{
|
|
// Compute precipitation emission speed
|
|
switch( m_nPrecipType )
|
|
{
|
|
case PRECIPITATION_TYPE_SNOW:
|
|
m_Speed = SNOW_SPEED;
|
|
m_MatHandle = materials->FindMaterial( "particle/snow", TEXTURE_GROUP_CLIENT_EFFECTS );
|
|
m_InitialRamp = 0.6f;
|
|
m_Width = SNOW_TRACER_WIDTH;
|
|
break;
|
|
|
|
case PRECIPITATION_TYPE_RAIN:
|
|
Assert( m_nPrecipType == PRECIPITATION_TYPE_RAIN );
|
|
m_Speed = RAIN_SPEED;
|
|
m_MatHandle = materials->FindMaterial( "particle/rain", TEXTURE_GROUP_CLIENT_EFFECTS );
|
|
m_InitialRamp = 1.0f;
|
|
m_Color[3] = 1.0f; // make translucent
|
|
m_Width = RAIN_TRACER_WIDTH;
|
|
break;
|
|
default:
|
|
m_InitialRamp = 1.0f;
|
|
m_Color[3] = 1.0f; // make translucent
|
|
break;
|
|
}
|
|
|
|
// Store off the color
|
|
m_Color[0] = 1.0f;
|
|
m_Color[1] = 1.0f;
|
|
m_Color[2] = 1.0f;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Gets the tracer width and speed
|
|
//-----------------------------------------------------------------------------
|
|
|
|
inline float CClient_Precipitation::GetWidth() const
|
|
{
|
|
// return m_Width;
|
|
return s_rainwidth.GetFloat();
|
|
}
|
|
|
|
inline float CClient_Precipitation::GetLength() const
|
|
{
|
|
// return m_Length;
|
|
return s_rainlength.GetFloat();
|
|
}
|
|
|
|
inline float CClient_Precipitation::GetSpeed() const
|
|
{
|
|
// return m_Speed;
|
|
return s_rainspeed.GetFloat();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Gets the remaining lifetime of the particle
|
|
//-----------------------------------------------------------------------------
|
|
|
|
inline float CClient_Precipitation::GetRemainingLifetime( CPrecipitationParticle* pParticle ) const
|
|
{
|
|
float timeSinceSpawn = gpGlobals->curtime - pParticle->m_SpawnTime;
|
|
return pParticle->m_flMaxLifetime - timeSinceSpawn; // TERROR: use per-particle lifetime not dependent on func_precipitation lower bound
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Creates a particle
|
|
//-----------------------------------------------------------------------------
|
|
|
|
inline CPrecipitationParticle* CClient_Precipitation::CreateParticle()
|
|
{
|
|
int i = m_Particles.AddToTail();
|
|
CPrecipitationParticle* pParticle = &m_Particles[i];
|
|
|
|
pParticle->m_SpawnTime = gpGlobals->curtime;
|
|
pParticle->m_Ramp = m_InitialRamp;
|
|
|
|
ASSERT_LOCAL_PLAYER_RESOLVABLE();
|
|
pParticle->m_nSplitScreenPlayerSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
|
|
|
|
return pParticle;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Compute the emission area
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool CClient_Precipitation::ComputeEmissionArea( Vector& origin, Vector2D& size, C_BaseCombatCharacter *pCharacter )
|
|
{
|
|
// calculate a volume around the player to snow in. Intersect this big magic
|
|
// box around the player with the volume of the current environmental ent.
|
|
|
|
if ( !pCharacter )
|
|
return false;
|
|
|
|
// FIXME: Compute the precipitation area based on computational power
|
|
float emissionSize = r_RainRadius.GetFloat(); // size of box to emit particles in
|
|
|
|
Vector vMins = WorldAlignMins();
|
|
Vector vMaxs = WorldAlignMaxs();
|
|
if ( r_RainHack.GetInt() )
|
|
{
|
|
vMins = GetClientWorldEntity()->m_WorldMins;
|
|
vMaxs = GetClientWorldEntity()->m_WorldMaxs;
|
|
}
|
|
|
|
// Determine how much time it'll take a falling particle to hit the player
|
|
float emissionHeight = MIN( vMaxs[2], pCharacter->GetAbsOrigin()[2] + 512 );
|
|
float distToFall = emissionHeight - pCharacter->GetAbsOrigin()[2];
|
|
float fallTime = distToFall / GetSpeed();
|
|
|
|
// Based on the windspeed, figure out the center point of the emission
|
|
Vector2D center;
|
|
center[0] = pCharacter->GetAbsOrigin()[0] - fallTime * s_WindVector[0];
|
|
center[1] = pCharacter->GetAbsOrigin()[1] - fallTime * s_WindVector[1];
|
|
|
|
Vector2D lobound, hibound;
|
|
lobound[0] = center[0] - emissionSize * 0.5f;
|
|
lobound[1] = center[1] - emissionSize * 0.5f;
|
|
hibound[0] = lobound[0] + emissionSize;
|
|
hibound[1] = lobound[1] + emissionSize;
|
|
|
|
// Cull non-intersecting.
|
|
if ( ( vMaxs[0] < lobound[0] ) || ( vMaxs[1] < lobound[1] ) ||
|
|
( vMins[0] > hibound[0] ) || ( vMins[1] > hibound[1] ) )
|
|
return false;
|
|
|
|
origin[0] = MAX( vMins[0], lobound[0] );
|
|
origin[1] = MAX( vMins[1], lobound[1] );
|
|
origin[2] = emissionHeight;
|
|
|
|
hibound[0] = MIN( vMaxs[0], hibound[0] );
|
|
hibound[1] = MIN( vMaxs[1], hibound[1] );
|
|
|
|
size[0] = hibound[0] - origin[0];
|
|
size[1] = hibound[1] - origin[1];
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pDebugName -
|
|
// Output : AshDebrisEffect*
|
|
//-----------------------------------------------------------------------------
|
|
AshDebrisEffect* AshDebrisEffect::Create( const char *pDebugName )
|
|
{
|
|
return new AshDebrisEffect( pDebugName );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pParticle -
|
|
// timeDelta -
|
|
// Output : float
|
|
//-----------------------------------------------------------------------------
|
|
float AshDebrisEffect::UpdateAlpha( const SimpleParticle *pParticle )
|
|
{
|
|
return ( ((float)pParticle->m_uchStartAlpha/255.0f) * sin( M_PI * (pParticle->m_flLifetime / pParticle->m_flDieTime) ) );
|
|
}
|
|
|
|
#define ASH_PARTICLE_NOISE 0x4
|
|
|
|
float AshDebrisEffect::UpdateRoll( SimpleParticle *pParticle, float timeDelta )
|
|
{
|
|
float flRoll = CSimpleEmitter::UpdateRoll(pParticle, timeDelta );
|
|
|
|
if ( pParticle->m_iFlags & ASH_PARTICLE_NOISE )
|
|
{
|
|
Vector vTempEntVel = pParticle->m_vecVelocity;
|
|
float fastFreq = gpGlobals->curtime * 1.5;
|
|
|
|
float s, c;
|
|
SinCos( fastFreq, &s, &c );
|
|
|
|
pParticle->m_Pos = ( pParticle->m_Pos + Vector(
|
|
vTempEntVel[0] * timeDelta * s,
|
|
vTempEntVel[1] * timeDelta * s, 0 ) );
|
|
}
|
|
|
|
return flRoll;
|
|
}
|
|
|
|
void CClient_Precipitation::CreateAshParticle( void )
|
|
{
|
|
FOR_EACH_VALID_SPLITSCREEN_PLAYER( hh )
|
|
{
|
|
ACTIVE_SPLITSCREEN_PLAYER_GUARD( hh );
|
|
// Make sure the emitter is setup
|
|
if ( m_Ash[ hh ].m_pAshEmitter == NULL )
|
|
{
|
|
if ( ( m_Ash[ hh ].m_pAshEmitter = AshDebrisEffect::Create( "ashtray" ) ) == NULL )
|
|
continue;
|
|
|
|
m_Ash[ hh ].m_tAshParticleTimer.Init( 192 );
|
|
m_Ash[ hh ].m_tAshParticleTraceTimer.Init( 15 );
|
|
m_Ash[ hh ].m_bActiveAshEmitter = false;
|
|
m_Ash[ hh ].m_iAshCount = 0;
|
|
m_Ash[ hh ].m_pAshEmitter->SetShouldDrawForSplitScreenUser( hh );
|
|
}
|
|
|
|
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
|
|
if ( pPlayer == NULL )
|
|
continue;
|
|
|
|
Vector vForward;
|
|
pPlayer->GetVectors( &vForward, NULL, NULL );
|
|
vForward.z = 0.0f;
|
|
|
|
float curTime = gpGlobals->frametime;
|
|
|
|
Vector vPushOrigin;
|
|
|
|
Vector absmins = WorldAlignMins();
|
|
Vector absmaxs = WorldAlignMaxs();
|
|
|
|
//15 Traces a second.
|
|
while ( m_Ash[ hh ].m_tAshParticleTraceTimer.NextEvent( curTime ) )
|
|
{
|
|
trace_t tr;
|
|
|
|
Vector vTraceStart = pPlayer->EyePosition();
|
|
Vector vTraceEnd = pPlayer->EyePosition() + vForward * MAX_TRACE_LENGTH;
|
|
|
|
UTIL_TraceLine( vTraceStart, vTraceEnd, MASK_SHOT_HULL & (~CONTENTS_GRATE), pPlayer, COLLISION_GROUP_NONE, &tr );
|
|
//UTIL_TraceModel( vTraceStart, vTraceEnd, Vector( -1, -1, -1 ), Vector( 1, 1, 1 ), this, COLLISION_GROUP_NONE, &tr );
|
|
//debugoverlay->AddLineOverlay( vTraceStart, tr.endpos, 255, 0, 0, 0, 0.2 );
|
|
|
|
if ( tr.fraction != 1.0f )
|
|
{
|
|
trace_t tr2;
|
|
|
|
UTIL_TraceModel( vTraceStart, tr.endpos, Vector( -1, -1, -1 ), Vector( 1, 1, 1 ), this, COLLISION_GROUP_NONE, &tr2 );
|
|
|
|
if ( tr2.m_pEnt == this )
|
|
{
|
|
m_Ash[ hh ].m_bActiveAshEmitter = true;
|
|
|
|
if ( tr2.startsolid == false )
|
|
{
|
|
m_Ash[ hh ].m_vAshSpawnOrigin = tr2.endpos + vForward * 256;
|
|
}
|
|
else
|
|
{
|
|
m_Ash[ hh ].m_vAshSpawnOrigin = vTraceStart;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_Ash[ hh ].m_bActiveAshEmitter = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( m_Ash[ hh ].m_bActiveAshEmitter == false )
|
|
continue;
|
|
|
|
Vector vecVelocity = pPlayer->GetAbsVelocity();
|
|
|
|
|
|
float flVelocity = VectorNormalize( vecVelocity );
|
|
Vector offset = m_Ash[ hh ].m_vAshSpawnOrigin;
|
|
|
|
m_Ash[ hh ].m_pAshEmitter->SetSortOrigin( offset );
|
|
|
|
PMaterialHandle hMaterial[4];
|
|
hMaterial[0] = ParticleMgr()->GetPMaterial( "effects/fleck_ash1" );
|
|
hMaterial[1] = ParticleMgr()->GetPMaterial( "effects/fleck_ash2" );
|
|
hMaterial[2] = ParticleMgr()->GetPMaterial( "effects/fleck_ash3" );
|
|
hMaterial[3] = ParticleMgr()->GetPMaterial( "effects/ember_swirling001" );
|
|
|
|
SimpleParticle *pParticle;
|
|
|
|
Vector vSpawnOrigin = vec3_origin;
|
|
|
|
if ( flVelocity > 0 )
|
|
{
|
|
vSpawnOrigin = ( vForward * 256 ) + ( vecVelocity * ( flVelocity * 2 ) );
|
|
}
|
|
|
|
// Add as many particles as we need
|
|
while ( m_Ash[ hh ].m_tAshParticleTimer.NextEvent( curTime ) )
|
|
{
|
|
int iRandomAltitude = RandomInt( 0, 128 );
|
|
|
|
offset = m_Ash[ hh ].m_vAshSpawnOrigin + vSpawnOrigin + RandomVector( -256, 256 );
|
|
offset.z = m_Ash[ hh ].m_vAshSpawnOrigin.z + iRandomAltitude;
|
|
|
|
if ( offset[0] > absmaxs[0]
|
|
|| offset[1] > absmaxs[1]
|
|
|| offset[2] > absmaxs[2]
|
|
|| offset[0] < absmins[0]
|
|
|| offset[1] < absmins[1]
|
|
|| offset[2] < absmins[2] )
|
|
continue;
|
|
|
|
m_Ash[ hh ].m_iAshCount++;
|
|
|
|
bool bEmberTime = false;
|
|
|
|
if ( m_Ash[ hh ].m_iAshCount >= 250 )
|
|
{
|
|
bEmberTime = true;
|
|
m_Ash[ hh ].m_iAshCount = 0;
|
|
}
|
|
|
|
int iRandom = random->RandomInt(0,2);
|
|
|
|
if ( bEmberTime == true )
|
|
{
|
|
offset = m_Ash[ hh ].m_vAshSpawnOrigin + (vForward * 256) + RandomVector( -128, 128 );
|
|
offset.z = pPlayer->EyePosition().z + RandomFloat( -16, 64 );
|
|
|
|
iRandom = 3;
|
|
}
|
|
|
|
pParticle = (SimpleParticle *)m_Ash[ hh ].m_pAshEmitter->AddParticle( sizeof(SimpleParticle), hMaterial[iRandom], offset );
|
|
|
|
if (pParticle == NULL)
|
|
continue;
|
|
|
|
pParticle->m_flLifetime = 0.0f;
|
|
pParticle->m_flDieTime = RemapVal( iRandomAltitude, 0, 128, 4, 8 );
|
|
|
|
if ( bEmberTime == true )
|
|
{
|
|
Vector vGoal = pPlayer->EyePosition() + RandomVector( -64, 64 );
|
|
Vector vDir = vGoal - offset;
|
|
VectorNormalize( vDir );
|
|
|
|
pParticle->m_vecVelocity = vDir * 75;
|
|
pParticle->m_flDieTime = 2.5f;
|
|
}
|
|
else
|
|
{
|
|
pParticle->m_vecVelocity = Vector( RandomFloat( -20.0f, 20.0f ), RandomFloat( -20.0f, 20.0f ), RandomFloat( -10, -15 ) );
|
|
}
|
|
|
|
float color = random->RandomInt( 125, 225 );
|
|
pParticle->m_uchColor[0] = color;
|
|
pParticle->m_uchColor[1] = color;
|
|
pParticle->m_uchColor[2] = color;
|
|
|
|
pParticle->m_uchStartSize = 1;
|
|
pParticle->m_uchEndSize = 1.5;
|
|
|
|
pParticle->m_uchStartAlpha = 255;
|
|
|
|
pParticle->m_flRoll = random->RandomInt( 0, 360 );
|
|
pParticle->m_flRollDelta = random->RandomFloat( -0.15f, 0.15f );
|
|
|
|
pParticle->m_iFlags = SIMPLE_PARTICLE_FLAG_WINDBLOWN;
|
|
|
|
if ( random->RandomInt( 0, 10 ) <= 1 )
|
|
{
|
|
pParticle->m_iFlags |= ASH_PARTICLE_NOISE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CClient_Precipitation::CreateParticlePrecip( void )
|
|
{
|
|
if ( !r_RainAllowInSplitScreen.GetBool() && engine->IsSplitScreenActive() )
|
|
{
|
|
FOR_EACH_VALID_SPLITSCREEN_PLAYER( hh )
|
|
{
|
|
if ( m_pParticlePrecipOuter[hh] != NULL )
|
|
{
|
|
DestroyInnerParticlePrecip( hh );
|
|
DestroyOuterParticlePrecip( hh );
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( !m_bParticlePrecipInitialized )
|
|
{
|
|
InitializeParticlePrecip();
|
|
}
|
|
|
|
|
|
FOR_EACH_VALID_SPLITSCREEN_PLAYER( hh )
|
|
{
|
|
ACTIVE_SPLITSCREEN_PLAYER_GUARD( hh );
|
|
|
|
#ifdef CSTRIKE15
|
|
C_CSPlayer *pPlayer = GetLocalOrInEyeCSPlayer();
|
|
#else
|
|
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
|
|
#endif
|
|
|
|
if ( pPlayer == NULL )
|
|
continue;
|
|
|
|
// Make sure the emitter is setup
|
|
if ( !m_bActiveParticlePrecipEmitter[hh] )
|
|
{
|
|
//Update 8 times per second.
|
|
m_tParticlePrecipTraceTimer[hh].Init( 8 );
|
|
DestroyInnerParticlePrecip( hh );
|
|
DestroyOuterParticlePrecip( hh );
|
|
m_bActiveParticlePrecipEmitter[hh] = true;
|
|
}
|
|
|
|
UpdateParticlePrecip( pPlayer, hh );
|
|
}
|
|
}
|
|
|
|
void CClient_Precipitation::UpdateParticlePrecip( C_BasePlayer *pPlayer, int nSlot )
|
|
{
|
|
if ( !pPlayer )
|
|
return;
|
|
|
|
Vector vForward;
|
|
Vector vRight;
|
|
|
|
#ifdef INFESTED_DLL
|
|
vForward[PITCH] = 0;
|
|
vForward[YAW] = ASWInput()->ASW_GetCameraPitch();
|
|
vForward[ROLL] = -ASWInput()->ASW_GetCameraYaw();
|
|
vForward.NormalizeInPlace();
|
|
#else
|
|
pPlayer->GetVectors( &vForward, &vRight, NULL );
|
|
vForward.z = 0.0f;
|
|
vForward.NormalizeInPlace();
|
|
Vector vForward45Right = vForward + vRight;
|
|
Vector vForward45Left = vForward - vRight;
|
|
vForward45Right.NormalizeInPlace();
|
|
vForward45Left.NormalizeInPlace();
|
|
fltx4 TMax = ReplicateX4( 320.0f );
|
|
SubFloat( TMax, 3 ) = FLT_MAX;
|
|
#endif
|
|
float curTime = gpGlobals->frametime;
|
|
|
|
while ( m_tParticlePrecipTraceTimer[nSlot].NextEvent( curTime ) )
|
|
{
|
|
#ifdef INFESTED_DLL
|
|
Vector vPlayerPos = MainViewOrigin( nSlot );
|
|
Vector vOffsetPos = vPlayerPos + Vector ( 0, 0, 4 );
|
|
Vector vOffsetPosNear = vPlayerPos + Vector ( 0, 0, 4 ) + ( vForward * 32 );
|
|
Vector vOffsetPosFar = vPlayerPos + Vector ( 0, 0, 4 ) + ( vForward * 100 );
|
|
Vector vDensity = Vector( r_RainParticleDensity.GetFloat(), (float)m_nSnowDustAmount/100.0f, 0 ) * m_flDensity;
|
|
|
|
RayTracingEnvironment *RtEnv = g_RayTraceEnvironments.Element( 0 );
|
|
|
|
bool bInside = !engine->CullBox( RtEnv->m_MinBound, RtEnv->m_MaxBound );
|
|
bool bNearby = false;
|
|
#else
|
|
Vector vPlayerPos = pPlayer->EyePosition();
|
|
Vector vOffsetPos = vPlayerPos + Vector ( 0, 0, 180 );
|
|
Vector vOffsetPosNear = vPlayerPos + Vector ( 0, 0, 180 ) + ( vForward * 32 );
|
|
Vector vOffsetPosFar = vPlayerPos + Vector ( 0, 0, 180 ) + ( vForward * 100 );
|
|
|
|
Vector vDensity = Vector( r_RainParticleDensity.GetFloat(), 0, 0 ) * m_flDensity;
|
|
|
|
// Get the rain volume Ray Tracing Environment. Currently hard coded to 0, should have this lookup
|
|
RayTracingEnvironment *RtEnv = g_RayTraceEnvironments.Element( 0 );
|
|
|
|
// Our 4 Rays are forward, off to the left and right, and directly up.
|
|
// Use the first three to determine if there's generally visible rain where we're looking.
|
|
// The forth, straight up, tells us if we're standing inside a rain volume
|
|
// (based on the normal that we hit or if we miss entirely)
|
|
FourRays frRays;
|
|
FourVectors fvDirection;
|
|
fvDirection = FourVectors( vForward, vForward45Left, vForward45Right, Vector( 0, 0, 1 ) );
|
|
frRays.direction = fvDirection;
|
|
frRays.origin.DuplicateVector( vPlayerPos );
|
|
RayTracingResult Result;
|
|
|
|
RtEnv->Trace4Rays( frRays, Four_Zeros, TMax, &Result );
|
|
|
|
i32x4 in4HitIds = LoadAlignedIntSIMD( Result.HitIds );
|
|
fltx4 fl4HitIds = SignedIntConvertToFltSIMD ( in4HitIds );
|
|
|
|
fltx4 fl4Tolerance = ReplicateX4( 300.0f );
|
|
// ignore upwards test for tolerance, as we may be below an area which is raining, but with it not visible in front of us
|
|
//SubFloat( fl4Tolerance, 3 ) = 0.0f;
|
|
|
|
bool bInside = ( Result.HitIds[3] != -1 && Result.surface_normal.Vec( 3 ).z < 0.0f );
|
|
bool bNearby = ( IsAnyTrue( CmpGeSIMD ( fl4HitIds, Four_Zeros ) ) && IsAnyTrue( CmpGeSIMD( fl4Tolerance, Result.HitDistance ) ) );
|
|
#endif
|
|
|
|
if ( bInside || bNearby )
|
|
{
|
|
#ifdef INFESTED_DLL
|
|
//debugoverlay->AddBoxOverlay(vPlayerPos, Vector( -12, -12, -12 ), Vector( 12, 12, 12 ), QAngle( 0, 0, 0 ), 255, 0, 0, 32, 0.2f );
|
|
//debugoverlay->AddBoxOverlay(vOffsetPosNear, Vector( -10, -10, -10 ), Vector( 10, 10, 10 ), QAngle( 0, 0, 0 ), 0, 255, 0, 32, 0.2f );
|
|
//debugoverlay->AddBoxOverlay(vOffsetPosFar, Vector( -5, -5, -5 ), Vector( 5, 5, 5 ), QAngle( 0, 0, 0 ), 0, 0, 255, 32, 0.2f );
|
|
|
|
// Update if we've already got systems, otherwise, create them.
|
|
if ( m_pParticlePrecipInnerNear[nSlot] != NULL && m_pParticlePrecipInnerFar[nSlot] != NULL && m_pParticlePrecipOuter[nSlot] != NULL )
|
|
{
|
|
m_pParticlePrecipOuter[nSlot]->SetControlPoint( 1, vOffsetPos );
|
|
m_pParticlePrecipInnerNear[nSlot]->SetControlPoint( 1, vOffsetPosNear );
|
|
m_pParticlePrecipInnerFar[nSlot]->SetControlPoint( 1, vOffsetPosFar );
|
|
m_pParticlePrecipInnerNear[nSlot]->SetControlPoint( 3, vDensity );
|
|
m_pParticlePrecipInnerFar[nSlot]->SetControlPoint( 3, vDensity );
|
|
m_pParticlePrecipOuter[nSlot]->SetControlPoint( 3, vDensity );
|
|
}
|
|
else
|
|
{
|
|
DispatchInnerParticlePrecip( nSlot, pPlayer, vForward );
|
|
}
|
|
#else
|
|
//We can see a rain volume, but it's farther than 180 units away, only use far effect.
|
|
if ( !bInside && SubFloat( FindLowestSIMD3( Result.HitDistance ), 0 ) >= m_flParticleInnerDist )
|
|
{
|
|
// Kill the inner rain if it's previously been in use
|
|
if ( m_pParticlePrecipInnerNear[nSlot] != NULL )
|
|
{
|
|
DestroyInnerParticlePrecip( nSlot );
|
|
}
|
|
// Update if we've already got systems, otherwise, create them.
|
|
if ( m_pParticlePrecipOuter[nSlot] != NULL )
|
|
{
|
|
m_pParticlePrecipOuter[nSlot]->SetControlPoint( 1, vOffsetPos );
|
|
m_pParticlePrecipOuter[nSlot]->SetControlPoint( 3, vDensity );
|
|
}
|
|
else
|
|
{
|
|
DispatchOuterParticlePrecip( nSlot, pPlayer, vForward );
|
|
}
|
|
}
|
|
else //We're close enough to use the near effect.
|
|
{
|
|
// Update if we've already got systems, otherwise, create them.
|
|
if ( m_pParticlePrecipInnerNear[nSlot] != NULL && m_pParticlePrecipInnerFar[nSlot] != NULL && m_pParticlePrecipOuter[nSlot] != NULL )
|
|
{
|
|
m_pParticlePrecipOuter[nSlot]->SetControlPoint( 1, vOffsetPos );
|
|
m_pParticlePrecipInnerNear[nSlot]->SetControlPoint( 1, vOffsetPosNear );
|
|
m_pParticlePrecipInnerFar[nSlot]->SetControlPoint( 1, vOffsetPosFar );
|
|
m_pParticlePrecipInnerNear[nSlot]->SetControlPoint( 3, vDensity );
|
|
m_pParticlePrecipInnerFar[nSlot]->SetControlPoint( 3, vDensity );
|
|
m_pParticlePrecipOuter[nSlot]->SetControlPoint( 3, vDensity );
|
|
}
|
|
else
|
|
{
|
|
DispatchInnerParticlePrecip( nSlot, pPlayer, vForward );
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
else // No rain in the area, kill any leftover systems.
|
|
{
|
|
DestroyInnerParticlePrecip( nSlot );
|
|
DestroyOuterParticlePrecip( nSlot );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CClient_Precipitation::InitializeParticlePrecip( void )
|
|
{
|
|
//Set up which type of precipitation particle we'll use
|
|
if ( m_nPrecipType == PRECIPITATION_TYPE_PARTICLEASH )
|
|
{
|
|
m_pParticleInnerNearDef = "ash";
|
|
m_pParticleInnerFarDef = "ash";
|
|
m_pParticleOuterDef = "ash_outer";
|
|
m_flParticleInnerDist = 280.0;
|
|
}
|
|
else if ( m_nPrecipType == PRECIPITATION_TYPE_PARTICLESNOW )
|
|
{
|
|
#ifdef INFESTED_DLL
|
|
m_pParticleInnerNearDef = "asw_snow";
|
|
m_pParticleInnerFarDef = "asw_snow";
|
|
m_pParticleOuterDef = "asw_snow_outer";
|
|
m_flParticleInnerDist = 240.0;
|
|
#else
|
|
m_pParticleInnerNearDef = "snow";
|
|
m_pParticleInnerFarDef = "snow";
|
|
m_pParticleOuterDef = "snow_outer";
|
|
m_flParticleInnerDist = 280.0;
|
|
#endif
|
|
}
|
|
else if ( m_nPrecipType == PRECIPITATION_TYPE_PARTICLERAINSTORM )
|
|
{
|
|
m_pParticleInnerNearDef = "rain_storm";
|
|
m_pParticleInnerFarDef = "rain_storm_screen";
|
|
m_pParticleOuterDef = "rain_storm_outer";
|
|
m_flParticleInnerDist = 0.0;
|
|
}
|
|
else //default to rain
|
|
{
|
|
m_pParticleInnerNearDef = "rain";
|
|
m_pParticleInnerFarDef = "rain";
|
|
m_pParticleOuterDef = "rain_outer";
|
|
m_flParticleInnerDist = 180.0;
|
|
}
|
|
|
|
Assert( m_pParticleInnerFarDef != NULL );
|
|
|
|
//We'll want to change this if/when we add more raytrace environments.
|
|
g_RayTraceEnvironments.PurgeAndDeleteElements();
|
|
|
|
// Sets up ray tracing environments for all func_precipitations and func_precipitation_blockers
|
|
RayTracingEnvironment *rtEnvRainEmission = new RayTracingEnvironment();
|
|
g_RayTraceEnvironments.AddToTail( rtEnvRainEmission );
|
|
RayTracingEnvironment *rtEnvRainBlocker = new RayTracingEnvironment();
|
|
g_RayTraceEnvironments.AddToTail( rtEnvRainBlocker );
|
|
|
|
rtEnvRainEmission->Flags |= RTE_FLAGS_DONT_STORE_TRIANGLE_COLORS; // save some ram
|
|
rtEnvRainBlocker->Flags |= RTE_FLAGS_DONT_STORE_TRIANGLE_COLORS; // save some ram
|
|
|
|
int nTriCount = 1;
|
|
for ( int i=0; i<g_Precipitations.Count(); ++i )
|
|
{
|
|
CClient_Precipitation *volume = g_Precipitations[i];
|
|
|
|
vcollide_t *pCollide = modelinfo->GetVCollide( volume->GetModelIndex() );
|
|
|
|
if ( !pCollide || pCollide->solidCount <= 0 )
|
|
continue;
|
|
|
|
Vector *outVerts;
|
|
int vertCount = g_pPhysicsCollision->CreateDebugMesh( pCollide->solids[0], &outVerts );
|
|
|
|
if ( vertCount )
|
|
{
|
|
for ( int j = 0; j < vertCount; j += 3 )
|
|
{
|
|
rtEnvRainEmission->AddTriangle( nTriCount++, outVerts[j], outVerts[j + 1], outVerts[j + 2], Vector( 1, 1, 1 ) );
|
|
}
|
|
}
|
|
physcollision->DestroyDebugMesh( vertCount, outVerts );
|
|
}
|
|
rtEnvRainEmission->SetupAccelerationStructure();
|
|
|
|
nTriCount = 1;
|
|
|
|
for ( int i=0; i<g_PrecipitationBlockers.Count(); ++i )
|
|
{
|
|
C_PrecipitationBlocker *blocker = g_PrecipitationBlockers[i];
|
|
|
|
vcollide_t *pCollide = modelinfo->GetVCollide( blocker->GetModelIndex() );
|
|
|
|
if ( !pCollide || pCollide->solidCount <= 0 )
|
|
continue;
|
|
|
|
Vector *outVerts;
|
|
int vertCount = g_pPhysicsCollision->CreateDebugMesh( pCollide->solids[0], &outVerts );
|
|
|
|
if ( vertCount )
|
|
{
|
|
for ( int j = 0; j < vertCount; j += 3 )
|
|
{
|
|
rtEnvRainBlocker->AddTriangle( nTriCount++, outVerts[j], outVerts[j + 1], outVerts[j + 2], Vector( 1, 1, 1 ) );
|
|
}
|
|
}
|
|
physcollision->DestroyDebugMesh( vertCount, outVerts );
|
|
}
|
|
|
|
rtEnvRainBlocker->SetupAccelerationStructure();
|
|
|
|
m_bParticlePrecipInitialized = true;
|
|
}
|
|
|
|
void CClient_Precipitation::DestroyInnerParticlePrecip( int nSlot )
|
|
{
|
|
if ( m_pParticlePrecipInnerFar[nSlot] != NULL )
|
|
{
|
|
m_pParticlePrecipInnerFar[nSlot]->StopEmission();
|
|
m_pParticlePrecipInnerFar[nSlot] = NULL;
|
|
}
|
|
if ( m_pParticlePrecipInnerNear[nSlot] != NULL )
|
|
{
|
|
m_pParticlePrecipInnerNear[nSlot]->StopEmission();
|
|
m_pParticlePrecipInnerNear[nSlot] = NULL;
|
|
}
|
|
}
|
|
|
|
void CClient_Precipitation::DestroyOuterParticlePrecip( int nSlot )
|
|
{
|
|
if ( m_pParticlePrecipOuter[nSlot] != NULL )
|
|
{
|
|
m_pParticlePrecipOuter[nSlot]->StopEmission();
|
|
m_pParticlePrecipOuter[nSlot] = NULL;
|
|
}
|
|
}
|
|
|
|
void CClient_Precipitation::DispatchOuterParticlePrecip( int nSlot, C_BasePlayer *pPlayer, Vector vForward )
|
|
{
|
|
DestroyOuterParticlePrecip( nSlot );
|
|
|
|
#ifdef INFESTED_DLL
|
|
Vector vDensity = Vector( r_RainParticleDensity.GetFloat(), (float)m_nSnowDustAmount/100.0f, 0 ) * m_flDensity;
|
|
Vector vPlayerPos = MainViewOrigin( nSlot );
|
|
#else
|
|
Vector vDensity = Vector( r_RainParticleDensity.GetFloat(), 0, 0 ) * m_flDensity;
|
|
Vector vPlayerPos = pPlayer->EyePosition();
|
|
#endif
|
|
|
|
m_pParticlePrecipOuter[nSlot] = ParticleProp()->Create( m_pParticleOuterDef, PATTACH_ABSORIGIN_FOLLOW );
|
|
m_pParticlePrecipOuter[nSlot]->SetControlPointEntity( 2, pPlayer );
|
|
m_pParticlePrecipOuter[nSlot]->SetControlPoint( 1, vPlayerPos + Vector (0, 0, 180 ) );
|
|
m_pParticlePrecipOuter[nSlot]->SetControlPoint( 3, vDensity );
|
|
m_pParticlePrecipOuter[nSlot]->SetDrawOnlyForSplitScreenUser( nSlot );
|
|
}
|
|
|
|
void CClient_Precipitation::DispatchInnerParticlePrecip( int nSlot, C_BasePlayer *pPlayer, Vector vForward )
|
|
{
|
|
DestroyInnerParticlePrecip( nSlot );
|
|
DestroyOuterParticlePrecip( nSlot );
|
|
#ifdef INFESTED_DLL
|
|
Vector vPlayerPos = MainViewOrigin( nSlot );
|
|
Vector vOffsetPos = vPlayerPos + Vector ( 0, 0, 64 );
|
|
Vector vOffsetPosNear = vPlayerPos + Vector ( 0, 0, 64 ) + ( vForward * 32 );
|
|
Vector vOffsetPosFar = vPlayerPos + Vector ( 0, 0, 64 ) + ( vForward * m_flParticleInnerDist );
|
|
Vector vDensity = Vector( r_RainParticleDensity.GetFloat(), (float)m_nSnowDustAmount/100.0f, 0 ) * m_flDensity;
|
|
#else
|
|
Vector vPlayerPos = pPlayer->EyePosition();
|
|
Vector vOffsetPos = vPlayerPos + Vector ( 0, 0, 180 );
|
|
Vector vOffsetPosNear = vPlayerPos + Vector ( 0, 0, 180 ) + ( vForward * 32 );
|
|
Vector vOffsetPosFar = vPlayerPos + Vector ( 0, 0, 180 ) + ( vForward * m_flParticleInnerDist ); // 100.0
|
|
Vector vDensity = Vector( r_RainParticleDensity.GetFloat(), 0, 0 ) * m_flDensity;
|
|
#endif
|
|
|
|
m_pParticlePrecipOuter[nSlot] = ParticleProp()->Create( m_pParticleOuterDef, PATTACH_ABSORIGIN_FOLLOW );
|
|
m_pParticlePrecipInnerNear[nSlot] = ParticleProp()->Create( m_pParticleInnerNearDef, PATTACH_ABSORIGIN_FOLLOW );
|
|
m_pParticlePrecipInnerFar[nSlot] = ParticleProp()->Create( m_pParticleInnerFarDef, PATTACH_ABSORIGIN_FOLLOW );
|
|
m_pParticlePrecipOuter[nSlot]->SetControlPointEntity( 2, pPlayer );
|
|
m_pParticlePrecipInnerNear[nSlot]->SetControlPointEntity( 2, pPlayer );
|
|
m_pParticlePrecipInnerFar[nSlot]->SetControlPointEntity( 2, pPlayer );
|
|
m_pParticlePrecipOuter[nSlot]->SetControlPoint( 1, vOffsetPos );
|
|
m_pParticlePrecipInnerNear[nSlot]->SetControlPoint( 1, vOffsetPosNear );
|
|
m_pParticlePrecipInnerFar[nSlot]->SetControlPoint( 1, vOffsetPosFar );
|
|
m_pParticlePrecipInnerNear[nSlot]->SetControlPoint( 3, vDensity );
|
|
m_pParticlePrecipInnerFar[nSlot]->SetControlPoint( 3, vDensity );
|
|
m_pParticlePrecipOuter[nSlot]->SetControlPoint( 3, vDensity );
|
|
m_pParticlePrecipOuter[nSlot]->SetDrawOnlyForSplitScreenUser( nSlot );
|
|
m_pParticlePrecipInnerNear[nSlot]->SetDrawOnlyForSplitScreenUser( nSlot );
|
|
m_pParticlePrecipInnerFar[nSlot]->SetDrawOnlyForSplitScreenUser( nSlot );
|
|
}
|
|
|
|
|
|
// TERROR: adding end pos for lifetime calcs
|
|
void CClient_Precipitation::CreateRainOrSnowParticle( const Vector &vSpawnPosition, const Vector &vEndPosition, const Vector &vVelocity )
|
|
{
|
|
// Create the particle
|
|
CPrecipitationParticle* p = CreateParticle();
|
|
if (!p)
|
|
return;
|
|
|
|
VectorCopy( vVelocity, p->m_Velocity );
|
|
p->m_Pos = vSpawnPosition;
|
|
|
|
/* TERROR: moving random velocity out so it can be included in endpos calcs
|
|
p->m_Velocity[ 0 ] += random->RandomFloat(-r_RainSideVel.GetInt(), r_RainSideVel.GetInt());
|
|
p->m_Velocity[ 1 ] += random->RandomFloat(-r_RainSideVel.GetInt(), r_RainSideVel.GetInt());
|
|
*/
|
|
|
|
p->m_Mass = random->RandomFloat( 0.5, 1.5 );
|
|
|
|
p->m_flMaxLifetime = fabs((vSpawnPosition.z - vEndPosition.z) / vVelocity.z);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// emit the precipitation particles
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CClient_Precipitation::EmitParticles( float fTimeDelta )
|
|
{
|
|
Vector2D size;
|
|
Vector vel, org;
|
|
|
|
FOR_EACH_VALID_SPLITSCREEN_PLAYER( hh )
|
|
{
|
|
ACTIVE_SPLITSCREEN_PLAYER_GUARD( hh );
|
|
|
|
#ifdef CSTRIKE15
|
|
C_CSPlayer *pPlayer = GetLocalOrInEyeCSPlayer();
|
|
#else
|
|
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
|
|
#endif
|
|
if ( !pPlayer )
|
|
continue;
|
|
|
|
Vector vPlayerCenter = pPlayer->WorldSpaceCenter();
|
|
|
|
// Compute where to emit
|
|
if (!ComputeEmissionArea( org, size, pPlayer ))
|
|
continue;
|
|
|
|
// clamp this to prevent creating a bunch of rain or snow at one time.
|
|
if( fTimeDelta > 0.075f )
|
|
fTimeDelta = 0.075f;
|
|
|
|
// FIXME: Compute the precipitation density based on computational power
|
|
float density = m_flDensity * 0.001;
|
|
|
|
if (density > 0.01f)
|
|
density = 0.01f;
|
|
|
|
// Compute number of particles to emit based on precip density and emission area and dt
|
|
float fParticles = size[0] * size[1] * density * fTimeDelta + m_Remainder;
|
|
int cParticles = (int)fParticles;
|
|
m_Remainder = fParticles - cParticles;
|
|
|
|
// calculate the max amount of time it will take this flake to fall.
|
|
// This works if we assume the wind doesn't have a z component
|
|
VectorCopy( s_WindVector, vel );
|
|
vel[2] -= GetSpeed();
|
|
|
|
// Emit all the particles
|
|
for ( int i = 0 ; i < cParticles ; i++ )
|
|
{
|
|
// TERROR: moving random velocity out so it can be included in endpos calcs
|
|
Vector vParticleVel = vel;
|
|
vParticleVel[ 0 ] += random->RandomFloat(-r_RainSideVel.GetInt(), r_RainSideVel.GetInt());
|
|
vParticleVel[ 1 ] += random->RandomFloat(-r_RainSideVel.GetInt(), r_RainSideVel.GetInt());
|
|
|
|
Vector vParticlePos = org;
|
|
vParticlePos[ 0 ] += size[ 0 ] * random->RandomFloat(0, 1);
|
|
vParticlePos[ 1 ] += size[ 1 ] * random->RandomFloat(0, 1);
|
|
|
|
// Figure out where the particle should lie in Z by tracing a line from the player's height up to the
|
|
// desired height and making sure it doesn't hit a wall.
|
|
Vector vPlayerHeight = vParticlePos;
|
|
vPlayerHeight.z = vPlayerCenter.z;
|
|
|
|
if ( ParticleIsBlocked( vPlayerHeight, vParticlePos ) )
|
|
{
|
|
if ( r_RainDebugDuration.GetBool() )
|
|
{
|
|
debugoverlay->AddLineOverlay( vPlayerHeight, vParticlePos, 255, 0, 0, false, r_RainDebugDuration.GetFloat() );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
Vector vUnitParticleVel = vParticleVel;
|
|
float fallHeight = vParticlePos.z - vPlayerHeight.z;
|
|
vUnitParticleVel /= fallHeight;
|
|
vPlayerHeight.x += vUnitParticleVel.x * fallHeight;
|
|
vPlayerHeight.y += vUnitParticleVel.y * fallHeight;
|
|
|
|
trace_t trace;
|
|
UTIL_TraceLine( vPlayerHeight, vParticlePos, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace );
|
|
if ( trace.fraction < 1 )
|
|
{
|
|
// If we hit a brush, then don't spawn the particle.
|
|
if ( trace.surface.flags & SURF_SKY )
|
|
{
|
|
vParticlePos = trace.endpos;
|
|
if ( r_RainDebugDuration.GetBool() )
|
|
{
|
|
debugoverlay->AddLineOverlay( vPlayerHeight, trace.endpos, 0, 0, 255, false, r_RainDebugDuration.GetFloat() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( r_RainDebugDuration.GetBool() )
|
|
{
|
|
debugoverlay->AddLineOverlay( vPlayerHeight, trace.endpos, 255, 0, 0, false, r_RainDebugDuration.GetFloat() );
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// TERROR: Find an endpos
|
|
Vector vParticleEndPos( vPlayerHeight );
|
|
//vParticleEndPos.z -= 256.0f;
|
|
//UTIL_TraceLine( vPlayerHeight, vParticleEndPos, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace );
|
|
//vParticleEndPos = trace.endpos;
|
|
|
|
if ( r_RainDebugDuration.GetBool() )
|
|
{
|
|
debugoverlay->AddLineOverlay( vParticlePos, vParticleEndPos, 0, 255, 0, true, r_RainDebugDuration.GetFloat() );
|
|
}
|
|
|
|
CreateRainOrSnowParticle( vParticlePos, vParticleEndPos, vParticleVel );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Computes the wind vector
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CClient_Precipitation::ComputeWindVector( )
|
|
{
|
|
// Compute the wind direction
|
|
QAngle windangle( 0, cl_winddir.GetFloat(), 0 ); // used to turn wind yaw direction into a vector
|
|
|
|
// Randomize the wind angle and speed slightly to get us a little variation
|
|
windangle[1] = windangle[1] + random->RandomFloat( -10, 10 );
|
|
float windspeed = cl_windspeed.GetFloat() * (1.0 + random->RandomFloat( -0.2, 0.2 ));
|
|
|
|
AngleVectors( windangle, &s_WindVector );
|
|
VectorScale( s_WindVector, windspeed, s_WindVector );
|
|
}
|
|
|
|
CHandle<CClient_Precipitation> g_pPrecipHackEnt;
|
|
|
|
class CPrecipHack : public CAutoGameSystemPerFrame
|
|
{
|
|
public:
|
|
explicit CPrecipHack( char const *name ) : CAutoGameSystemPerFrame( name )
|
|
{
|
|
m_bLevelInitted = false;
|
|
}
|
|
|
|
virtual void LevelInitPostEntity()
|
|
{
|
|
if ( r_RainHack.GetInt() )
|
|
{
|
|
CClient_Precipitation *pPrecipHackEnt = new CClient_Precipitation;
|
|
pPrecipHackEnt->InitializeAsClientEntity( NULL, false );
|
|
g_pPrecipHackEnt = pPrecipHackEnt;
|
|
}
|
|
m_bLevelInitted = true;
|
|
}
|
|
|
|
virtual void LevelShutdownPreEntity()
|
|
{
|
|
if ( r_RainHack.GetInt() && g_pPrecipHackEnt )
|
|
{
|
|
UTIL_Remove( g_pPrecipHackEnt );
|
|
}
|
|
m_bLevelInitted = false;
|
|
}
|
|
|
|
virtual void Update( float frametime )
|
|
{
|
|
// Handle changes to the cvar at runtime.
|
|
if ( m_bLevelInitted )
|
|
{
|
|
if ( r_RainHack.GetInt() && !g_pPrecipHackEnt )
|
|
LevelInitPostEntity();
|
|
else if ( !r_RainHack.GetInt() && g_pPrecipHackEnt )
|
|
LevelShutdownPreEntity();
|
|
}
|
|
}
|
|
|
|
bool m_bLevelInitted;
|
|
};
|
|
CPrecipHack g_PrecipHack( "CPrecipHack" );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// EnvWind - global wind info
|
|
//-----------------------------------------------------------------------------
|
|
class C_EnvWind : public C_BaseEntity
|
|
{
|
|
public:
|
|
C_EnvWind();
|
|
|
|
DECLARE_CLIENTCLASS();
|
|
DECLARE_CLASS( C_EnvWind, C_BaseEntity );
|
|
|
|
virtual void OnDataChanged( DataUpdateType_t updateType );
|
|
virtual bool ShouldDraw( void ) { return false; }
|
|
|
|
virtual void ClientThink( );
|
|
|
|
private:
|
|
C_EnvWind( const C_EnvWind & );
|
|
|
|
CEnvWindShared m_EnvWindShared;
|
|
};
|
|
|
|
// Receive datatables
|
|
BEGIN_RECV_TABLE_NOBASE(CEnvWindShared, DT_EnvWindShared)
|
|
RecvPropInt (RECVINFO(m_iMinWind)),
|
|
RecvPropInt (RECVINFO(m_iMaxWind)),
|
|
RecvPropInt (RECVINFO(m_iMinGust)),
|
|
RecvPropInt (RECVINFO(m_iMaxGust)),
|
|
RecvPropFloat (RECVINFO(m_flMinGustDelay)),
|
|
RecvPropFloat (RECVINFO(m_flMaxGustDelay)),
|
|
RecvPropInt (RECVINFO(m_iGustDirChange)),
|
|
RecvPropInt (RECVINFO(m_iWindSeed)),
|
|
RecvPropInt (RECVINFO(m_iInitialWindDir)),
|
|
RecvPropFloat (RECVINFO(m_flInitialWindSpeed)),
|
|
RecvPropFloat (RECVINFO(m_flStartTime)),
|
|
RecvPropFloat (RECVINFO(m_flGustDuration)),
|
|
// RecvPropInt (RECVINFO(m_iszGustSound)),
|
|
END_RECV_TABLE()
|
|
|
|
IMPLEMENT_CLIENTCLASS_DT( C_EnvWind, DT_EnvWind, CEnvWind )
|
|
RecvPropDataTable(RECVINFO_DT(m_EnvWindShared), 0, &REFERENCE_RECV_TABLE(DT_EnvWindShared)),
|
|
END_RECV_TABLE()
|
|
|
|
|
|
C_EnvWind::C_EnvWind()
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Post data update!
|
|
//-----------------------------------------------------------------------------
|
|
void C_EnvWind::OnDataChanged( DataUpdateType_t updateType )
|
|
{
|
|
// Whenever we get an update, reset the entire state.
|
|
// Note that the fields have already been stored by the datatables,
|
|
// but there's still work to be done in the init block
|
|
m_EnvWindShared.Init( entindex(), m_EnvWindShared.m_iWindSeed,
|
|
m_EnvWindShared.m_flStartTime, m_EnvWindShared.m_iInitialWindDir,
|
|
m_EnvWindShared.m_flInitialWindSpeed );
|
|
|
|
SetNextClientThink(0.0f);
|
|
|
|
BaseClass::OnDataChanged( updateType );
|
|
}
|
|
|
|
void C_EnvWind::ClientThink( )
|
|
{
|
|
// Update the wind speed
|
|
float flNextThink = m_EnvWindShared.WindThink( gpGlobals->curtime );
|
|
SetNextClientThink(flNextThink);
|
|
}
|
|
|
|
|
|
|
|
//==================================================
|
|
// EmberParticle
|
|
//==================================================
|
|
|
|
class CEmberEmitter : public CSimpleEmitter
|
|
{
|
|
public:
|
|
explicit CEmberEmitter( const char *pDebugName );
|
|
static CSmartPtr<CEmberEmitter> Create( const char *pDebugName );
|
|
virtual void UpdateVelocity( SimpleParticle *pParticle, float timeDelta );
|
|
virtual Vector UpdateColor( const SimpleParticle *pParticle );
|
|
|
|
private:
|
|
CEmberEmitter( const CEmberEmitter & );
|
|
};
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : fTimeDelta -
|
|
// Output : Vector
|
|
//-----------------------------------------------------------------------------
|
|
CEmberEmitter::CEmberEmitter( const char *pDebugName ) : CSimpleEmitter( pDebugName )
|
|
{
|
|
}
|
|
|
|
|
|
CSmartPtr<CEmberEmitter> CEmberEmitter::Create( const char *pDebugName )
|
|
{
|
|
return new CEmberEmitter( pDebugName );
|
|
}
|
|
|
|
|
|
void CEmberEmitter::UpdateVelocity( SimpleParticle *pParticle, float timeDelta )
|
|
{
|
|
float speed = VectorNormalize( pParticle->m_vecVelocity );
|
|
Vector offset;
|
|
|
|
speed -= ( 1.0f * timeDelta );
|
|
|
|
offset.Random( -0.025f, 0.025f );
|
|
offset[2] = 0.0f;
|
|
|
|
pParticle->m_vecVelocity += offset;
|
|
VectorNormalize( pParticle->m_vecVelocity );
|
|
|
|
pParticle->m_vecVelocity *= speed;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pParticle -
|
|
// timeDelta -
|
|
//-----------------------------------------------------------------------------
|
|
Vector CEmberEmitter::UpdateColor( const SimpleParticle *pParticle )
|
|
{
|
|
Vector color;
|
|
float ramp = 1.0f - ( pParticle->m_flLifetime / pParticle->m_flDieTime );
|
|
|
|
color[0] = ( (float) pParticle->m_uchColor[0] * ramp ) / 255.0f;
|
|
color[1] = ( (float) pParticle->m_uchColor[1] * ramp ) / 255.0f;
|
|
color[2] = ( (float) pParticle->m_uchColor[2] * ramp ) / 255.0f;
|
|
|
|
return color;
|
|
}
|
|
|
|
//==================================================
|
|
// C_Embers
|
|
//==================================================
|
|
|
|
class C_Embers : public C_BaseEntity
|
|
{
|
|
public:
|
|
DECLARE_CLIENTCLASS();
|
|
DECLARE_CLASS( C_Embers, C_BaseEntity );
|
|
|
|
C_Embers();
|
|
~C_Embers();
|
|
|
|
void Start( void );
|
|
|
|
virtual void OnDataChanged( DataUpdateType_t updateType );
|
|
virtual bool ShouldDraw( void );
|
|
virtual bool Simulate( void );
|
|
|
|
//Server-side
|
|
int m_nDensity;
|
|
int m_nLifetime;
|
|
int m_nSpeed;
|
|
bool m_bEmit;
|
|
|
|
protected:
|
|
|
|
void SpawnEmber( void );
|
|
|
|
PMaterialHandle m_hMaterial;
|
|
TimedEvent m_tParticleSpawn;
|
|
CSmartPtr<CEmberEmitter> m_pEmitter;
|
|
|
|
};
|
|
|
|
//Receive datatable
|
|
IMPLEMENT_CLIENTCLASS_DT( C_Embers, DT_Embers, CEmbers )
|
|
RecvPropInt( RECVINFO( m_nDensity ) ),
|
|
RecvPropInt( RECVINFO( m_nLifetime ) ),
|
|
RecvPropInt( RECVINFO( m_nSpeed ) ),
|
|
RecvPropInt( RECVINFO( m_bEmit ) ),
|
|
END_RECV_TABLE()
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : bnewentity -
|
|
//-----------------------------------------------------------------------------
|
|
C_Embers::C_Embers()
|
|
{
|
|
m_pEmitter = CEmberEmitter::Create( "C_Embers" );
|
|
AddToEntityList(ENTITY_LIST_SIMULATE);
|
|
}
|
|
|
|
C_Embers::~C_Embers()
|
|
{
|
|
}
|
|
|
|
void C_Embers::OnDataChanged( DataUpdateType_t updateType )
|
|
{
|
|
BaseClass::OnDataChanged( updateType );
|
|
|
|
if ( updateType == DATA_UPDATE_CREATED )
|
|
{
|
|
m_pEmitter->SetSortOrigin( GetAbsOrigin() );
|
|
|
|
Start();
|
|
}
|
|
if ( m_bEmit )
|
|
{
|
|
AddToEntityList(ENTITY_LIST_SIMULATE);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool C_Embers::ShouldDraw()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void C_Embers::Start( void )
|
|
{
|
|
//Various setup info
|
|
m_tParticleSpawn.Init( m_nDensity );
|
|
|
|
m_hMaterial = m_pEmitter->GetPMaterial( "particle/fire" );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool C_Embers::Simulate( void )
|
|
{
|
|
if ( m_bEmit == false )
|
|
return false;
|
|
|
|
float tempDelta = gpGlobals->frametime;
|
|
|
|
while( m_tParticleSpawn.NextEvent( tempDelta ) )
|
|
{
|
|
SpawnEmber();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void C_Embers::SpawnEmber( void )
|
|
{
|
|
Vector offset, mins, maxs;
|
|
|
|
modelinfo->GetModelBounds( GetModel(), mins, maxs );
|
|
|
|
//Setup our spawn position
|
|
offset[0] = random->RandomFloat( mins[0], maxs[0] );
|
|
offset[1] = random->RandomFloat( mins[1], maxs[1] );
|
|
offset[2] = random->RandomFloat( mins[2], maxs[2] );
|
|
|
|
//Spawn the particle
|
|
SimpleParticle *sParticle = (SimpleParticle *) m_pEmitter->AddParticle( sizeof( SimpleParticle ), m_hMaterial, offset );
|
|
|
|
if (sParticle == NULL)
|
|
return;
|
|
|
|
float cScale = random->RandomFloat( 0.75f, 1.0f );
|
|
|
|
//Set it up
|
|
sParticle->m_flLifetime = 0.0f;
|
|
sParticle->m_flDieTime = m_nLifetime;
|
|
|
|
sParticle->m_uchColor[0] = GetRenderColorR() * cScale;
|
|
sParticle->m_uchColor[1] = GetRenderColorG() * cScale;
|
|
sParticle->m_uchColor[2] = GetRenderColorB() * cScale;
|
|
sParticle->m_uchStartAlpha = 255;
|
|
sParticle->m_uchEndAlpha = 0;
|
|
sParticle->m_uchStartSize = 1;
|
|
sParticle->m_uchEndSize = 0;
|
|
sParticle->m_flRollDelta = 0;
|
|
sParticle->m_flRoll = 0;
|
|
|
|
//Set the velocity
|
|
Vector velocity;
|
|
|
|
AngleVectors( GetAbsAngles(), &velocity );
|
|
|
|
sParticle->m_vecVelocity = velocity * m_nSpeed;
|
|
|
|
sParticle->m_vecVelocity[0] += random->RandomFloat( -(m_nSpeed/8), (m_nSpeed/8) );
|
|
sParticle->m_vecVelocity[1] += random->RandomFloat( -(m_nSpeed/8), (m_nSpeed/8) );
|
|
sParticle->m_vecVelocity[2] += random->RandomFloat( -(m_nSpeed/8), (m_nSpeed/8) );
|
|
|
|
UpdateVisibility();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Quadratic spline beam effect
|
|
//-----------------------------------------------------------------------------
|
|
#include "beamdraw.h"
|
|
|
|
class C_QuadraticBeam : public C_BaseEntity
|
|
{
|
|
public:
|
|
DECLARE_CLIENTCLASS();
|
|
DECLARE_CLASS( C_QuadraticBeam, C_BaseEntity );
|
|
|
|
//virtual void OnDataChanged( DataUpdateType_t updateType );
|
|
virtual bool ShouldDraw( void ) { return true; }
|
|
virtual int DrawModel( int, const RenderableInstance_t &instance );
|
|
|
|
virtual void GetRenderBounds( Vector& mins, Vector& maxs )
|
|
{
|
|
ClearBounds( mins, maxs );
|
|
AddPointToBounds( vec3_origin, mins, maxs );
|
|
AddPointToBounds( m_targetPosition, mins, maxs );
|
|
AddPointToBounds( m_controlPosition, mins, maxs );
|
|
mins -= GetRenderOrigin();
|
|
maxs -= GetRenderOrigin();
|
|
}
|
|
|
|
protected:
|
|
|
|
Vector m_targetPosition;
|
|
Vector m_controlPosition;
|
|
float m_scrollRate;
|
|
float m_flWidth;
|
|
};
|
|
|
|
//Receive datatable
|
|
IMPLEMENT_CLIENTCLASS_DT( C_QuadraticBeam, DT_QuadraticBeam, CEnvQuadraticBeam )
|
|
RecvPropVector( RECVINFO(m_targetPosition) ),
|
|
RecvPropVector( RECVINFO(m_controlPosition) ),
|
|
RecvPropFloat( RECVINFO(m_scrollRate) ),
|
|
RecvPropFloat( RECVINFO(m_flWidth) ),
|
|
END_RECV_TABLE()
|
|
|
|
Vector Color24ToVector( const color24 &color )
|
|
{
|
|
return Vector( color.r * (1.0/255.0f), color.g * (1.0/255.0f), color.b * (1.0/255.0f) );
|
|
}
|
|
|
|
int C_QuadraticBeam::DrawModel( int, const RenderableInstance_t &instance )
|
|
{
|
|
Draw_SetSpriteTexture( GetModel(), 0, GetRenderMode() );
|
|
Vector color = Color24ToVector( GetRenderColor() );
|
|
DrawBeamQuadratic( GetRenderOrigin(), m_controlPosition, m_targetPosition, m_flWidth, color, gpGlobals->curtime*m_scrollRate );
|
|
return 1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
class SnowFallEffect : public CSimpleEmitter
|
|
{
|
|
public:
|
|
|
|
explicit SnowFallEffect( const char *pDebugName ) : CSimpleEmitter( pDebugName ) {}
|
|
static SnowFallEffect* Create( const char *pDebugName )
|
|
{
|
|
return new SnowFallEffect( pDebugName );
|
|
}
|
|
|
|
void UpdateVelocity( SimpleParticle *pParticle, float timeDelta )
|
|
{
|
|
float flSpeed = VectorNormalize( pParticle->m_vecVelocity );
|
|
flSpeed -= timeDelta;
|
|
|
|
int randomVelocityValue = RandomInt( 0, 65536 );
|
|
float velX = ((128 - (randomVelocityValue / 256)) / 128.0f) * 0.025f;
|
|
float velY = ((128 - (randomVelocityValue % 256)) / 128.0f) * 0.025f;
|
|
VectorNormalize( pParticle->m_vecVelocity );
|
|
|
|
pParticle->m_vecVelocity.x += velX;
|
|
pParticle->m_vecVelocity.y += velY;
|
|
|
|
VectorNormalize( pParticle->m_vecVelocity );
|
|
|
|
pParticle->m_vecVelocity *= flSpeed;
|
|
|
|
Vector vecWindVelocity;
|
|
GetWindspeedAtTime( gpGlobals->curtime, vecWindVelocity );
|
|
pParticle->m_vecVelocity += ( vecWindVelocity * r_SnowWindScale.GetFloat() );
|
|
}
|
|
|
|
void 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;
|
|
|
|
//Should this particle die?
|
|
pParticle->m_flLifetime += timeDelta;
|
|
UpdateRoll( pParticle, timeDelta );
|
|
|
|
if ( pParticle->m_flLifetime >= pParticle->m_flDieTime )
|
|
{
|
|
pIterator->RemoveParticle( pParticle );
|
|
}
|
|
else if ( !IsInAir( pParticle->m_Pos ) )
|
|
{
|
|
pIterator->RemoveParticle( pParticle );
|
|
}
|
|
|
|
pParticle = (SimpleParticle*)pIterator->GetNext();
|
|
}
|
|
}
|
|
|
|
int GetParticleCount( void )
|
|
{
|
|
return GetBinding().GetNumActiveParticles();
|
|
}
|
|
|
|
void SetBounds( const Vector &vecMin, const Vector &vecMax )
|
|
{
|
|
GetBinding().SetBBox( vecMin, vecMax, true );
|
|
}
|
|
|
|
RenderableTranslucencyType_t ComputeTranslucencyType( void ) { return RENDERABLE_IS_OPAQUE; }
|
|
|
|
private:
|
|
SnowFallEffect( const SnowFallEffect & );
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
class CSnowFallManager : public C_BaseEntity
|
|
{
|
|
public:
|
|
|
|
CSnowFallManager();
|
|
~CSnowFallManager();
|
|
|
|
bool CreateEmitter( void );
|
|
|
|
void SpawnClientEntity( void );
|
|
void ClientThink();
|
|
|
|
void AddSnowFallEntity( CClient_Precipitation *pSnowEntity );
|
|
|
|
// Snow Effect
|
|
enum
|
|
{
|
|
SNOWFALL_NONE = 0,
|
|
SNOWFALL_AROUND_PLAYER,
|
|
SNOWFALL_IN_ENTITY,
|
|
};
|
|
|
|
RenderableTranslucencyType_t ComputeTranslucencyType( void ) { return RENDERABLE_IS_OPAQUE; }
|
|
void SetSplitScreenPlayerSlot( int nSlot );
|
|
|
|
private:
|
|
bool CreateSnowFallEmitter( void );
|
|
void CreateSnowFall( void );
|
|
void CreateSnowFallParticles( float flCurrentTime, float flRadius, const Vector &vecEyePos, const Vector &vecForward, float flZoomScale, C_BasePlayer *pLocalPlayer );
|
|
void CreateOutsideVolumeSnowParticles( float flCurrentTime, float flRadius, float flZoomScale, C_BasePlayer *pLocalPlayer );
|
|
void CreateInsideVolumeSnowParticles( float flCurrentTime, float flRadius, const Vector &vecEyePos, const Vector &vecForward, float flZoomScale, C_BasePlayer *pLocalPlayer );
|
|
void CreateSnowParticlesSphere( float flRadius, C_BasePlayer *pLocalPlayer );
|
|
void CreateSnowParticlesRay( float flRadius, const Vector &vecEyePos, const Vector &vecForward, C_BasePlayer *pLocalPlayer );
|
|
void CreateSnowFallParticle( const Vector &vecParticleSpawn, int iBBox, C_BasePlayer *pLocalPlayer );
|
|
|
|
int StandingInSnowVolume( Vector &vecPoint );
|
|
void FindSnowVolumes( Vector const &vecCenter, float flRadius, Vector const &vecEyePos, Vector const &vecForward );
|
|
|
|
void UpdateBounds( const Vector &vecSnowMin, const Vector &vecSnowMax );
|
|
|
|
private:
|
|
|
|
enum { MAX_SNOW_PARTICLES = 500 };
|
|
enum { MAX_SNOW_LIST = 32 };
|
|
|
|
TimedEvent m_tSnowFallParticleTimer;
|
|
TimedEvent m_tSnowFallParticleTraceTimer;
|
|
|
|
int m_iSnowFallArea;
|
|
CSmartPtr<SnowFallEffect> m_pSnowFallEmitter;
|
|
Vector m_vecSnowFallEmitOrigin;
|
|
float m_flSnowRadius;
|
|
|
|
Vector m_vecMin;
|
|
Vector m_vecMax;
|
|
|
|
int m_nActiveSnowCount;
|
|
int m_aActiveSnow[MAX_SNOW_LIST];
|
|
|
|
bool m_bRayParticles;
|
|
|
|
struct SnowFall_t
|
|
{
|
|
PMaterialHandle m_hMaterial;
|
|
CClient_Precipitation *m_pEntity;
|
|
CSmartPtr<SnowFallEffect> m_pEffect;
|
|
Vector m_vecMin;
|
|
Vector m_vecMax;
|
|
};
|
|
|
|
CUtlVector<SnowFall_t> m_aSnow;
|
|
int m_nSplitScreenPlayerSlot;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CSnowFallManager::CSnowFallManager( void )
|
|
{
|
|
m_iSnowFallArea = SNOWFALL_NONE;
|
|
m_pSnowFallEmitter = NULL;
|
|
m_vecSnowFallEmitOrigin.Init();
|
|
m_flSnowRadius = 0.0f;
|
|
m_vecMin.Init( FLT_MAX, FLT_MAX, FLT_MAX );
|
|
m_vecMax.Init( FLT_MIN, FLT_MIN, FLT_MIN );
|
|
m_nActiveSnowCount = 0;
|
|
m_aSnow.Purge();
|
|
m_nSplitScreenPlayerSlot = -1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CSnowFallManager::~CSnowFallManager( void )
|
|
{
|
|
m_aSnow.Purge();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CSnowFallManager::CreateEmitter( void )
|
|
{
|
|
return CreateSnowFallEmitter();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CSnowFallManager::SpawnClientEntity( void )
|
|
{
|
|
m_tSnowFallParticleTimer.Init( 500 );
|
|
m_tSnowFallParticleTraceTimer.Init( 6 );
|
|
m_iSnowFallArea = SNOWFALL_NONE;
|
|
|
|
// Have the Snow Fall Manager think for all the snow fall entities.
|
|
SetNextClientThink( CLIENT_THINK_ALWAYS );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CSnowFallManager::CreateSnowFallEmitter( void )
|
|
{
|
|
if ( ( m_pSnowFallEmitter = SnowFallEffect::Create( "snowfall" ) ) == NULL )
|
|
return false;
|
|
|
|
m_pSnowFallEmitter->SetShouldDrawForSplitScreenUser( m_nSplitScreenPlayerSlot );
|
|
return true;
|
|
}
|
|
|
|
void CSnowFallManager::SetSplitScreenPlayerSlot( int nSlot )
|
|
{
|
|
m_nSplitScreenPlayerSlot = nSlot;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CSnowFallManager::ClientThink( void )
|
|
{
|
|
if ( !IsValidSplitScreenSlot( m_nSplitScreenPlayerSlot ) )
|
|
return;
|
|
|
|
ACTIVE_SPLITSCREEN_PLAYER_GUARD( m_nSplitScreenPlayerSlot );
|
|
|
|
if ( !r_SnowEnable.GetBool() )
|
|
return;
|
|
|
|
// Make sure we have a snow fall emitter.
|
|
if ( !m_pSnowFallEmitter )
|
|
{
|
|
if ( !CreateSnowFallEmitter() )
|
|
return;
|
|
}
|
|
|
|
CreateSnowFall();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pSnowEntity -
|
|
//-----------------------------------------------------------------------------
|
|
void CSnowFallManager::AddSnowFallEntity( CClient_Precipitation *pSnowEntity )
|
|
{
|
|
if ( !pSnowEntity )
|
|
return;
|
|
|
|
int nSnowCount = m_aSnow.Count();
|
|
int iSnow = 0;
|
|
for ( iSnow = 0; iSnow < nSnowCount; ++iSnow )
|
|
{
|
|
if ( m_aSnow[iSnow].m_pEntity == pSnowEntity )
|
|
break;
|
|
}
|
|
|
|
if ( iSnow != nSnowCount )
|
|
return;
|
|
|
|
iSnow = m_aSnow.AddToTail();
|
|
m_aSnow[iSnow].m_pEntity = pSnowEntity;
|
|
m_aSnow[iSnow].m_pEffect = SnowFallEffect::Create( "snowfall" );
|
|
m_aSnow[iSnow].m_hMaterial = ParticleMgr()->GetPMaterial( "particle/snow" );
|
|
|
|
VectorCopy( pSnowEntity->WorldAlignMins(), m_aSnow[iSnow].m_vecMin );
|
|
VectorCopy( pSnowEntity->WorldAlignMaxs(), m_aSnow[iSnow].m_vecMax );
|
|
|
|
UpdateBounds( m_aSnow[iSnow].m_vecMin, m_aSnow[iSnow].m_vecMax );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CSnowFallManager::UpdateBounds( const Vector &vecSnowMin, const Vector &vecSnowMax )
|
|
{
|
|
int iAxis = 0;
|
|
for ( iAxis = 0; iAxis < 3; ++iAxis )
|
|
{
|
|
if ( vecSnowMin[iAxis] < m_vecMin[iAxis] )
|
|
{
|
|
m_vecMin[iAxis] = vecSnowMin[iAxis];
|
|
}
|
|
|
|
if ( vecSnowMax[iAxis] > m_vecMax[iAxis] )
|
|
{
|
|
m_vecMax[iAxis] = vecSnowMax[iAxis];
|
|
}
|
|
}
|
|
|
|
Assert( m_pSnowFallEmitter );
|
|
m_pSnowFallEmitter->SetBounds( m_vecMin, m_vecMax );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &vecPoint -
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int CSnowFallManager::StandingInSnowVolume( Vector &vecPoint )
|
|
{
|
|
trace_t traceSnow;
|
|
|
|
int nSnowCount = m_aSnow.Count();
|
|
int iSnow = 0;
|
|
for ( iSnow = 0; iSnow < nSnowCount; ++iSnow )
|
|
{
|
|
UTIL_TraceModel( vecPoint, vecPoint, vec3_origin, vec3_origin, static_cast<C_BaseEntity*>( m_aSnow[iSnow].m_pEntity ), COLLISION_GROUP_NONE, &traceSnow );
|
|
if ( traceSnow.startsolid )
|
|
return iSnow;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &vecCenter -
|
|
// flRadius -
|
|
//-----------------------------------------------------------------------------
|
|
void CSnowFallManager::FindSnowVolumes( Vector const &vecCenter, float flRadius, Vector const &vecEyePos, Vector const &vecForward )
|
|
{
|
|
// Reset.
|
|
m_nActiveSnowCount = 0;
|
|
m_bRayParticles = false;
|
|
|
|
int nSnowCount = m_aSnow.Count();
|
|
int iSnow = 0;
|
|
for ( iSnow = 0; iSnow < nSnowCount; ++iSnow )
|
|
{
|
|
// Check to see if the volume is in the PVS.
|
|
bool bInPVS = g_pClientLeafSystem->IsRenderableInPVS( m_aSnow[iSnow].m_pEntity->GetClientRenderable() );
|
|
if ( !bInPVS )
|
|
continue;
|
|
|
|
// Check to see if a snow volume is inside the given radius.
|
|
if ( IsBoxIntersectingSphere( m_aSnow[iSnow].m_vecMin, m_aSnow[iSnow].m_vecMax, vecCenter, flRadius ) )
|
|
{
|
|
m_aActiveSnow[m_nActiveSnowCount] = iSnow;
|
|
++m_nActiveSnowCount;
|
|
if ( m_nActiveSnowCount >= MAX_SNOW_LIST )
|
|
{
|
|
DevWarning( 1, "Max Active Snow Volume Count!\n" );
|
|
break;
|
|
}
|
|
}
|
|
// Check to see if a snow volume is outside of the sphere radius, but is along line-of-sight.
|
|
else
|
|
{
|
|
CBaseTrace trace;
|
|
Vector vecNewForward;
|
|
vecNewForward = vecForward * r_SnowRayLength.GetFloat();
|
|
vecNewForward.z = 0.0f;
|
|
IntersectRayWithBox( vecEyePos, vecNewForward, m_aSnow[iSnow].m_vecMin, m_aSnow[iSnow].m_vecMax, 0.325f, &trace );
|
|
if ( trace.fraction < 1.0f )
|
|
{
|
|
m_aActiveSnow[m_nActiveSnowCount] = iSnow;
|
|
++m_nActiveSnowCount;
|
|
if ( m_nActiveSnowCount >= MAX_SNOW_LIST )
|
|
{
|
|
DevWarning( 1, "Max Active Snow Volume Count!\n" );
|
|
break;
|
|
}
|
|
|
|
m_bRayParticles = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Debugging code!
|
|
#ifdef _DEBUG
|
|
if ( r_SnowDebugBox.GetFloat() != 0.0f )
|
|
{
|
|
for ( iSnow = 0; iSnow < m_nActiveSnowCount; ++iSnow )
|
|
{
|
|
Vector vecCenter, vecMin, vecMax;
|
|
vecCenter = ( m_aSnow[iSnow].m_vecMin, m_aSnow[iSnow].m_vecMax ) * 0.5;
|
|
vecMin = m_aSnow[iSnow].m_vecMin - vecCenter;
|
|
vecMax = m_aSnow[iSnow].m_vecMax - vecCenter;
|
|
debugoverlay->AddBoxOverlay( vecCenter, vecMin, vecMax, QAngle( 0, 0, 0 ), 200, 0, 0, 25, r_SnowDebugBox.GetFloat() );
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CSnowFallManager::CreateSnowFall( void )
|
|
{
|
|
#if 1
|
|
VPROF_BUDGET( "SnowFall", VPROF_BUDGETGROUP_PARTICLE_RENDERING );
|
|
#endif
|
|
|
|
ASSERT_LOCAL_PLAYER_RESOLVABLE();
|
|
// Check to see if we have a local player before starting the snow around a local player.
|
|
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
|
|
if ( pPlayer == NULL )
|
|
return;
|
|
|
|
// Get the current frame time.
|
|
float flCurrentTime = gpGlobals->frametime;
|
|
|
|
// Get the players data to determine where the snow emitter should reside.
|
|
VectorCopy( pPlayer->EyePosition(), m_vecSnowFallEmitOrigin );
|
|
Vector vecForward;
|
|
pPlayer->GetVectors( &vecForward, NULL, NULL );
|
|
vecForward.z = 0.0f;
|
|
Vector vecVelocity = pPlayer->GetAbsVelocity();
|
|
float flSpeed = VectorNormalize( vecVelocity );
|
|
m_vecSnowFallEmitOrigin += ( vecForward * ( 64.0f + ( flSpeed * 0.4f * r_SnowPosScale.GetFloat() ) ) );
|
|
m_vecSnowFallEmitOrigin += ( vecVelocity * ( flSpeed * 1.25f * r_SnowSpeedScale.GetFloat() ) );
|
|
|
|
// Check to see if the player is zoomed.
|
|
bool bZoomed = ( pPlayer->GetFOV() != pPlayer->GetDefaultFOV() );
|
|
float flZoomScale = 1.0f;
|
|
if ( bZoomed )
|
|
{
|
|
flZoomScale = pPlayer->GetDefaultFOV() / pPlayer->GetFOV();
|
|
flZoomScale *= 0.5f;
|
|
}
|
|
|
|
// Time to test for a snow volume yet? (Only do this 6 times a second!)
|
|
if ( m_tSnowFallParticleTraceTimer.NextEvent( flCurrentTime ) )
|
|
{
|
|
// Reset the active snow emitter.
|
|
m_iSnowFallArea = SNOWFALL_NONE;
|
|
|
|
// Set the trace start and the emit origin.
|
|
Vector vecTraceStart;
|
|
VectorCopy( pPlayer->EyePosition(), vecTraceStart );
|
|
|
|
int iSnowVolume = StandingInSnowVolume( vecTraceStart );
|
|
if ( iSnowVolume != -1 )
|
|
{
|
|
m_flSnowRadius = r_SnowInsideRadius.GetFloat() + ( flSpeed * 0.5f );
|
|
m_iSnowFallArea = SNOWFALL_AROUND_PLAYER;
|
|
}
|
|
else
|
|
{
|
|
m_flSnowRadius = r_SnowOutsideRadius.GetFloat();
|
|
}
|
|
|
|
float flRadius = m_flSnowRadius;
|
|
if ( bZoomed )
|
|
{
|
|
if ( m_iSnowFallArea == SNOWFALL_AROUND_PLAYER )
|
|
{
|
|
flRadius = r_SnowOutsideRadius.GetFloat() * flZoomScale;
|
|
}
|
|
else
|
|
{
|
|
flRadius *= flZoomScale;
|
|
}
|
|
}
|
|
|
|
FindSnowVolumes( m_vecSnowFallEmitOrigin, flRadius, pPlayer->EyePosition(), vecForward );
|
|
if ( m_nActiveSnowCount != 0 && m_iSnowFallArea != SNOWFALL_AROUND_PLAYER )
|
|
{
|
|
// We found an active snow emitter.
|
|
m_iSnowFallArea = SNOWFALL_IN_ENTITY;
|
|
|
|
}
|
|
}
|
|
|
|
if ( m_iSnowFallArea == SNOWFALL_NONE )
|
|
return;
|
|
|
|
// Set the origin in the snow emitter.
|
|
m_pSnowFallEmitter->SetSortOrigin( m_vecSnowFallEmitOrigin );
|
|
|
|
// Create snow fall particles.
|
|
CreateSnowFallParticles( flCurrentTime, m_flSnowRadius, pPlayer->EyePosition(), vecForward, flZoomScale, pPlayer );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : flCurrentTime -
|
|
// flRadius -
|
|
// &vecEyePos -
|
|
// &vecForward -
|
|
// flZoomScale -
|
|
//-----------------------------------------------------------------------------
|
|
void CSnowFallManager::CreateSnowFallParticles( float flCurrentTime, float flRadius, const Vector &vecEyePos, const Vector &vecForward, float flZoomScale, C_BasePlayer *pLocalPlayer )
|
|
{
|
|
const float SnowfallRate = 500.0f;
|
|
if ( m_nActiveSnowCount > 0 )
|
|
{
|
|
C_BaseEntity *pEntity = m_aSnow[ m_aActiveSnow[0] ].m_pEntity;
|
|
int density = pEntity->GetRenderAlpha();
|
|
density = clamp( density, 0, 100 );
|
|
if ( pEntity && density > 0 )
|
|
{
|
|
m_tSnowFallParticleTimer.ResetRate( SnowfallRate * density * 0.01f );
|
|
}
|
|
else
|
|
{
|
|
m_tSnowFallParticleTimer.ResetRate( SnowfallRate );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_tSnowFallParticleTimer.ResetRate( SnowfallRate );
|
|
}
|
|
|
|
// Outside of a snow volume.
|
|
if ( m_iSnowFallArea == SNOWFALL_IN_ENTITY )
|
|
{
|
|
CreateOutsideVolumeSnowParticles( flCurrentTime, flRadius, flZoomScale, pLocalPlayer );
|
|
}
|
|
// Inside of a snow volume.
|
|
else
|
|
{
|
|
CreateInsideVolumeSnowParticles( flCurrentTime, flRadius, vecEyePos, vecForward, flZoomScale, pLocalPlayer );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : flCurrentTime -
|
|
// flRadius -
|
|
// flZoomScale -
|
|
//-----------------------------------------------------------------------------
|
|
void CSnowFallManager::CreateOutsideVolumeSnowParticles( float flCurrentTime, float flRadius, float flZoomScale, C_BasePlayer *pLocalPlayer )
|
|
{
|
|
Vector vecParticleSpawn;
|
|
|
|
// Outside of a snow volume.
|
|
int iSnow = 0;
|
|
float flRadiusScaled = flRadius * flZoomScale;
|
|
float flRadius2 = flRadiusScaled * flRadiusScaled;
|
|
|
|
// Add as many particles as we need
|
|
while ( m_tSnowFallParticleTimer.NextEvent( flCurrentTime ) )
|
|
{
|
|
// Check for a max particle count.
|
|
if ( m_pSnowFallEmitter->GetParticleCount() >= r_SnowParticles.GetInt() )
|
|
continue;
|
|
|
|
vecParticleSpawn.x = RandomFloat( m_aSnow[m_aActiveSnow[iSnow]].m_vecMin.x, m_aSnow[m_aActiveSnow[iSnow]].m_vecMax.x );
|
|
vecParticleSpawn.y = RandomFloat( m_aSnow[m_aActiveSnow[iSnow]].m_vecMin.y, m_aSnow[m_aActiveSnow[iSnow]].m_vecMax.y );
|
|
vecParticleSpawn.z = RandomFloat( m_aSnow[m_aActiveSnow[iSnow]].m_vecMin.z, m_aSnow[m_aActiveSnow[iSnow]].m_vecMax.z );
|
|
|
|
float flDistance2 = ( m_vecSnowFallEmitOrigin - vecParticleSpawn ).LengthSqr();
|
|
if ( flDistance2 < flRadius2 )
|
|
{
|
|
CreateSnowFallParticle( vecParticleSpawn, m_aActiveSnow[iSnow], pLocalPlayer );
|
|
}
|
|
|
|
iSnow = ( iSnow + 1 ) % m_nActiveSnowCount;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : flCurrentTime -
|
|
// flRadius -
|
|
// &vecEyePos -
|
|
// &vecForward -
|
|
// flZoomScale -
|
|
//-----------------------------------------------------------------------------
|
|
void CSnowFallManager::CreateInsideVolumeSnowParticles( float flCurrentTime, float flRadius, const Vector &vecEyePos, const Vector &vecForward, float flZoomScale, C_BasePlayer *pLocalPlayer )
|
|
{
|
|
Vector vecParticleSpawn;
|
|
|
|
// Check/Setup for zoom.
|
|
bool bZoomed = ( flZoomScale > 1.0f );
|
|
float flZoomRadius = 0.0f;
|
|
Vector vecZoomEmitOrigin;
|
|
if ( bZoomed )
|
|
{
|
|
vecZoomEmitOrigin = m_vecSnowFallEmitOrigin + ( vecForward * ( r_SnowZoomOffset.GetFloat() * flZoomScale ) );
|
|
flZoomRadius = flRadius * flZoomScale;
|
|
}
|
|
|
|
int iIndex = 0;
|
|
|
|
// Add as many particles as we need
|
|
while ( m_tSnowFallParticleTimer.NextEvent( flCurrentTime ) )
|
|
{
|
|
// Check for a max particle count.
|
|
if ( m_pSnowFallEmitter->GetParticleCount() >= r_SnowParticles.GetInt() )
|
|
continue;
|
|
|
|
// Create particle inside of sphere.
|
|
if ( iIndex > 0 )
|
|
{
|
|
CreateSnowParticlesSphere( flZoomRadius, pLocalPlayer );
|
|
CreateSnowParticlesRay( flZoomRadius, vecEyePos, vecForward, pLocalPlayer );
|
|
}
|
|
else
|
|
{
|
|
CreateSnowParticlesSphere( flRadius, pLocalPlayer );
|
|
CreateSnowParticlesRay( flRadius, vecEyePos, vecForward, pLocalPlayer );
|
|
}
|
|
|
|
// Increment if zoomed.
|
|
if ( bZoomed )
|
|
{
|
|
iIndex = ( iIndex + 1 ) % 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : flRadius -
|
|
//-----------------------------------------------------------------------------
|
|
void CSnowFallManager::CreateSnowParticlesSphere( float flRadius, C_BasePlayer *pLocalPlayer )
|
|
{
|
|
Vector vecParticleSpawn;
|
|
|
|
vecParticleSpawn.x = m_vecSnowFallEmitOrigin.x + RandomFloat( -flRadius, flRadius );
|
|
vecParticleSpawn.y = m_vecSnowFallEmitOrigin.y + RandomFloat( -flRadius, flRadius );
|
|
vecParticleSpawn.z = m_vecSnowFallEmitOrigin.z + RandomFloat( -flRadius, flRadius );
|
|
|
|
int iSnow = 0;
|
|
for ( iSnow = 0; iSnow < m_nActiveSnowCount; ++iSnow )
|
|
{
|
|
if ( ( vecParticleSpawn.x < m_aSnow[m_aActiveSnow[iSnow]].m_vecMin.x ) || ( vecParticleSpawn.x > m_aSnow[m_aActiveSnow[iSnow]].m_vecMax.x ) )
|
|
continue;
|
|
if ( ( vecParticleSpawn.y < m_aSnow[m_aActiveSnow[iSnow]].m_vecMin.y ) || ( vecParticleSpawn.y > m_aSnow[m_aActiveSnow[iSnow]].m_vecMax.y ) )
|
|
continue;
|
|
if ( ( vecParticleSpawn.z < m_aSnow[m_aActiveSnow[iSnow]].m_vecMin.z ) || ( vecParticleSpawn.z > m_aSnow[m_aActiveSnow[iSnow]].m_vecMax.z ) )
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
|
|
if ( iSnow == m_nActiveSnowCount )
|
|
return;
|
|
|
|
CreateSnowFallParticle( vecParticleSpawn, m_aActiveSnow[iSnow], pLocalPlayer );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &vecEyePos -
|
|
// &vecForward -
|
|
//-----------------------------------------------------------------------------
|
|
void CSnowFallManager::CreateSnowParticlesRay( float flRadius, const Vector &vecEyePos, const Vector &vecForward, C_BasePlayer *pLocalPlayer )
|
|
{
|
|
// Check to see if we should create particles along line-of-sight.
|
|
if ( !m_bRayParticles && r_SnowRayEnable.GetBool() )
|
|
return;
|
|
|
|
Vector vecParticleSpawn;
|
|
|
|
// Create a particle down the player's view beyond the radius.
|
|
float flRayRadius = r_SnowRayRadius.GetFloat();
|
|
|
|
Vector vecNewForward;
|
|
vecNewForward = vecForward * RandomFloat( flRadius, r_SnowRayLength.GetFloat() );
|
|
|
|
vecParticleSpawn.x = vecEyePos.x + vecNewForward.x;
|
|
vecParticleSpawn.y = vecEyePos.y + vecNewForward.y;
|
|
vecParticleSpawn.z = vecEyePos.z + RandomFloat( 72, flRayRadius );
|
|
vecParticleSpawn.x += RandomFloat( -flRayRadius, flRayRadius );
|
|
vecParticleSpawn.y += RandomFloat( -flRayRadius, flRayRadius );
|
|
|
|
int iSnow = 0;
|
|
for ( iSnow = 0; iSnow < m_nActiveSnowCount; ++iSnow )
|
|
{
|
|
if ( ( vecParticleSpawn.x < m_aSnow[m_aActiveSnow[iSnow]].m_vecMin.x ) || ( vecParticleSpawn.x > m_aSnow[m_aActiveSnow[iSnow]].m_vecMax.x ) )
|
|
continue;
|
|
if ( ( vecParticleSpawn.y < m_aSnow[m_aActiveSnow[iSnow]].m_vecMin.y ) || ( vecParticleSpawn.y > m_aSnow[m_aActiveSnow[iSnow]].m_vecMax.y ) )
|
|
continue;
|
|
if ( ( vecParticleSpawn.z < m_aSnow[m_aActiveSnow[iSnow]].m_vecMin.z ) || ( vecParticleSpawn.z > m_aSnow[m_aActiveSnow[iSnow]].m_vecMax.z ) )
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
|
|
if ( iSnow == m_nActiveSnowCount )
|
|
return;
|
|
|
|
CreateSnowFallParticle( vecParticleSpawn, m_aActiveSnow[iSnow], pLocalPlayer );
|
|
}
|
|
|
|
void CSnowFallManager::CreateSnowFallParticle( const Vector &vecParticleSpawn, int iSnow, C_BasePlayer *pLocalPlayer )
|
|
{
|
|
SimpleParticle *pParticle = ( SimpleParticle* )m_pSnowFallEmitter->AddParticle( sizeof( SimpleParticle ), m_aSnow[iSnow].m_hMaterial, vecParticleSpawn );
|
|
if ( pParticle == NULL )
|
|
return;
|
|
|
|
pParticle->m_flLifetime = 0.0f;
|
|
pParticle->m_vecVelocity = Vector( RandomFloat( -5.0f, 5.0f ), RandomFloat( -5.0f, 5.0f ), ( RandomFloat( -25, -35 ) * r_SnowFallSpeed.GetFloat() ) );
|
|
pParticle->m_flDieTime = fabs( ( vecParticleSpawn.z - m_aSnow[iSnow].m_vecMin.z ) / ( pParticle->m_vecVelocity.z - 0.1 ) );
|
|
|
|
// We finally now use the color of the func_precipitation entity for snow color.
|
|
pParticle->m_uchColor[0] = m_aSnow[iSnow].m_pEntity->GetRenderColorR();
|
|
pParticle->m_uchColor[1] = m_aSnow[iSnow].m_pEntity->GetRenderColorG();
|
|
pParticle->m_uchColor[2] = m_aSnow[iSnow].m_pEntity->GetRenderColorB();
|
|
//pParticle->m_uchColor[0] = r_SnowColorRed.GetInt();
|
|
//pParticle->m_uchColor[1] = r_SnowColorGreen.GetInt();
|
|
//pParticle->m_uchColor[2] = r_SnowColorBlue.GetInt();
|
|
|
|
pParticle->m_uchStartSize = r_SnowStartSize.GetInt();
|
|
pParticle->m_uchEndSize = r_SnowEndSize.GetInt();
|
|
|
|
// pParticle->m_uchStartAlpha = 255;
|
|
pParticle->m_uchStartAlpha = r_SnowStartAlpha.GetInt();
|
|
pParticle->m_uchEndAlpha = r_SnowEndAlpha.GetInt();
|
|
|
|
pParticle->m_flRoll = random->RandomInt( 0, 360 );
|
|
pParticle->m_flRollDelta = random->RandomFloat( -0.15f, 0.15f );
|
|
|
|
pParticle->m_iFlags = SIMPLE_PARTICLE_FLAG_WINDBLOWN;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool SnowFallManagerCreate( CClient_Precipitation *pSnowEntity )
|
|
{
|
|
bool bret = true;
|
|
|
|
for ( int i = 0; i < MAX_SPLITSCREEN_PLAYERS; ++i )
|
|
{
|
|
if ( !s_pSnowFallMgr[ i ] )
|
|
{
|
|
ACTIVE_SPLITSCREEN_PLAYER_GUARD( i );
|
|
|
|
s_pSnowFallMgr[ i ] = new CSnowFallManager();
|
|
if ( !s_pSnowFallMgr[ i ] )
|
|
{
|
|
bret = false;
|
|
break;
|
|
}
|
|
s_pSnowFallMgr[ i ]->SetSplitScreenPlayerSlot( i );
|
|
s_pSnowFallMgr[ i ]->CreateEmitter();
|
|
s_pSnowFallMgr[ i ]->InitializeAsClientEntity( NULL, false );
|
|
g_pClientLeafSystem->EnableRendering( s_pSnowFallMgr[ i ]->RenderHandle(), false );
|
|
}
|
|
|
|
s_pSnowFallMgr[ i ]->AddSnowFallEntity( pSnowEntity );
|
|
}
|
|
return bret;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void SnowFallManagerDestroy( void )
|
|
{
|
|
for ( int i = 0; i < MAX_SPLITSCREEN_PLAYERS; ++i )
|
|
{
|
|
if ( s_pSnowFallMgr[ i ] )
|
|
{
|
|
delete s_pSnowFallMgr[ i ];
|
|
s_pSnowFallMgr[ i ] = NULL;
|
|
}
|
|
}
|
|
}
|