mirror of
https://github.com/alliedmodders/hl2sdk.git
synced 2025-01-04 00:23:25 +08:00
1457 lines
39 KiB
C++
1457 lines
39 KiB
C++
|
//===== Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ======//
|
|||
|
//
|
|||
|
// Purpose:
|
|||
|
//
|
|||
|
// $NoKeywords: $
|
|||
|
//
|
|||
|
//===========================================================================//
|
|||
|
|
|||
|
|
|||
|
#include "cbase.h"
|
|||
|
#include "particlemgr.h"
|
|||
|
#include "particledraw.h"
|
|||
|
#include "materialsystem/imesh.h"
|
|||
|
#include "materialsystem/imaterialvar.h"
|
|||
|
#include "mempool.h"
|
|||
|
#include "IClientMode.h"
|
|||
|
#include "view_scene.h"
|
|||
|
#include "tier0/vprof.h"
|
|||
|
#include "engine/ivdebugoverlay.h"
|
|||
|
#include "view.h"
|
|||
|
#include "keyvalues.h"
|
|||
|
|
|||
|
// memdbgon must be the last include file in a .cpp file!!!
|
|||
|
#include "tier0/memdbgon.h"
|
|||
|
|
|||
|
int g_nParticlesDrawn;
|
|||
|
// CCycleCount g_ParticleTimer;
|
|||
|
|
|||
|
static ConVar r_DrawParticles("r_drawparticles", "1", FCVAR_CHEAT, "Enable/disable particle rendering");
|
|||
|
static ConVar particle_simulateoverflow( "particle_simulateoverflow", "0", FCVAR_CHEAT, "Used for stress-testing particle systems. Randomly denies creation of particles." );
|
|||
|
static ConVar cl_particleeffect_aabb_buffer( "cl_particleeffect_aabb_buffer", "2", FCVAR_CHEAT, "Add this amount to a particle effect's bbox in the leaf system so if it's growing slowly, it won't have to be reinserted as often." );
|
|||
|
static ConVar cl_particles_show_bbox( "cl_particles_show_bbox", "0", FCVAR_CHEAT );
|
|||
|
|
|||
|
#define BUCKET_SORT_EVERY_N 8 // It does a bucket sort for each material approximately every N times.
|
|||
|
#define BBOX_UPDATE_EVERY_N 8 // It does a full bbox update (checks all particles instead of every eighth one).
|
|||
|
|
|||
|
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
//
|
|||
|
// Particle manager implementation
|
|||
|
//
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
|
|||
|
#define PARTICLE_SIZE 96
|
|||
|
|
|||
|
CParticleMgr *ParticleMgr()
|
|||
|
{
|
|||
|
static CParticleMgr s_ParticleMgr;
|
|||
|
return &s_ParticleMgr;
|
|||
|
}
|
|||
|
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
// Particle implementation
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
|
|||
|
void Particle::ToolRecordParticle( KeyValues *msg )
|
|||
|
{
|
|||
|
msg->SetPtr( "material", m_pSubTexture );
|
|||
|
msg->SetFloat( "posx", m_Pos.x );
|
|||
|
msg->SetFloat( "posy", m_Pos.y );
|
|||
|
msg->SetFloat( "posz", m_Pos.z );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
// CParticleSubTextureGroup implementation.
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
|
|||
|
CParticleSubTextureGroup::CParticleSubTextureGroup()
|
|||
|
{
|
|||
|
m_pPageMaterial = NULL;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
CParticleSubTextureGroup::~CParticleSubTextureGroup()
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
// CParticleSubTexture implementation.
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
|
|||
|
CParticleSubTexture::CParticleSubTexture()
|
|||
|
{
|
|||
|
m_tCoordMins[0] = m_tCoordMins[0] = 0;
|
|||
|
m_tCoordMaxs[0] = m_tCoordMaxs[0] = 1;
|
|||
|
m_pGroup = &m_DefaultGroup;
|
|||
|
m_pMaterial = NULL;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
// CEffectMaterial.
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
|
|||
|
CEffectMaterial::CEffectMaterial()
|
|||
|
{
|
|||
|
m_Particles.m_pNext = m_Particles.m_pPrev = &m_Particles;
|
|||
|
m_pGroup = NULL;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
// CParticleEffectBinding.
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
CParticleEffectBinding::CParticleEffectBinding()
|
|||
|
{
|
|||
|
m_pParticleMgr = NULL;
|
|||
|
m_pSim = NULL;
|
|||
|
|
|||
|
m_LocalSpaceTransform.Identity();
|
|||
|
m_bLocalSpaceTransformIdentity = true;
|
|||
|
|
|||
|
m_Flags = 0;
|
|||
|
SetAutoUpdateBBox( true );
|
|||
|
SetFirstFrameFlag( true );
|
|||
|
SetNeedsBBoxUpdate( true );
|
|||
|
SetAlwaysSimulate( true );
|
|||
|
SetEffectCameraSpace( true );
|
|||
|
SetDrawThruLeafSystem( true );
|
|||
|
SetAutoApplyLocalTransform( true );
|
|||
|
|
|||
|
// default bbox
|
|||
|
m_Min.Init( -50, -50, -50 );
|
|||
|
m_Max.Init( 50, 50, 50 );
|
|||
|
|
|||
|
m_LastMin = m_Min;
|
|||
|
m_LastMax = m_Max;
|
|||
|
|
|||
|
SetParticleCullRadius( 0.0f );
|
|||
|
m_nActiveParticles = 0;
|
|||
|
|
|||
|
m_FrameCode = 0;
|
|||
|
m_ListIndex = 0xFFFF;
|
|||
|
|
|||
|
m_UpdateBBoxCounter = 0;
|
|||
|
|
|||
|
memset( m_EffectMaterialHash, 0, sizeof( m_EffectMaterialHash ) );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
CParticleEffectBinding::~CParticleEffectBinding()
|
|||
|
{
|
|||
|
if( m_pParticleMgr )
|
|||
|
m_pParticleMgr->RemoveEffect( this );
|
|||
|
|
|||
|
Term();
|
|||
|
}
|
|||
|
|
|||
|
// The is the max size of the particles for use in bounding computation
|
|||
|
void CParticleEffectBinding::SetParticleCullRadius( float flMaxParticleRadius )
|
|||
|
{
|
|||
|
if ( m_flParticleCullRadius != flMaxParticleRadius )
|
|||
|
{
|
|||
|
m_flParticleCullRadius = flMaxParticleRadius;
|
|||
|
|
|||
|
if ( m_hRenderHandle != INVALID_CLIENT_RENDER_HANDLE )
|
|||
|
{
|
|||
|
ClientLeafSystem()->RenderableChanged( m_hRenderHandle );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
const Vector& CParticleEffectBinding::GetRenderOrigin( void )
|
|||
|
{
|
|||
|
return m_pSim->GetSortOrigin();
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
const QAngle& CParticleEffectBinding::GetRenderAngles( void )
|
|||
|
{
|
|||
|
return vec3_angle;
|
|||
|
}
|
|||
|
|
|||
|
const matrix3x4_t & CParticleEffectBinding::RenderableToWorldTransform()
|
|||
|
{
|
|||
|
static matrix3x4_t mat;
|
|||
|
SetIdentityMatrix( mat );
|
|||
|
PositionMatrix( GetRenderOrigin(), mat );
|
|||
|
return mat;
|
|||
|
}
|
|||
|
|
|||
|
void CParticleEffectBinding::GetRenderBounds( Vector& mins, Vector& maxs )
|
|||
|
{
|
|||
|
const Vector &vSortOrigin = m_pSim->GetSortOrigin();
|
|||
|
|
|||
|
// Convert to local space (around the sort origin).
|
|||
|
mins = m_Min - vSortOrigin;
|
|||
|
mins.x -= m_flParticleCullRadius; mins.y -= m_flParticleCullRadius; mins.z -= m_flParticleCullRadius;
|
|||
|
maxs = m_Max - vSortOrigin;
|
|||
|
maxs.x += m_flParticleCullRadius; maxs.y += m_flParticleCullRadius; maxs.z += m_flParticleCullRadius;
|
|||
|
}
|
|||
|
|
|||
|
bool CParticleEffectBinding::ShouldDraw( void )
|
|||
|
{
|
|||
|
return GetFlag( FLAGS_DRAW_THRU_LEAF_SYSTEM ) != 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
bool CParticleEffectBinding::IsTransparent( void )
|
|||
|
{
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
inline void CParticleEffectBinding::StartDrawMaterialParticles(
|
|||
|
CEffectMaterial *pMaterial,
|
|||
|
float flTimeDelta,
|
|||
|
IMesh* &pMesh,
|
|||
|
CMeshBuilder &builder,
|
|||
|
ParticleDraw &particleDraw,
|
|||
|
bool bWireframe )
|
|||
|
{
|
|||
|
// Setup the ParticleDraw and bind the material.
|
|||
|
if( bWireframe )
|
|||
|
{
|
|||
|
IMaterial *pMaterial = m_pParticleMgr->m_pMaterialSystem->FindMaterial( "debug/debugparticlewireframe", TEXTURE_GROUP_OTHER );
|
|||
|
m_pParticleMgr->m_pMaterialSystem->Bind( pMaterial, NULL );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
m_pParticleMgr->m_pMaterialSystem->Bind( pMaterial->m_pGroup->m_pPageMaterial, m_pParticleMgr );
|
|||
|
}
|
|||
|
|
|||
|
pMesh = m_pParticleMgr->m_pMaterialSystem->GetDynamicMesh( true );
|
|||
|
|
|||
|
builder.Begin( pMesh, MATERIAL_QUADS, NUM_PARTICLES_PER_BATCH * 4 );
|
|||
|
particleDraw.Init( &builder, pMaterial->m_pGroup->m_pPageMaterial, flTimeDelta );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleEffectBinding::BBoxCalcStart( bool bFullBBoxUpdate, Vector &bbMin, Vector &bbMax )
|
|||
|
{
|
|||
|
if ( !GetAutoUpdateBBox() )
|
|||
|
return;
|
|||
|
|
|||
|
if ( bFullBBoxUpdate )
|
|||
|
{
|
|||
|
// We're going to fully recompute the bbox.
|
|||
|
bbMin.Init( FLT_MAX, FLT_MAX, FLT_MAX );
|
|||
|
bbMax.Init( -FLT_MAX, -FLT_MAX, -FLT_MAX );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// We're going to push out the bbox using just some of the particles.
|
|||
|
if ( m_bLocalSpaceTransformIdentity )
|
|||
|
{
|
|||
|
bbMin = m_Min;
|
|||
|
bbMax = m_Max;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
ITransformAABB( m_LocalSpaceTransform.As3x4(), m_Min, m_Max, bbMin, bbMax );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleEffectBinding::BBoxCalcEnd( bool bFullBBoxUpdate, bool bboxSet, Vector &bbMin, Vector &bbMax )
|
|||
|
{
|
|||
|
if ( !GetAutoUpdateBBox() )
|
|||
|
return;
|
|||
|
|
|||
|
// Get the bbox into world space.
|
|||
|
Vector bbMinWorld, bbMaxWorld;
|
|||
|
if ( m_bLocalSpaceTransformIdentity )
|
|||
|
{
|
|||
|
bbMinWorld = bbMin;
|
|||
|
bbMaxWorld = bbMax;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
TransformAABB( m_LocalSpaceTransform.As3x4(), bbMin, bbMax, bbMinWorld, bbMaxWorld );
|
|||
|
}
|
|||
|
|
|||
|
if( bFullBBoxUpdate )
|
|||
|
{
|
|||
|
// If there were ANY particles in the system, then we've got a valid bbox here. Otherwise,
|
|||
|
// we don't have anything, so leave m_Min and m_Max at the sort origin.
|
|||
|
if ( bboxSet )
|
|||
|
{
|
|||
|
m_Min = bbMinWorld;
|
|||
|
m_Max = bbMaxWorld;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
m_Min = m_Max = m_pSim->GetSortOrigin();
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Take whatever our bbox was + pushing out from other particles.
|
|||
|
m_Min = bbMinWorld;
|
|||
|
m_Max = bbMaxWorld;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
int CParticleEffectBinding::DrawModel( int flags )
|
|||
|
{
|
|||
|
VPROF_BUDGET( "CParticleEffectBinding::DrawModel", VPROF_BUDGETGROUP_PARTICLE_RENDERING );
|
|||
|
#ifndef PARTICLEPROTOTYPE_APP
|
|||
|
if ( !r_DrawParticles.GetInt() )
|
|||
|
return 0;
|
|||
|
#endif
|
|||
|
|
|||
|
Assert( flags != 0 );
|
|||
|
|
|||
|
// If we're in commander mode and it's trying to draw the effect,
|
|||
|
// exit out. If the effect has FLAGS_ALWAYSSIMULATE set, then it'll come back
|
|||
|
// in here and simulate at the end of the frame.
|
|||
|
if( !g_pClientMode->ShouldDrawParticles() )
|
|||
|
return 0;
|
|||
|
|
|||
|
SetDrawn( true );
|
|||
|
|
|||
|
// Don't do anything if there are no particles.
|
|||
|
if( !m_nActiveParticles )
|
|||
|
return 1;
|
|||
|
|
|||
|
// Reset the transformation matrix to identity.
|
|||
|
VMatrix mTempModel, mTempView;
|
|||
|
RenderStart( mTempModel, mTempView );
|
|||
|
|
|||
|
// Setup to redo our bbox?
|
|||
|
bool bBucketSort = random->RandomInt( 0, BUCKET_SORT_EVERY_N ) == 0;
|
|||
|
|
|||
|
// Set frametime to zero if we've already rendered this frame.
|
|||
|
float flFrameTime = 0;
|
|||
|
if ( m_FrameCode != m_pParticleMgr->m_FrameCode )
|
|||
|
{
|
|||
|
m_FrameCode = m_pParticleMgr->m_FrameCode;
|
|||
|
flFrameTime = Helper_GetFrameTime();
|
|||
|
}
|
|||
|
|
|||
|
// For each material, render...
|
|||
|
// This does an incremental bubble sort. It only does one pass every frame, and it will shuffle
|
|||
|
// unsorted particles one step towards where they should be.
|
|||
|
bool bWireframe = false;
|
|||
|
FOR_EACH_LL( m_Materials, iMaterial )
|
|||
|
{
|
|||
|
CEffectMaterial *pMaterial = m_Materials[iMaterial];
|
|||
|
|
|||
|
if ( pMaterial->m_pGroup->m_pPageMaterial && pMaterial->m_pGroup->m_pPageMaterial->NeedsPowerOfTwoFrameBufferTexture() )
|
|||
|
{
|
|||
|
UpdateRefractTexture();
|
|||
|
}
|
|||
|
|
|||
|
DrawMaterialParticles(
|
|||
|
bBucketSort,
|
|||
|
pMaterial,
|
|||
|
flFrameTime,
|
|||
|
bWireframe );
|
|||
|
}
|
|||
|
|
|||
|
if ( ShouldDrawInWireFrameMode() )
|
|||
|
{
|
|||
|
bWireframe = true;
|
|||
|
FOR_EACH_LL( m_Materials, iDrawMaterial )
|
|||
|
{
|
|||
|
CEffectMaterial *pMaterial = m_Materials[iDrawMaterial];
|
|||
|
|
|||
|
DrawMaterialParticles(
|
|||
|
bBucketSort,
|
|||
|
pMaterial,
|
|||
|
flFrameTime,
|
|||
|
bWireframe );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if ( !IsRetail() && cl_particles_show_bbox.GetBool() )
|
|||
|
{
|
|||
|
Vector center = (m_Min + m_Max)/2;
|
|||
|
Vector mins = m_Min - center;
|
|||
|
Vector maxs = m_Max - center;
|
|||
|
|
|||
|
int r, g;
|
|||
|
if ( m_Flags & FLAGS_AUTOUPDATEBBOX )
|
|||
|
{
|
|||
|
// red is bad, the bbox update is costly
|
|||
|
r = 255;
|
|||
|
g = 0;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// green, this effect presents less cpu load
|
|||
|
r = 0;
|
|||
|
g = 255;
|
|||
|
}
|
|||
|
|
|||
|
debugoverlay->AddBoxOverlay( center, mins, maxs, QAngle( 0, 0, 0 ), r, g, 0, 16, 0 );
|
|||
|
debugoverlay->AddTextOverlayRGB( center, 0, 0, r, g, 0, 64, "%s:(%d)", m_pSim->GetEffectName(), m_nActiveParticles );
|
|||
|
}
|
|||
|
|
|||
|
RenderEnd( mTempModel, mTempView );
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
PMaterialHandle CParticleEffectBinding::FindOrAddMaterial( const char *pMaterialName )
|
|||
|
{
|
|||
|
if ( !m_pParticleMgr )
|
|||
|
{
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
return m_pParticleMgr->GetPMaterial( pMaterialName );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
Particle* CParticleEffectBinding::AddParticle( int sizeInBytes, PMaterialHandle hMaterial )
|
|||
|
{
|
|||
|
// We've currently clamped the particle size to PARTICLE_SIZE,
|
|||
|
// we may need to change this algorithm if we get particles with
|
|||
|
// widely varying size
|
|||
|
if ( sizeInBytes > PARTICLE_SIZE )
|
|||
|
{
|
|||
|
Assert( sizeInBytes <= PARTICLE_SIZE );
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
// This is for testing - simulate it running out of memory.
|
|||
|
if ( particle_simulateoverflow.GetInt() )
|
|||
|
{
|
|||
|
if ( rand() % 10 <= 6 )
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
// Allocate the puppy. We are actually allocating space for the
|
|||
|
// internals + the actual data
|
|||
|
Particle* pParticle = m_pParticleMgr->AllocParticle( PARTICLE_SIZE );
|
|||
|
if( !pParticle )
|
|||
|
return NULL;
|
|||
|
|
|||
|
// Link it in
|
|||
|
CEffectMaterial *pEffectMat = GetEffectMaterial( hMaterial );
|
|||
|
InsertParticleAfter( pParticle, &pEffectMat->m_Particles );
|
|||
|
|
|||
|
if ( hMaterial )
|
|||
|
pParticle->m_pSubTexture = hMaterial;
|
|||
|
else
|
|||
|
pParticle->m_pSubTexture = &m_pParticleMgr->m_DefaultInvalidSubTexture;
|
|||
|
|
|||
|
++m_nActiveParticles;
|
|||
|
return pParticle;
|
|||
|
}
|
|||
|
|
|||
|
void CParticleEffectBinding::SetBBox( const Vector &bbMin, const Vector &bbMax, bool bDisableAutoUpdate )
|
|||
|
{
|
|||
|
m_Min = bbMin;
|
|||
|
m_Max = bbMax;
|
|||
|
|
|||
|
if ( bDisableAutoUpdate )
|
|||
|
SetAutoUpdateBBox( false );
|
|||
|
}
|
|||
|
|
|||
|
void CParticleEffectBinding::SetLocalSpaceTransform( const matrix3x4_t &transform )
|
|||
|
{
|
|||
|
m_LocalSpaceTransform.CopyFrom3x4( transform );
|
|||
|
if ( m_LocalSpaceTransform.IsIdentity() )
|
|||
|
{
|
|||
|
m_bLocalSpaceTransformIdentity = true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
m_bLocalSpaceTransformIdentity = false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
bool CParticleEffectBinding::EnlargeBBoxToContain( const Vector &pt )
|
|||
|
{
|
|||
|
if ( m_nActiveParticles == 0 )
|
|||
|
{
|
|||
|
m_Min = m_Max = pt;
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
bool bHasChanged = false;
|
|||
|
|
|||
|
// check min bounds
|
|||
|
if ( pt.x < m_Min.x )
|
|||
|
{ m_Min.x = pt.x; bHasChanged = true; }
|
|||
|
|
|||
|
if ( pt.y < m_Min.y )
|
|||
|
{ m_Min.y = pt.y; bHasChanged = true; }
|
|||
|
|
|||
|
if ( pt.z < m_Min.z )
|
|||
|
{ m_Min.z = pt.z; bHasChanged = true; }
|
|||
|
|
|||
|
// check max bounds
|
|||
|
if ( pt.x > m_Max.x )
|
|||
|
{ m_Max.x = pt.x; bHasChanged = true; }
|
|||
|
|
|||
|
if ( pt.y > m_Max.y )
|
|||
|
{ m_Max.y = pt.y; bHasChanged = true; }
|
|||
|
|
|||
|
if ( pt.z > m_Max.z )
|
|||
|
{ m_Max.z = pt.z; bHasChanged = true; }
|
|||
|
|
|||
|
return bHasChanged;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleEffectBinding::DetectChanges()
|
|||
|
{
|
|||
|
// if we have no render handle, return
|
|||
|
if ( m_hRenderHandle == INVALID_CLIENT_RENDER_HANDLE )
|
|||
|
return;
|
|||
|
|
|||
|
float flBuffer = cl_particleeffect_aabb_buffer.GetFloat();
|
|||
|
float flExtraBuffer = flBuffer * 1.3f;
|
|||
|
|
|||
|
// if nothing changed, return
|
|||
|
if ( m_Min.x < m_LastMin.x ||
|
|||
|
m_Min.y < m_LastMin.y ||
|
|||
|
m_Min.z < m_LastMin.z ||
|
|||
|
|
|||
|
m_Min.x > (m_LastMin.x + flExtraBuffer) ||
|
|||
|
m_Min.y > (m_LastMin.y + flExtraBuffer) ||
|
|||
|
m_Min.z > (m_LastMin.z + flExtraBuffer) ||
|
|||
|
|
|||
|
m_Max.x > m_LastMax.x ||
|
|||
|
m_Max.y > m_LastMax.y ||
|
|||
|
m_Max.z > m_LastMax.z ||
|
|||
|
|
|||
|
m_Max.x < (m_LastMax.x - flExtraBuffer) ||
|
|||
|
m_Max.y < (m_LastMax.y - flExtraBuffer) ||
|
|||
|
m_Max.z < (m_LastMax.z - flExtraBuffer)
|
|||
|
)
|
|||
|
{
|
|||
|
// call leafsystem to updated this guy
|
|||
|
ClientLeafSystem()->RenderableChanged( m_hRenderHandle );
|
|||
|
|
|||
|
// remember last parameters
|
|||
|
// Add some padding in here so we don't reinsert it into the leaf system if it just changes a tiny amount.
|
|||
|
m_LastMin = m_Min - Vector( flBuffer, flBuffer, flBuffer );
|
|||
|
m_LastMax = m_Max + Vector( flBuffer, flBuffer, flBuffer );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleEffectBinding::GrowBBoxFromParticlePositions( CEffectMaterial *pMaterial, bool bFullBBoxUpdate, bool &bboxSet, Vector &bbMin, Vector &bbMax )
|
|||
|
{
|
|||
|
// If its bbox is manually set, don't bother updating it here.
|
|||
|
if ( !GetAutoUpdateBBox() )
|
|||
|
return;
|
|||
|
|
|||
|
if ( bFullBBoxUpdate )
|
|||
|
{
|
|||
|
for( Particle *pCur=pMaterial->m_Particles.m_pNext; pCur != &pMaterial->m_Particles; pCur=pCur->m_pNext )
|
|||
|
{
|
|||
|
// Update bounding box
|
|||
|
VectorMin( bbMin, pCur->m_Pos, bbMin );
|
|||
|
VectorMax( bbMax, pCur->m_Pos, bbMax );
|
|||
|
bboxSet = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
// Simulate particles
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
void CParticleEffectBinding::SimulateParticles( float flTimeDelta )
|
|||
|
{
|
|||
|
if ( !m_pSim->ShouldSimulate() )
|
|||
|
return;
|
|||
|
|
|||
|
Vector bbMin(0,0,0), bbMax(0,0,0);
|
|||
|
bool bboxSet = false;
|
|||
|
|
|||
|
// slow the expensive update operation for particle systems that use auto-update-bbox
|
|||
|
// auto update the bbox after N frames then randomly 1/N or after 2*N frames
|
|||
|
bool bFullBBoxUpdate = false;
|
|||
|
++m_UpdateBBoxCounter;
|
|||
|
if ( ( m_UpdateBBoxCounter >= BBOX_UPDATE_EVERY_N && random->RandomInt( 0, BBOX_UPDATE_EVERY_N ) == 0 ) ||
|
|||
|
( m_UpdateBBoxCounter >= 2*BBOX_UPDATE_EVERY_N ) )
|
|||
|
{
|
|||
|
bFullBBoxUpdate = true;
|
|||
|
|
|||
|
// reset watchdog
|
|||
|
m_UpdateBBoxCounter = 0;
|
|||
|
}
|
|||
|
|
|||
|
BBoxCalcStart( bFullBBoxUpdate, bbMin, bbMax );
|
|||
|
|
|||
|
FOR_EACH_LL( m_Materials, i )
|
|||
|
{
|
|||
|
CEffectMaterial *pMaterial = m_Materials[i];
|
|||
|
|
|||
|
CParticleSimulateIterator simulateIterator;
|
|||
|
|
|||
|
simulateIterator.m_pEffectBinding = this;
|
|||
|
simulateIterator.m_pMaterial = pMaterial;
|
|||
|
simulateIterator.m_flTimeDelta = flTimeDelta;
|
|||
|
|
|||
|
m_pSim->SimulateParticles( &simulateIterator );
|
|||
|
|
|||
|
// Update the bbox.
|
|||
|
GrowBBoxFromParticlePositions( pMaterial, bFullBBoxUpdate, bboxSet, bbMin, bbMax );
|
|||
|
}
|
|||
|
|
|||
|
BBoxCalcEnd( bFullBBoxUpdate, bboxSet, bbMin, bbMax );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleEffectBinding::SetDrawThruLeafSystem( int bDraw )
|
|||
|
{
|
|||
|
if ( bDraw )
|
|||
|
{
|
|||
|
// If SetDrawBeforeViewModel was called, then they shouldn't be telling it to draw through
|
|||
|
// the leaf system too.
|
|||
|
Assert( !( m_Flags & FLAGS_DRAW_BEFORE_VIEW_MODEL) );
|
|||
|
}
|
|||
|
|
|||
|
SetFlag( FLAGS_DRAW_THRU_LEAF_SYSTEM, bDraw );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleEffectBinding::SetDrawBeforeViewModel( int bDraw )
|
|||
|
{
|
|||
|
// Don't draw through the leaf system if they want it to specifically draw before the view model.
|
|||
|
if ( bDraw )
|
|||
|
m_Flags &= ~FLAGS_DRAW_THRU_LEAF_SYSTEM;
|
|||
|
|
|||
|
SetFlag( FLAGS_DRAW_BEFORE_VIEW_MODEL, bDraw );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
int CParticleEffectBinding::GetNumActiveParticles()
|
|||
|
{
|
|||
|
return m_nActiveParticles;
|
|||
|
}
|
|||
|
|
|||
|
// Build a list of all active particles
|
|||
|
int CParticleEffectBinding::GetActiveParticleList( int nCount, Particle **ppParticleList )
|
|||
|
{
|
|||
|
int nCurrCount = 0;
|
|||
|
|
|||
|
FOR_EACH_LL( m_Materials, i )
|
|||
|
{
|
|||
|
CEffectMaterial *pMaterial = m_Materials[i];
|
|||
|
Particle *pParticle = pMaterial->m_Particles.m_pNext;
|
|||
|
for ( ; pParticle != &pMaterial->m_Particles; pParticle = pParticle->m_pNext )
|
|||
|
{
|
|||
|
ppParticleList[nCurrCount] = pParticle;
|
|||
|
if ( ++nCurrCount == nCount )
|
|||
|
return nCurrCount;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return nCurrCount;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
int CParticleEffectBinding::DrawMaterialParticles(
|
|||
|
bool bBucketSort,
|
|||
|
CEffectMaterial *pMaterial,
|
|||
|
float flTimeDelta,
|
|||
|
bool bWireframe
|
|||
|
)
|
|||
|
{
|
|||
|
// Setup everything.
|
|||
|
CMeshBuilder builder;
|
|||
|
ParticleDraw particleDraw;
|
|||
|
IMesh *pMesh = NULL;
|
|||
|
StartDrawMaterialParticles( pMaterial, flTimeDelta, pMesh, builder, particleDraw, bWireframe );
|
|||
|
|
|||
|
if ( m_nActiveParticles > MAX_TOTAL_PARTICLES )
|
|||
|
Error( "CParticleEffectBinding::DrawMaterialParticles: too many particles (%d should be less than %d)", m_nActiveParticles, MAX_TOTAL_PARTICLES );
|
|||
|
|
|||
|
// Simluate and render all the particles.
|
|||
|
CParticleRenderIterator renderIterator;
|
|||
|
|
|||
|
renderIterator.m_pEffectBinding = this;
|
|||
|
renderIterator.m_pMaterial = pMaterial;
|
|||
|
renderIterator.m_pParticleDraw = &particleDraw;
|
|||
|
renderIterator.m_pMeshBuilder = &builder;
|
|||
|
renderIterator.m_pMesh = pMesh;
|
|||
|
renderIterator.m_bBucketSort = bBucketSort;
|
|||
|
|
|||
|
m_pSim->RenderParticles( &renderIterator );
|
|||
|
g_nParticlesDrawn += m_nActiveParticles;
|
|||
|
|
|||
|
if( bBucketSort )
|
|||
|
{
|
|||
|
DoBucketSort( pMaterial, renderIterator.m_zCoords, renderIterator.m_nZCoords, renderIterator.m_MinZ, renderIterator.m_MaxZ );
|
|||
|
}
|
|||
|
|
|||
|
// Flush out any remaining particles.
|
|||
|
builder.End( false, true );
|
|||
|
|
|||
|
return m_nActiveParticles;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleEffectBinding::RenderStart( VMatrix &tempModel, VMatrix &tempView )
|
|||
|
{
|
|||
|
if( IsEffectCameraSpace() )
|
|||
|
{
|
|||
|
// Store matrices off so we can restore them in RenderEnd().
|
|||
|
m_pParticleMgr->m_pMaterialSystem->GetMatrix(MATERIAL_VIEW, &tempView);
|
|||
|
m_pParticleMgr->m_pMaterialSystem->GetMatrix(MATERIAL_MODEL, &tempModel);
|
|||
|
|
|||
|
// We're gonna assume the model matrix was identity and blow it off
|
|||
|
// This means that the particle positions are all specified in world space
|
|||
|
// which makes bounding box computations faster.
|
|||
|
m_pParticleMgr->m_mModelView = tempView;
|
|||
|
|
|||
|
// Force the user clip planes to use the old view matrix
|
|||
|
m_pParticleMgr->m_pMaterialSystem->EnableUserClipTransformOverride( true );
|
|||
|
m_pParticleMgr->m_pMaterialSystem->UserClipTransform( tempView );
|
|||
|
|
|||
|
// The particle renderers want to do things in camera space
|
|||
|
m_pParticleMgr->m_pMaterialSystem->MatrixMode( MATERIAL_MODEL );
|
|||
|
m_pParticleMgr->m_pMaterialSystem->LoadIdentity();
|
|||
|
|
|||
|
m_pParticleMgr->m_pMaterialSystem->MatrixMode( MATERIAL_VIEW );
|
|||
|
m_pParticleMgr->m_pMaterialSystem->LoadIdentity();
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
m_pParticleMgr->m_mModelView.Identity();
|
|||
|
}
|
|||
|
|
|||
|
// Add their local space transform if they have one and they want it applied.
|
|||
|
if ( GetAutoApplyLocalTransform() && !m_bLocalSpaceTransformIdentity )
|
|||
|
{
|
|||
|
m_pParticleMgr->m_mModelView = m_pParticleMgr->m_mModelView * m_LocalSpaceTransform;
|
|||
|
}
|
|||
|
|
|||
|
// Let the particle effect do any per-frame setup/processing here
|
|||
|
m_pSim->StartRender( m_pParticleMgr->m_mModelView );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleEffectBinding::RenderEnd( VMatrix &tempModel, VMatrix &tempView )
|
|||
|
{
|
|||
|
if( IsEffectCameraSpace() )
|
|||
|
{
|
|||
|
// Make user clip planes work normally
|
|||
|
m_pParticleMgr->m_pMaterialSystem->EnableUserClipTransformOverride( false );
|
|||
|
|
|||
|
// Reset the model matrix.
|
|||
|
m_pParticleMgr->m_pMaterialSystem->MatrixMode( MATERIAL_MODEL );
|
|||
|
m_pParticleMgr->m_pMaterialSystem->LoadMatrix( tempModel );
|
|||
|
|
|||
|
// Reset the view matrix.
|
|||
|
m_pParticleMgr->m_pMaterialSystem->MatrixMode( MATERIAL_VIEW );
|
|||
|
m_pParticleMgr->m_pMaterialSystem->LoadMatrix( tempView );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleEffectBinding::DoBucketSort( CEffectMaterial *pMaterial, float *zCoords, int nZCoords, float minZ, float maxZ )
|
|||
|
{
|
|||
|
// Do an O(N) bucket sort. This helps the sort when there are lots of particles.
|
|||
|
#define NUM_BUCKETS 32
|
|||
|
Particle buckets[NUM_BUCKETS];
|
|||
|
for( int iBucket=0; iBucket < NUM_BUCKETS; iBucket++ )
|
|||
|
{
|
|||
|
buckets[iBucket].m_pPrev = buckets[iBucket].m_pNext = &buckets[iBucket];
|
|||
|
}
|
|||
|
|
|||
|
// Sort into buckets.
|
|||
|
int iCurParticle = 0;
|
|||
|
Particle *pNext, *pCur;
|
|||
|
for( pCur=pMaterial->m_Particles.m_pNext; pCur != &pMaterial->m_Particles; pCur=pNext )
|
|||
|
{
|
|||
|
pNext = pCur->m_pNext;
|
|||
|
if( iCurParticle >= nZCoords )
|
|||
|
break;
|
|||
|
|
|||
|
// Remove it..
|
|||
|
UnlinkParticle( pCur );
|
|||
|
|
|||
|
// Add it to the appropriate bucket.
|
|||
|
float flPercent;
|
|||
|
if (maxZ == minZ)
|
|||
|
flPercent = 0;
|
|||
|
else
|
|||
|
flPercent = (zCoords[iCurParticle] - minZ) / (maxZ - minZ);
|
|||
|
|
|||
|
int iAddBucket = (int)( flPercent * (NUM_BUCKETS - 0.0001f) );
|
|||
|
iAddBucket = NUM_BUCKETS - iAddBucket - 1;
|
|||
|
Assert( iAddBucket >= 0 && iAddBucket < NUM_BUCKETS );
|
|||
|
|
|||
|
InsertParticleAfter( pCur, &buckets[iAddBucket] );
|
|||
|
|
|||
|
++iCurParticle;
|
|||
|
}
|
|||
|
|
|||
|
// Put the buckets back into the main list.
|
|||
|
for( int iReAddBucket=0; iReAddBucket < NUM_BUCKETS; iReAddBucket++ )
|
|||
|
{
|
|||
|
Particle *pListHead = &buckets[iReAddBucket];
|
|||
|
for( pCur=pListHead->m_pNext; pCur != pListHead; pCur=pNext )
|
|||
|
{
|
|||
|
pNext = pCur->m_pNext;
|
|||
|
InsertParticleAfter( pCur, &pMaterial->m_Particles );
|
|||
|
--iCurParticle;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Assert(iCurParticle==0);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleEffectBinding::Init( CParticleMgr *pMgr, IParticleEffect *pSim )
|
|||
|
{
|
|||
|
// Must Term before reinitializing.
|
|||
|
Assert( !m_pSim && !m_pParticleMgr );
|
|||
|
|
|||
|
m_pSim = pSim;
|
|||
|
m_pParticleMgr = pMgr;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleEffectBinding::Term()
|
|||
|
{
|
|||
|
if ( !m_pParticleMgr )
|
|||
|
return;
|
|||
|
|
|||
|
// Free materials.
|
|||
|
FOR_EACH_LL( m_Materials, iMaterial )
|
|||
|
{
|
|||
|
CEffectMaterial *pMaterial = m_Materials[iMaterial];
|
|||
|
|
|||
|
// Remove all particles tied to this effect.
|
|||
|
Particle *pNext = NULL;
|
|||
|
for(Particle *pCur = pMaterial->m_Particles.m_pNext; pCur != &pMaterial->m_Particles; pCur=pNext )
|
|||
|
{
|
|||
|
pNext = pCur->m_pNext;
|
|||
|
|
|||
|
RemoveParticle( pCur );
|
|||
|
}
|
|||
|
|
|||
|
delete pMaterial;
|
|||
|
}
|
|||
|
m_Materials.Purge();
|
|||
|
|
|||
|
memset( m_EffectMaterialHash, 0, sizeof( m_EffectMaterialHash ) );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleEffectBinding::RemoveParticle( Particle *pParticle )
|
|||
|
{
|
|||
|
UnlinkParticle( pParticle );
|
|||
|
|
|||
|
// Important that this is updated BEFORE NotifyDestroyParticle is called.
|
|||
|
--m_nActiveParticles;
|
|||
|
Assert( m_nActiveParticles >= 0 );
|
|||
|
|
|||
|
// Let the effect do any necessary cleanup
|
|||
|
m_pSim->NotifyDestroyParticle(pParticle);
|
|||
|
|
|||
|
// Remove it from the list of particles and deallocate
|
|||
|
m_pParticleMgr->FreeParticle(pParticle);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
bool CParticleEffectBinding::RecalculateBoundingBox()
|
|||
|
{
|
|||
|
if ( m_nActiveParticles == 0 )
|
|||
|
{
|
|||
|
m_Max = m_Min = m_pSim->GetSortOrigin();
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
Vector bbMin( 1e28, 1e28, 1e28 );
|
|||
|
Vector bbMax( -1e28, -1e28, -1e28 );
|
|||
|
|
|||
|
FOR_EACH_LL( m_Materials, iMaterial )
|
|||
|
{
|
|||
|
CEffectMaterial *pMaterial = m_Materials[iMaterial];
|
|||
|
|
|||
|
for( Particle *pCur=pMaterial->m_Particles.m_pNext; pCur != &pMaterial->m_Particles; pCur=pCur->m_pNext )
|
|||
|
{
|
|||
|
VectorMin( bbMin, pCur->m_Pos, bbMin );
|
|||
|
VectorMax( bbMax, pCur->m_Pos, bbMax );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Get the bbox into world space.
|
|||
|
if ( m_bLocalSpaceTransformIdentity )
|
|||
|
{
|
|||
|
m_Min = bbMin;
|
|||
|
m_Max = bbMax;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
TransformAABB( m_LocalSpaceTransform.As3x4(), bbMin, bbMax, m_Min, m_Max );
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
CEffectMaterial* CParticleEffectBinding::GetEffectMaterial( CParticleSubTexture *pSubTexture )
|
|||
|
{
|
|||
|
// Hash the IMaterial pointer.
|
|||
|
unsigned long index = (((unsigned long)pSubTexture->m_pGroup) >> 6) % EFFECT_MATERIAL_HASH_SIZE;
|
|||
|
for ( CEffectMaterial *pCur=m_EffectMaterialHash[index]; pCur; pCur = pCur->m_pHashedNext )
|
|||
|
{
|
|||
|
if ( pCur->m_pGroup == pSubTexture->m_pGroup )
|
|||
|
return pCur;
|
|||
|
}
|
|||
|
|
|||
|
CEffectMaterial *pEffectMat = new CEffectMaterial;
|
|||
|
pEffectMat->m_pGroup = pSubTexture->m_pGroup;
|
|||
|
pEffectMat->m_pHashedNext = m_EffectMaterialHash[index];
|
|||
|
m_EffectMaterialHash[index] = pEffectMat;
|
|||
|
|
|||
|
m_Materials.AddToTail( pEffectMat );
|
|||
|
return pEffectMat;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
// CParticleMgr
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
|
|||
|
CParticleMgr::CParticleMgr()
|
|||
|
{
|
|||
|
m_nToolParticleEffectId = 0;
|
|||
|
m_bUpdatingEffects = false;
|
|||
|
m_pMaterialSystem = NULL;
|
|||
|
|
|||
|
memset( &m_DirectionalLight, 0, sizeof( m_DirectionalLight ) );
|
|||
|
|
|||
|
m_FrameCode = 1;
|
|||
|
|
|||
|
m_DefaultInvalidSubTexture.m_pGroup = &m_DefaultInvalidSubTexture.m_DefaultGroup;
|
|||
|
m_DefaultInvalidSubTexture.m_pMaterial = NULL;
|
|||
|
m_DefaultInvalidSubTexture.m_tCoordMins[0] = m_DefaultInvalidSubTexture.m_tCoordMins[1] = 0;
|
|||
|
m_DefaultInvalidSubTexture.m_tCoordMaxs[0] = m_DefaultInvalidSubTexture.m_tCoordMaxs[1] = 1;
|
|||
|
|
|||
|
m_nCurrentParticlesAllocated = 0;
|
|||
|
|
|||
|
SetDefLessFunc( m_effectFactories );
|
|||
|
}
|
|||
|
|
|||
|
CParticleMgr::~CParticleMgr()
|
|||
|
{
|
|||
|
Term();
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
// Initialization and shutdown
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
bool CParticleMgr::Init(unsigned long count, IMaterialSystem *pMaterials)
|
|||
|
{
|
|||
|
Term();
|
|||
|
|
|||
|
m_pMaterialSystem = pMaterials;
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
void CParticleMgr::Term()
|
|||
|
{
|
|||
|
// Free all the effects.
|
|||
|
int iNext;
|
|||
|
for ( int i=m_Effects.Head(); i != m_Effects.InvalidIndex(); i = iNext )
|
|||
|
{
|
|||
|
iNext = m_Effects.Next( i );
|
|||
|
m_Effects[i]->m_pSim->NotifyRemove();
|
|||
|
}
|
|||
|
m_Effects.Purge();
|
|||
|
|
|||
|
m_SubTextures.PurgeAndDeleteElements();
|
|||
|
m_SubTextureGroups.PurgeAndDeleteElements();
|
|||
|
|
|||
|
if ( m_pMaterialSystem )
|
|||
|
{
|
|||
|
m_pMaterialSystem->UncacheUnusedMaterials();
|
|||
|
}
|
|||
|
m_pMaterialSystem = NULL;
|
|||
|
|
|||
|
Assert( m_nCurrentParticlesAllocated == 0 );
|
|||
|
}
|
|||
|
|
|||
|
Particle *CParticleMgr::AllocParticle( int size )
|
|||
|
{
|
|||
|
// Enforce max particle limit.
|
|||
|
if ( m_nCurrentParticlesAllocated >= MAX_TOTAL_PARTICLES )
|
|||
|
return NULL;
|
|||
|
|
|||
|
Particle *pRet = (Particle *)malloc( size );
|
|||
|
if ( pRet )
|
|||
|
++m_nCurrentParticlesAllocated;
|
|||
|
|
|||
|
return pRet;
|
|||
|
}
|
|||
|
|
|||
|
void CParticleMgr::FreeParticle( Particle *pParticle )
|
|||
|
{
|
|||
|
Assert( m_nCurrentParticlesAllocated > 0 );
|
|||
|
if ( pParticle )
|
|||
|
--m_nCurrentParticlesAllocated;
|
|||
|
|
|||
|
free( pParticle );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
// add a class that gets notified of entity events
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
|
|||
|
void CParticleMgr::AddEffectListener( IClientParticleListener *pListener )
|
|||
|
{
|
|||
|
int i = m_effectListeners.Find( pListener );
|
|||
|
if ( !m_effectListeners.IsValidIndex( i ) )
|
|||
|
{
|
|||
|
m_effectListeners.AddToTail( pListener );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void CParticleMgr::RemoveEffectListener( IClientParticleListener *pListener )
|
|||
|
{
|
|||
|
int i = m_effectListeners.Find( pListener );
|
|||
|
if ( m_effectListeners.IsValidIndex( i ) )
|
|||
|
{
|
|||
|
m_effectListeners.Remove( i );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
// registers effects classes, and create instances of these effects classes
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
|
|||
|
void CParticleMgr::RegisterEffect( const char *pEffectType, CreateParticleEffectFN func )
|
|||
|
{
|
|||
|
#ifdef _DEBUG
|
|||
|
int i = m_effectFactories.Find( pEffectType );
|
|||
|
Assert( !m_effectFactories.IsValidIndex( i ) );
|
|||
|
#endif
|
|||
|
|
|||
|
m_effectFactories.Insert( pEffectType, func );
|
|||
|
}
|
|||
|
|
|||
|
IParticleEffect *CParticleMgr::CreateEffect( const char *pEffectType )
|
|||
|
{
|
|||
|
int i = m_effectFactories.Find( pEffectType );
|
|||
|
if ( !m_effectFactories.IsValidIndex( i ) )
|
|||
|
{
|
|||
|
Msg( "CParticleMgr::CreateEffect: factory not found for effect '%s'\n", pEffectType );
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
CreateParticleEffectFN func = m_effectFactories[ i ];
|
|||
|
if ( func == NULL )
|
|||
|
{
|
|||
|
Msg( "CParticleMgr::CreateEffect: NULL factory for effect '%s'\n", pEffectType );
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
return func();
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
// Adds and removes effects from our global list
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
|
|||
|
bool CParticleMgr::AddEffect( CParticleEffectBinding *pEffect, IParticleEffect *pSim )
|
|||
|
{
|
|||
|
#ifdef _DEBUG
|
|||
|
FOR_EACH_LL( m_Effects, i )
|
|||
|
{
|
|||
|
if( m_Effects[i]->m_pSim == pSim )
|
|||
|
{
|
|||
|
Assert( !"CParticleMgr::AddEffect: added same effect twice" );
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
pEffect->Init( this, pSim );
|
|||
|
|
|||
|
// Add it to the leaf system.
|
|||
|
#if !defined( PARTICLEPROTOTYPE_APP )
|
|||
|
ClientLeafSystem()->CreateRenderableHandle( pEffect );
|
|||
|
#endif
|
|||
|
|
|||
|
pEffect->m_ListIndex = m_Effects.AddToTail( pEffect );
|
|||
|
|
|||
|
Assert( pEffect->m_ListIndex != 0xFFFF );
|
|||
|
|
|||
|
// notify listeners
|
|||
|
int nListeners = m_effectListeners.Count();
|
|||
|
for ( int i = 0; i < nListeners; ++i )
|
|||
|
{
|
|||
|
m_effectListeners[ i ]->OnParticleEffectAdded( pSim );
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleMgr::RemoveEffect( CParticleEffectBinding *pEffect )
|
|||
|
{
|
|||
|
// This prevents certain recursive situations where a NotifyRemove
|
|||
|
// call can wind up triggering another one, usually in an effect's
|
|||
|
// destructor.
|
|||
|
if( pEffect->GetRemovalInProgressFlag() )
|
|||
|
return;
|
|||
|
pEffect->SetRemovalInProgressFlag();
|
|||
|
|
|||
|
// Don't call RemoveEffect while inside an IParticleEffect's Update() function.
|
|||
|
// Return false from the Update function instead.
|
|||
|
Assert( !m_bUpdatingEffects );
|
|||
|
|
|||
|
// notify listeners
|
|||
|
int nListeners = m_effectListeners.Count();
|
|||
|
for ( int i = 0; i < nListeners; ++i )
|
|||
|
{
|
|||
|
m_effectListeners[ i ]->OnParticleEffectRemoved( pEffect->m_pSim );
|
|||
|
}
|
|||
|
|
|||
|
// Take it out of the leaf system.
|
|||
|
ClientLeafSystem()->RemoveRenderable( pEffect->m_hRenderHandle );
|
|||
|
|
|||
|
int listIndex = pEffect->m_ListIndex;
|
|||
|
if ( pEffect->m_pSim )
|
|||
|
{
|
|||
|
pEffect->m_pSim->NotifyRemove();
|
|||
|
m_Effects.Remove( listIndex );
|
|||
|
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
Assert( listIndex == 0xFFFF );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleMgr::RemoveAllEffects()
|
|||
|
{
|
|||
|
int iNext;
|
|||
|
for ( int i=m_Effects.Head(); i != m_Effects.InvalidIndex(); i = iNext )
|
|||
|
{
|
|||
|
iNext = m_Effects.Next( i );
|
|||
|
RemoveEffect( m_Effects[i] );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleMgr::IncrementFrameCode()
|
|||
|
{
|
|||
|
VPROF( "CParticleMgr::IncrementFrameCode()" );
|
|||
|
|
|||
|
++m_FrameCode;
|
|||
|
if ( m_FrameCode == 0 )
|
|||
|
{
|
|||
|
// Reset all the CParticleEffectBindings..
|
|||
|
FOR_EACH_LL( m_Effects, i )
|
|||
|
{
|
|||
|
m_Effects[i]->m_FrameCode = 0;
|
|||
|
}
|
|||
|
|
|||
|
m_FrameCode = 1;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
// Main rendering loop
|
|||
|
//-----------------------------------------------------------------------------
|
|||
|
void CParticleMgr::Simulate( float flTimeDelta )
|
|||
|
{
|
|||
|
g_nParticlesDrawn = 0;
|
|||
|
|
|||
|
if(!m_pMaterialSystem)
|
|||
|
{
|
|||
|
Assert(false);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Update all the effects.
|
|||
|
UpdateAllEffects( flTimeDelta );
|
|||
|
}
|
|||
|
|
|||
|
void CParticleMgr::PostRender()
|
|||
|
{
|
|||
|
VPROF("CParticleMgr::SimulateUndrawnEffects");
|
|||
|
|
|||
|
// Simulate all effects that weren't drawn (if they have their 'always simulate' flag set).
|
|||
|
FOR_EACH_LL( m_Effects, i )
|
|||
|
{
|
|||
|
CParticleEffectBinding *pEffect = m_Effects[i];
|
|||
|
|
|||
|
// Tell the effect if it was drawn or not.
|
|||
|
pEffect->SetWasDrawnPrevFrame( pEffect->WasDrawn() );
|
|||
|
|
|||
|
// Now that we've rendered, clear this flag so it'll simulate next frame.
|
|||
|
pEffect->SetFlag( CParticleEffectBinding::FLAGS_FIRST_FRAME, false );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleMgr::DrawBeforeViewModelEffects()
|
|||
|
{
|
|||
|
FOR_EACH_LL( m_Effects, i )
|
|||
|
{
|
|||
|
CParticleEffectBinding *pEffect = m_Effects[i];
|
|||
|
|
|||
|
if ( pEffect->GetFlag( CParticleEffectBinding::FLAGS_DRAW_BEFORE_VIEW_MODEL ) )
|
|||
|
{
|
|||
|
Assert( !pEffect->WasDrawn() );
|
|||
|
pEffect->DrawModel( 1 );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleMgr::UpdateAllEffects( float flTimeDelta )
|
|||
|
{
|
|||
|
m_bUpdatingEffects = true;
|
|||
|
|
|||
|
if( flTimeDelta > 0.1f )
|
|||
|
flTimeDelta = 0.1f;
|
|||
|
|
|||
|
FOR_EACH_LL( m_Effects, iEffect )
|
|||
|
{
|
|||
|
CParticleEffectBinding *pEffect = m_Effects[iEffect];
|
|||
|
|
|||
|
// Don't update this effect if it will be removed.
|
|||
|
if( pEffect->GetRemoveFlag() )
|
|||
|
continue;
|
|||
|
|
|||
|
// If this is a new effect, then update its bbox so it goes in the
|
|||
|
// right leaves (if it has particles).
|
|||
|
int bFirstUpdate = pEffect->GetNeedsBBoxUpdate();
|
|||
|
if ( bFirstUpdate )
|
|||
|
{
|
|||
|
// If the effect already disabled auto-updating of the bbox, then it should have
|
|||
|
// set the bbox by now and we can ignore this responsibility here.
|
|||
|
if ( !pEffect->GetAutoUpdateBBox() || pEffect->RecalculateBoundingBox() )
|
|||
|
{
|
|||
|
pEffect->SetNeedsBBoxUpdate( false );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// This flag will get set to true if the effect is drawn through the leaf system.
|
|||
|
pEffect->SetDrawn( false );
|
|||
|
|
|||
|
// Update the effect.
|
|||
|
pEffect->m_pSim->Update( flTimeDelta );
|
|||
|
|
|||
|
if ( pEffect->GetFirstFrameFlag() )
|
|||
|
pEffect->SetFirstFrameFlag( false );
|
|||
|
else
|
|||
|
pEffect->SimulateParticles( flTimeDelta );
|
|||
|
|
|||
|
// Update its position in the leaf system if its bbox changed.
|
|||
|
pEffect->DetectChanges();
|
|||
|
}
|
|||
|
|
|||
|
m_bUpdatingEffects = false;
|
|||
|
|
|||
|
// Remove any effects that were flagged to be removed.
|
|||
|
int iNext;
|
|||
|
for ( int i=m_Effects.Head(); i != m_Effects.InvalidIndex(); i=iNext )
|
|||
|
{
|
|||
|
iNext = m_Effects.Next( i );
|
|||
|
CParticleEffectBinding *pEffect = m_Effects[i];
|
|||
|
|
|||
|
if( pEffect->GetRemoveFlag() )
|
|||
|
{
|
|||
|
RemoveEffect( pEffect );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
CParticleSubTextureGroup* CParticleMgr::FindOrAddSubTextureGroup( IMaterial *pPageMaterial )
|
|||
|
{
|
|||
|
for ( int i=0; i < m_SubTextureGroups.Count(); i++ )
|
|||
|
{
|
|||
|
if ( m_SubTextureGroups[i]->m_pPageMaterial == pPageMaterial )
|
|||
|
return m_SubTextureGroups[i];
|
|||
|
}
|
|||
|
|
|||
|
CParticleSubTextureGroup *pGroup = new CParticleSubTextureGroup;
|
|||
|
m_SubTextureGroups.AddToTail( pGroup );
|
|||
|
pGroup->m_pPageMaterial = pPageMaterial;
|
|||
|
|
|||
|
return pGroup;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
PMaterialHandle CParticleMgr::GetPMaterial( const char *pMaterialName )
|
|||
|
{
|
|||
|
if( !m_pMaterialSystem )
|
|||
|
{
|
|||
|
Assert(false);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
int hMat = m_SubTextures.Find( pMaterialName );
|
|||
|
if ( hMat == m_SubTextures.InvalidIndex() )
|
|||
|
{
|
|||
|
IMaterial *pIMaterial = m_pMaterialSystem->FindMaterial( pMaterialName, TEXTURE_GROUP_PARTICLE );
|
|||
|
if ( pIMaterial )
|
|||
|
{
|
|||
|
m_pMaterialSystem->Bind( pIMaterial, this );
|
|||
|
|
|||
|
hMat = m_SubTextures.Insert( pMaterialName );
|
|||
|
CParticleSubTexture *pSubTexture = new CParticleSubTexture;
|
|||
|
m_SubTextures[hMat] = pSubTexture;
|
|||
|
pSubTexture->m_pMaterial = pIMaterial;
|
|||
|
|
|||
|
// See if it's got a group name. If not, make a group with a special name.
|
|||
|
IMaterial *pPageMaterial = pIMaterial->GetMaterialPage();
|
|||
|
if ( pIMaterial->InMaterialPage() && pPageMaterial )
|
|||
|
{
|
|||
|
float flOffset[2], flScale[2];
|
|||
|
pIMaterial->GetMaterialOffset( flOffset );
|
|||
|
pIMaterial->GetMaterialScale( flScale );
|
|||
|
|
|||
|
pSubTexture->m_tCoordMins[0] = (0*flScale[0] + flOffset[0]) * pPageMaterial->GetMappingWidth();
|
|||
|
pSubTexture->m_tCoordMaxs[0] = (1*flScale[0] + flOffset[0]) * pPageMaterial->GetMappingWidth();
|
|||
|
|
|||
|
pSubTexture->m_tCoordMins[1] = (0*flScale[1] + flOffset[1]) * pPageMaterial->GetMappingHeight();
|
|||
|
pSubTexture->m_tCoordMaxs[1] = (1*flScale[1] + flOffset[1]) * pPageMaterial->GetMappingHeight();
|
|||
|
|
|||
|
pSubTexture->m_pGroup = FindOrAddSubTextureGroup( pPageMaterial );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Ok, this material isn't part of a group. Give it its own subtexture group.
|
|||
|
pSubTexture->m_pGroup = &pSubTexture->m_DefaultGroup;
|
|||
|
pSubTexture->m_DefaultGroup.m_pPageMaterial = pIMaterial;
|
|||
|
pPageMaterial = pIMaterial; // For tcoord scaling.
|
|||
|
|
|||
|
pSubTexture->m_tCoordMins[0] = pSubTexture->m_tCoordMins[1] = 0;
|
|||
|
pSubTexture->m_tCoordMaxs[0] = pIMaterial->GetMappingWidth();
|
|||
|
pSubTexture->m_tCoordMaxs[1] = pIMaterial->GetMappingHeight();
|
|||
|
}
|
|||
|
|
|||
|
// Rescale the texture coordinates.
|
|||
|
pSubTexture->m_tCoordMins[0] = (pSubTexture->m_tCoordMins[0] + 0.5f) / pPageMaterial->GetMappingWidth();
|
|||
|
pSubTexture->m_tCoordMins[1] = (pSubTexture->m_tCoordMins[1] + 0.5f) / pPageMaterial->GetMappingHeight();
|
|||
|
pSubTexture->m_tCoordMaxs[0] = (pSubTexture->m_tCoordMaxs[0] - 0.5f) / pPageMaterial->GetMappingWidth();
|
|||
|
pSubTexture->m_tCoordMaxs[1] = (pSubTexture->m_tCoordMaxs[1] - 0.5f) / pPageMaterial->GetMappingHeight();
|
|||
|
|
|||
|
return pSubTexture;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
return m_SubTextures[hMat];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
IMaterial* CParticleMgr::PMaterialToIMaterial( PMaterialHandle hMaterial ) const
|
|||
|
{
|
|||
|
if ( hMaterial )
|
|||
|
return hMaterial->m_pMaterial;
|
|||
|
else
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleMgr::GetDirectionalLightInfo( CParticleLightInfo &info ) const
|
|||
|
{
|
|||
|
info = m_DirectionalLight;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void CParticleMgr::SetDirectionalLightInfo( const CParticleLightInfo &info )
|
|||
|
{
|
|||
|
m_DirectionalLight = info;
|
|||
|
}
|
|||
|
|
|||
|
#ifndef _RETAIL
|
|||
|
void CParticleMgr::SpewInfo( bool bDetail )
|
|||
|
{
|
|||
|
DevMsg( "Particle Effect Systems:\n" );
|
|||
|
FOR_EACH_LL( m_Effects, i )
|
|||
|
{
|
|||
|
const char *pEffectName = m_Effects[i]->m_pSim->GetEffectName();
|
|||
|
DevMsg( "%3d: NumActive: %3d, AutoBBox: %3s \"%s\" \n", i, m_Effects[i]->m_nActiveParticles, m_Effects[i]->GetAutoUpdateBBox() ? "on" : "off", pEffectName );
|
|||
|
}
|
|||
|
}
|
|||
|
CON_COMMAND( cl_particles_dump_effects, "" )
|
|||
|
{
|
|||
|
ParticleMgr()->SpewInfo( true );
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
// ------------------------------------------------------------------------------------ //
|
|||
|
// ------------------------------------------------------------------------------------ //
|
|||
|
float Helper_GetTime()
|
|||
|
{
|
|||
|
#if defined( PARTICLEPROTOTYPE_APP )
|
|||
|
static bool bStarted = false;
|
|||
|
static CCycleCount startTimer;
|
|||
|
if( !bStarted )
|
|||
|
{
|
|||
|
bStarted = true;
|
|||
|
startTimer.Sample();
|
|||
|
}
|
|||
|
|
|||
|
CCycleCount curCount;
|
|||
|
curCount.Sample();
|
|||
|
|
|||
|
CCycleCount elapsed;
|
|||
|
CCycleCount::Sub( curCount, startTimer, elapsed );
|
|||
|
|
|||
|
return (float)elapsed.GetSeconds();
|
|||
|
#else
|
|||
|
return gpGlobals->curtime;
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
float Helper_RandomFloat( float minVal, float maxVal )
|
|||
|
{
|
|||
|
#if defined( PARTICLEPROTOTYPE_APP )
|
|||
|
return Lerp( (float)rand() / RAND_MAX, minVal, maxVal );
|
|||
|
#else
|
|||
|
return random->RandomFloat( minVal, maxVal );
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
int Helper_RandomInt( int minVal, int maxVal )
|
|||
|
{
|
|||
|
#if defined( PARTICLEPROTOTYPE_APP )
|
|||
|
return minVal + (rand() * (maxVal - minVal)) / RAND_MAX;
|
|||
|
#else
|
|||
|
return random->RandomInt( minVal, maxVal );
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
float Helper_GetFrameTime()
|
|||
|
{
|
|||
|
#if defined( PARTICLEPROTOTYPE_APP )
|
|||
|
extern float g_ParticleAppFrameTime;
|
|||
|
return g_ParticleAppFrameTime;
|
|||
|
#else
|
|||
|
return gpGlobals->frametime;
|
|||
|
#endif
|
|||
|
}
|