2025-05-17 14:15:00 -04:00
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
2008-09-15 01:00:17 -05:00
//
// 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
}