2021-07-24 21:11:47 -07:00

4862 lines
153 KiB
C++
Raw Permalink Blame History

//===== Copyright <20> 1996-2007, Valve Corporation, All rights reserved. ======//
//
// Purpose: particle system code
//
//===========================================================================//
#include "tier0/platform.h"
#include "particles/particles.h"
#include "bitmap/psheet.h"
#include "filesystem.h"
#include "tier2/tier2.h"
#include "tier2/fileutils.h"
#include "tier1/utlbuffer.h"
#include "tier1/UtlStringMap.h"
#include "tier1/strtools.h"
#include "dmxloader/dmxloader.h"
#include "materialsystem/imaterial.h"
#include "materialsystem/imaterialvar.h"
#include "materialsystem/itexture.h"
#include "materialsystem/imesh.h"
#include "tier0/vprof.h"
#include "tier1/keyvalues.h"
#include "tier1/lzmaDecoder.h"
#include "random_floats.h"
#include "vtf/vtf.h"
#include "studio.h"
#include "particles_internal.h"
#include "ivrenderview.h"
#include "materialsystem/imaterialsystem.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// rename table from the great rename
static char *s_RemapOperatorNameTable[]={
"alpha_fade", "Alpha Fade and Decay",
"alpha_fade_in_random", "Alpha Fade In Random",
"alpha_fade_out_random", "Alpha Fade Out Random",
"basic_movement", "Movement Basic",
"color_fade", "Color Fade",
"controlpoint_light", "Color Light From Control Point",
"Dampen Movement Relative to Control Point", "Movement Dampen Relative to Control Point",
"Distance Between Control Points Scale", "Remap Distance Between Two Control Points to Scalar",
"Distance to Control Points Scale", "Remap Distance to Control Point to Scalar",
"lifespan_decay", "Lifespan Decay",
"lock to bone", "Movement Lock to Bone",
"postion_lock_to_controlpoint", "Movement Lock to Control Point",
"maintain position along path", "Movement Maintain Position Along Path",
"Match Particle Velocities", "Movement Match Particle Velocities",
"Max Velocity", "Movement Max Velocity",
"noise", "Noise Scalar",
"vector noise", "Noise Vector",
"oscillate_scalar", "Oscillate Scalar",
"oscillate_vector", "Oscillate Vector",
"Orient Rotation to 2D Direction", "Rotation Orient to 2D Direction",
"radius_scale", "Radius Scale",
"Random Cull", "Cull Random",
"remap_scalar", "Remap Scalar",
"rotation_movement", "Rotation Basic",
"rotation_spin", "Rotation Spin Roll",
"rotation_spin yaw", "Rotation Spin Yaw",
"alpha_random", "Alpha Random",
"color_random", "Color Random",
"create from parent particles", "Position From Parent Particles",
"Create In Hierarchy", "Position In CP Hierarchy",
"random position along path", "Position Along Path Random",
"random position on model", "Position on Model Random",
"sequential position along path", "Position Along Path Sequential",
"position_offset_random", "Position Modify Offset Random",
"position_warp_random", "Position Modify Warp Random",
"position_within_box", "Position Within Box Random",
"position_within_sphere", "Position Within Sphere Random",
"Inherit Velocity", "Velocity Inherit from Control Point",
"Initial Repulsion Velocity", "Velocity Repulse from World",
"Initial Velocity Noise", "Velocity Noise",
"Initial Scalar Noise", "Remap Noise to Scalar",
"Lifespan from distance to world", "Lifetime from Time to Impact",
"Pre-Age Noise", "Lifetime Pre-Age Noise",
"lifetime_random", "Lifetime Random",
"radius_random", "Radius Random",
"random yaw", "Rotation Yaw Random",
"Randomly Flip Yaw", "Rotation Yaw Flip Random",
"rotation_random", "Rotation Random",
"rotation_speed_random", "Rotation Speed Random",
"sequence_random", "Sequence Random",
"second_sequence_random", "Sequence Two Random",
"trail_length_random", "Trail Length Random",
"velocity_random", "Velocity Random",
};
static char const *RemapOperatorName( char const *pOpName )
{
for( int i = 0 ; i < ARRAYSIZE( s_RemapOperatorNameTable ) ; i += 2 )
{
if ( Q_stricmp( pOpName, s_RemapOperatorNameTable[i] ) == 0 )
{
return s_RemapOperatorNameTable[i + 1 ];
}
}
return pOpName;
}
//-----------------------------------------------------------------------------
// Default implementation of particle system mgr
//-----------------------------------------------------------------------------
static CParticleSystemMgr s_ParticleSystemMgr;
CParticleSystemMgr *g_pParticleSystemMgr = &s_ParticleSystemMgr;
CParticleSystemMgr::ParticleAttribute_t CParticleSystemMgr::s_AttributeTable[ MAX_PARTICLE_ATTRIBUTES ];
int g_nParticle_Multiplier = 1;
//-----------------------------------------------------------------------------
// Particle dictionary
//-----------------------------------------------------------------------------
class CParticleSystemDictionary
{
public:
~CParticleSystemDictionary();
CParticleSystemDefinition* AddParticleSystem( CDmxElement *pParticleSystem );
int Count() const;
int NameCount() const;
CParticleSystemDefinition* GetParticleSystem( int i );
ParticleSystemHandle_t FindParticleSystemHandle( const char *pName );
ParticleSystemHandle_t FindOrAddParticleSystemHandle( const char *pName );
CParticleSystemDefinition* FindParticleSystem( ParticleSystemHandle_t h );
CParticleSystemDefinition* FindParticleSystem( const char *pName );
CParticleSystemDefinition* FindParticleSystem( const DmObjectId_t &id );
CParticleSystemDefinition* operator[]( int idx )
{
return m_ParticleNameMap[ idx ];
}
private:
typedef CUtlStringMap< CParticleSystemDefinition * > ParticleNameMap_t;
typedef CUtlVector< CParticleSystemDefinition* > ParticleIdMap_t;
void DestroyExistingElement( CDmxElement *pElement );
ParticleNameMap_t m_ParticleNameMap;
ParticleIdMap_t m_ParticleIdMap;
};
//-----------------------------------------------------------------------------
// Destructor
//-----------------------------------------------------------------------------
CParticleSystemDictionary::~CParticleSystemDictionary()
{
int nCount = m_ParticleIdMap.Count();
for ( int i = 0; i < nCount; ++i )
{
delete m_ParticleIdMap[i];
}
}
//-----------------------------------------------------------------------------
// Destroys an existing element, returns if this element should be added to the name list
//-----------------------------------------------------------------------------
void CParticleSystemDictionary::DestroyExistingElement( CDmxElement *pElement )
{
const char *pParticleSystemName = pElement->GetName();
bool bPreventNameBasedLookup = pElement->GetValue<bool>( "preventNameBasedLookup" );
if ( !bPreventNameBasedLookup )
{
if ( m_ParticleNameMap.Defined( pParticleSystemName ) )
{
CParticleSystemDefinition *pDef = m_ParticleNameMap[ pParticleSystemName ];
delete pDef;
m_ParticleNameMap[ pParticleSystemName ] = NULL;
}
return;
}
// Use id based lookup instead
int nCount = m_ParticleIdMap.Count();
const DmObjectId_t& id = pElement->GetId();
for ( int i = 0; i < nCount; ++i )
{
// Was already removed by the name lookup
if ( !IsUniqueIdEqual( m_ParticleIdMap[i]->GetId(), id ) )
continue;
CParticleSystemDefinition *pDef = m_ParticleIdMap[ i ];
m_ParticleIdMap.FastRemove( i );
delete pDef;
break;
}
}
//-----------------------------------------------------------------------------
// Adds a destructor
//-----------------------------------------------------------------------------
CParticleSystemDefinition* CParticleSystemDictionary::AddParticleSystem( CDmxElement *pParticleSystem )
{
if ( Q_stricmp( pParticleSystem->GetTypeString(), "DmeParticleSystemDefinition" ) )
return NULL;
DestroyExistingElement( pParticleSystem );
CParticleSystemDefinition *pDef = new CParticleSystemDefinition;
// Must add the def to the maps before Read() because Read() may create new child particle systems
bool bPreventNameBasedLookup = pParticleSystem->GetValue<bool>( "preventNameBasedLookup" );
if ( !bPreventNameBasedLookup )
{
m_ParticleNameMap[ pParticleSystem->GetName() ] = pDef;
}
else
{
m_ParticleIdMap.AddToTail( pDef );
}
pDef->Read( pParticleSystem );
return pDef;
}
int CParticleSystemDictionary::NameCount() const
{
return m_ParticleNameMap.GetNumStrings();
}
int CParticleSystemDictionary::Count() const
{
return m_ParticleIdMap.Count();
}
CParticleSystemDefinition* CParticleSystemDictionary::GetParticleSystem( int i )
{
return m_ParticleIdMap[i];
}
ParticleSystemHandle_t CParticleSystemDictionary::FindParticleSystemHandle( const char *pName )
{
return m_ParticleNameMap.Find( pName );
}
ParticleSystemHandle_t CParticleSystemDictionary::FindOrAddParticleSystemHandle( const char *pName )
{
int nCount = m_ParticleNameMap.GetNumStrings();
ParticleSystemHandle_t hSystem = m_ParticleNameMap.AddString( pName );
if ( hSystem >= nCount )
{
m_ParticleNameMap[ hSystem ] = NULL;
}
return hSystem;
}
CParticleSystemDefinition* CParticleSystemDictionary::FindParticleSystem( ParticleSystemHandle_t h )
{
if ( h == UTL_INVAL_SYMBOL || h >= m_ParticleNameMap.GetNumStrings() )
return NULL;
return m_ParticleNameMap[ h ];
}
CParticleSystemDefinition* CParticleSystemDictionary::FindParticleSystem( const char *pName )
{
if ( m_ParticleNameMap.Defined( pName ) )
return m_ParticleNameMap[ pName ];
return NULL;
}
CParticleSystemDefinition* CParticleSystemDictionary::FindParticleSystem( const DmObjectId_t &id )
{
int nCount = m_ParticleIdMap.Count();
for ( int i = 0; i < nCount; ++i )
{
if ( IsUniqueIdEqual( m_ParticleIdMap[i]->GetId(), id ) )
return m_ParticleIdMap[i];
}
return NULL;
}
//-----------------------------------------------------------------------------
// For editing, create a faked particle operator definition for children
// The only thing used in here is GetUnpackStructure.
//-----------------------------------------------------------------------------
BEGIN_DMXELEMENT_UNPACK( ParticleChildrenInfo_t )
DMXELEMENT_UNPACK_FIELD( "delay", "0.0", float, m_flDelay )
DMXELEMENT_UNPACK_FIELD( "end cap effect", "0", bool, m_bEndCap )
END_DMXELEMENT_UNPACK( ParticleChildrenInfo_t, s_ChildrenInfoUnpack )
class CChildOperatorDefinition : public IParticleOperatorDefinition
{
public:
virtual const char *GetName() const { Assert(0); return NULL; }
virtual CParticleOperatorInstance *CreateInstance( const DmObjectId_t &id ) const { Assert(0); return NULL; }
// virtual void DestroyInstance( CParticleOperatorInstance *pInstance ) const { Assert(0); }
virtual const DmxElementUnpackStructure_t* GetUnpackStructure() const
{
return s_ChildrenInfoUnpack;
}
virtual ParticleOperatorId_t GetId() const { return OPERATOR_GENERIC; }
virtual uint32 GetFilter() const { return 0; }
virtual bool IsObsolete() const { return false; }
};
static CChildOperatorDefinition s_ChildOperatorDefinition;
//-----------------------------------------------------------------------------
//
// CParticleSystemDefinition
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Unpack structure for CParticleSystemDefinition
//-----------------------------------------------------------------------------
BEGIN_DMXELEMENT_UNPACK( CParticleSystemDefinition )
DMXELEMENT_UNPACK_FIELD( "max_particles", "1000", int, m_nMaxParticles )
DMXELEMENT_UNPACK_FIELD( "initial_particles", "0", int, m_nInitialParticles )
DMXELEMENT_UNPACK_FIELD_UTLSTRING_USERDATA( "material", "vgui/white", m_MaterialName, "vmtPicker" )
DMXELEMENT_UNPACK_FIELD( "bounding_box_min", "-10 -10 -10", Vector, m_BoundingBoxMin )
DMXELEMENT_UNPACK_FIELD( "bounding_box_max", "10 10 10", Vector, m_BoundingBoxMax )
DMXELEMENT_UNPACK_FIELD( "cull_radius", "0", float, m_flCullRadius )
DMXELEMENT_UNPACK_FIELD( "cull_cost", "1", float, m_flCullFillCost )
DMXELEMENT_UNPACK_FIELD( "cull_control_point", "0", int, m_nCullControlPoint )
DMXELEMENT_UNPACK_FIELD_UTLSTRING( "cull_replacement_definition", "", m_CullReplacementName )
DMXELEMENT_UNPACK_FIELD_UTLSTRING( "fallback replacement definition", "", m_FallbackReplacementName )
DMXELEMENT_UNPACK_FIELD( "fallback max count", "-1", int, m_nFallbackMaxCount )
DMXELEMENT_UNPACK_FIELD( "radius", "5", float, m_flConstantRadius )
DMXELEMENT_UNPACK_FIELD( "color", "255 255 255 255", Color, m_ConstantColor )
DMXELEMENT_UNPACK_FIELD( "rotation", "0", float, m_flConstantRotation )
DMXELEMENT_UNPACK_FIELD( "rotation_speed", "0", float, m_flConstantRotationSpeed )
DMXELEMENT_UNPACK_FIELD( "normal", "0 0 1", Vector, m_ConstantNormal )
DMXELEMENT_UNPACK_FIELD_USERDATA( "sequence_number", "0", int, m_nConstantSequenceNumber, "sheetsequencepicker" )
DMXELEMENT_UNPACK_FIELD_USERDATA( "sequence_number 1", "0", int, m_nConstantSequenceNumber1, "sheetsequencepicker_second" )
DMXELEMENT_UNPACK_FIELD( "group id", "0", int, m_nGroupID )
DMXELEMENT_UNPACK_FIELD( "maximum time step", "0.1", float, m_flMaximumTimeStep )
DMXELEMENT_UNPACK_FIELD( "maximum sim tick rate", "0.0", float, m_flMaximumSimTime )
DMXELEMENT_UNPACK_FIELD( "minimum sim tick rate", "0.0", float, m_flMinimumSimTime )
DMXELEMENT_UNPACK_FIELD( "minimum rendered frames", "0", int, m_nMinimumFrames )
DMXELEMENT_UNPACK_FIELD( "control point to disable rendering if it is the camera", "-1", int, m_nSkipRenderControlPoint )
DMXELEMENT_UNPACK_FIELD( "control point to only enable rendering if it is the camera", "-1", int, m_nAllowRenderControlPoint )
DMXELEMENT_UNPACK_FIELD( "maximum draw distance", "100000.0", float, m_flMaxDrawDistance )
DMXELEMENT_UNPACK_FIELD( "time to sleep when not drawn", "8", float, m_flNoDrawTimeToGoToSleep )
DMXELEMENT_UNPACK_FIELD( "Sort particles", "1", bool, m_bShouldSort )
DMXELEMENT_UNPACK_FIELD( "batch particle systems", "0", bool, m_bShouldBatch )
DMXELEMENT_UNPACK_FIELD( "view model effect", "0", bool, m_bViewModelEffect )
DMXELEMENT_UNPACK_FIELD( "screen space effect", "0", bool, m_bScreenSpaceEffect )
DMXELEMENT_UNPACK_FIELD( "draw through leafsystem", "1", bool, m_bDrawThroughLeafSystem )
DMXELEMENT_UNPACK_FIELD( "maximum portal recursion depth", "8", int, m_nMaxRecursionDepth )
DMXELEMENT_UNPACK_FIELD( "aggregation radius", "0", float, m_flAggregateRadius )
DMXELEMENT_UNPACK_FIELD( "minimum free particles to aggregate", "0", int, m_nAggregationMinAvailableParticles )
DMXELEMENT_UNPACK_FIELD( "minimum simulation time step", "0", float, m_flMinimumTimeStep )
DMXELEMENT_UNPACK_FIELD( "minimum CPU level", "0", int, m_nMinCPULevel )
DMXELEMENT_UNPACK_FIELD( "minimum GPU level", "0", int, m_nMinGPULevel )
DMXELEMENT_UNPACK_FIELD( "freeze simulation after time", "1000000000", float, m_flStopSimulationAfterTime )
END_DMXELEMENT_UNPACK( CParticleSystemDefinition, s_pParticleSystemDefinitionUnpack )
//-----------------------------------------------------------------------------
//
// CParticleOperatorDefinition begins here
// A template describing how a particle system will function
//
//-----------------------------------------------------------------------------
void CParticleSystemDefinition::UnlinkAllCollections()
{
while ( m_pFirstCollection )
{
m_pFirstCollection->UnlinkFromDefList();
}
Assert( m_nFallbackCurrentCount == 0 );
}
const char *CParticleSystemDefinition::GetName() const
{
return m_Name;
}
//-----------------------------------------------------------------------------
// Should we always precache this?
//-----------------------------------------------------------------------------
bool CParticleSystemDefinition::ShouldAlwaysPrecache() const
{
return m_bAlwaysPrecache;
}
//-----------------------------------------------------------------------------
// Precache/uncache
//-----------------------------------------------------------------------------
void CParticleSystemDefinition::Precache()
{
if ( m_bIsPrecached )
return;
m_bIsPrecached = true;
#ifndef DEDICATED
if ( !UTIL_IsDedicatedServer() && g_pMaterialSystem )
{
m_Material.Init( MaterialName(), TEXTURE_GROUP_OTHER, true );
if ( m_Material->HasProxy() )
{
Warning( "Material %s used by particle systems cannot use proxies!\n", m_Material->GetName() );
m_Material.Init( "debug/particleerror", TEXTURE_GROUP_OTHER, true );
}
g_pParticleSystemMgr->FindOrLoadSheet( this );
// NOTE: Subtle. This has to be called after HasProxy, which will
// have loaded all material vars. The "queue friendly" version of a material
// doesn't precache material vars
m_Material->GetColorModulation( &m_vecMaterialModulation[0], &m_vecMaterialModulation[1], &m_vecMaterialModulation[2] );
m_vecMaterialModulation[3] = m_Material->GetAlphaModulation();
}
#endif
if ( HasFallback() )
{
CParticleSystemDefinition *pFallback = GetFallbackReplacementDefinition();
if ( pFallback )
{
pFallback->Precache();
}
}
// call the precache method of the renderers in case they need assets
for( int i = 0; i < m_Renderers.Count(); i++ )
{
CParticleOperatorInstance *pOp = m_Renderers[i];
pOp->Precache();
}
int nChildCount = m_Children.Count();
for ( int i = 0; i < nChildCount; ++i )
{
CParticleSystemDefinition *pChild;
if ( m_Children[i].m_bUseNameBasedLookup )
{
pChild = g_pParticleSystemMgr->FindParticleSystem( m_Children[i].m_Name );
}
else
{
pChild = g_pParticleSystemMgr->FindParticleSystem( m_Children[i].m_Id );
}
if ( pChild )
{
pChild->Precache();
}
}
}
void CParticleSystemDefinition::Uncache()
{
if ( !m_bIsPrecached )
return;
m_bIsPrecached = false;
m_Material.Shutdown();
// m_Material.Init( "debug/particleerror", TEXTURE_GROUP_OTHER, true );
// m_vecMaterialModulation.Init( 1.0f, 1.0f, 1.0f, 1.0f );
if ( HasFallback() )
{
CParticleSystemDefinition *pFallback = GetFallbackReplacementDefinition();
if ( pFallback )
{
pFallback->Uncache();
}
}
for( int i = 0; i < m_Renderers.Count(); i++ )
{
CParticleOperatorInstance *pOp = m_Renderers[i];
pOp->Uncache();
}
int nChildCount = m_Children.Count();
for ( int i = 0; i < nChildCount; ++i )
{
CParticleSystemDefinition *pChild;
if ( m_Children[i].m_bUseNameBasedLookup )
{
pChild = g_pParticleSystemMgr->FindParticleSystem( m_Children[i].m_Name );
}
else
{
pChild = g_pParticleSystemMgr->FindParticleSystem( m_Children[i].m_Id );
}
if ( pChild )
{
pChild->Uncache();
}
}
}
//-----------------------------------------------------------------------------
// Has this been precached?
//-----------------------------------------------------------------------------
bool CParticleSystemDefinition::IsPrecached() const
{
return m_bIsPrecached;
}
//-----------------------------------------------------------------------------
// Helper methods to help with unserialization
//-----------------------------------------------------------------------------
void CParticleSystemDefinition::ParseOperators(
const char *pszOpKey, ParticleFunctionType_t nFunctionType,
CDmxElement *pElement,
CUtlVector<CParticleOperatorInstance *> &outList)
{
const CDmxAttribute* pAttribute = pElement->GetAttribute( pszOpKey );
if ( !pAttribute || pAttribute->GetType() != AT_ELEMENT_ARRAY )
return;
const CUtlVector<IParticleOperatorDefinition *> &flist = g_pParticleSystemMgr->GetAvailableParticleOperatorList( nFunctionType );
const CUtlVector< CDmxElement* >& ops = pAttribute->GetArray<CDmxElement*>( );
int nCount = ops.Count();
for ( int i = 0; i < nCount; ++i )
{
const char *pOrigName = ops[i]->GetValueString( "functionName" );
char const *pOpName = RemapOperatorName( pOrigName );
if ( pOpName != pOrigName )
{
pElement->SetValue( "functionName", pOpName );
}
bool bFound = false;
int nFunctionCount = flist.Count();
for( int j = 0; j < nFunctionCount; ++j )
{
if ( Q_stricmp( pOpName, flist[j]->GetName() ) )
continue;
// found it!
bFound = true;
CParticleOperatorInstance *pNewRef = flist[j]->CreateInstance( ops[i]->GetId() );
const DmxElementUnpackStructure_t *pUnpack = flist[j]->GetUnpackStructure();
if ( pUnpack )
{
ops[i]->UnpackIntoStructure( pNewRef, pUnpack );
}
pNewRef->InitParams( this );
pNewRef->CheckForFastPath();
m_nAttributeReadMask |= pNewRef->GetReadAttributes();
m_nControlPointReadMask |= pNewRef->GetReadControlPointMask();
m_nControlPointNonPositionalMask |= pNewRef->GetNonPositionalControlPointMask();
switch( nFunctionType )
{
case FUNCTION_INITIALIZER:
case FUNCTION_EMITTER:
m_nPerParticleInitializedAttributeMask |= pNewRef->GetWrittenAttributes();
Assert( pNewRef->GetReadInitialAttributes() == 0 );
break;
case FUNCTION_OPERATOR:
case FUNCTION_FORCEGENERATOR:
case FUNCTION_CONSTRAINT:
m_nPerParticleUpdatedAttributeMask |= pNewRef->GetWrittenAttributes();
m_nInitialAttributeReadMask |= pNewRef->GetReadInitialAttributes();
break;
case FUNCTION_RENDERER:
m_nPerParticleUpdatedAttributeMask |= pNewRef->GetWrittenAttributes();
m_nInitialAttributeReadMask |= pNewRef->GetReadInitialAttributes();
break;
}
// Special case: Reading particle ID means we're reading the initial particle id
if ( ( pNewRef->GetReadAttributes() | pNewRef->GetReadInitialAttributes() ) & PARTICLE_ATTRIBUTE_PARTICLE_ID_MASK )
{
m_nInitialAttributeReadMask |= PARTICLE_ATTRIBUTE_PARTICLE_ID_MASK;
m_nPerParticleInitializedAttributeMask |= PARTICLE_ATTRIBUTE_PARTICLE_ID_MASK;
}
outList.AddToTail( pNewRef );
break;
}
if ( !bFound )
{
if ( flist.Count() ) // don't warn if no ops of that type defined (server)
Warning( "Didn't find particle function %s\n", pOpName );
}
}
}
void CParticleSystemDefinition::ParseChildren( CDmxElement *pElement )
{
const CUtlVector<CDmxElement*>& children = pElement->GetArray<CDmxElement*>( "children" );
int nCount = children.Count();
for ( int i = 0; i < nCount; ++i )
{
CDmxElement *pChild = children[i]->GetValue<CDmxElement*>( "child" );
if ( !pChild || Q_stricmp( pChild->GetTypeString(), "DmeParticleSystemDefinition" ) )
continue;
int j = m_Children.AddToTail();
children[i]->UnpackIntoStructure( &m_Children[j], s_ChildrenInfoUnpack );
m_Children[j].m_bUseNameBasedLookup = !pChild->GetValue<bool>( "preventNameBasedLookup" );
if ( m_Children[j].m_bUseNameBasedLookup )
{
m_Children[j].m_Name = g_pParticleSystemMgr->FindOrAddParticleSystemIndex( pChild->GetName() );
}
else
{
CopyUniqueId( pChild->GetId(), &m_Children[j].m_Id );
}
// Check to see if this child has been encountered already, and if not, then
// create a new particle definition for this child
g_pParticleSystemMgr->AddParticleSystem( pChild );
}
}
void CParticleSystemDefinition::Read( CDmxElement *pElement )
{
m_Name = pElement->GetName();
CopyUniqueId( pElement->GetId(), &m_Id );
pElement->UnpackIntoStructure( this, s_pParticleSystemDefinitionUnpack );
if ( m_nInitialParticles < 0 )
{
m_nInitialParticles = 0;
}
if ( m_nMaxParticles < 1 )
{
m_nMaxParticles = 1;
}
m_nMaxParticles *= g_nParticle_Multiplier;
m_nMaxParticles = MIN( m_nMaxParticles, MAX_PARTICLES_IN_A_SYSTEM );
if ( m_flCullRadius > 0 )
{
m_nControlPointReadMask |= 1ULL << m_nCullControlPoint;
}
ParseOperators( "renderers", FUNCTION_RENDERER, pElement, m_Renderers );
ParseOperators( "operators", FUNCTION_OPERATOR, pElement, m_Operators );
ParseOperators( "initializers", FUNCTION_INITIALIZER, pElement, m_Initializers );
ParseOperators( "emitters", FUNCTION_EMITTER, pElement, m_Emitters );
ParseChildren( pElement );
ParseOperators( "forces", FUNCTION_FORCEGENERATOR, pElement, m_ForceGenerators );
ParseOperators( "constraints", FUNCTION_CONSTRAINT, pElement, m_Constraints );
SetupContextData();
}
IMaterial *CParticleSystemDefinition::GetMaterial() const
{
// NOTE: This has to be this way to ensure we don't load every freaking material @ startup
Assert( IsPrecached() );
if ( !IsPrecached() )
return NULL;
return (IMaterial *) ( (const IMaterial *) m_Material );
}
CUtlSymbol CParticleSystemDefinition::GetSheetSymbol() const
{
Assert( IsSheetSymbolCached() );
return m_SheetSymbol;
}
void CParticleSystemDefinition::CacheSheetSymbol( CUtlSymbol sheetSymbol )
{
m_SheetSymbol = sheetSymbol;
m_bSheetSymbolCached = true;
}
bool CParticleSystemDefinition::IsSheetSymbolCached() const
{
return m_bSheetSymbolCached;
}
void CParticleSystemDefinition::InvalidateSheetSymbol()
{
m_bSheetSymbolCached = false;
}
//----------------------------------------------------------------------------------
// Returns the particle system fallback
//----------------------------------------------------------------------------------
CParticleSystemDefinition *CParticleSystemDefinition::GetFallbackReplacementDefinition() const
{
if ( HasFallback() )
{
if ( !m_pFallback() )
{
const_cast< CParticleSystemDefinition* >( this )->m_pFallback.Set( g_pParticleSystemMgr->FindParticleSystem( m_FallbackReplacementName ) );
}
return m_pFallback();
}
return NULL;
}
//----------------------------------------------------------------------------------
// Does the particle system use the power of two frame buffer texture (refraction?)
//----------------------------------------------------------------------------------
bool CParticleSystemDefinition::UsesPowerOfTwoFrameBufferTexture()
{
// NOTE: This has to be this way to ensure we don't load every freaking material @ startup
Assert( IsPrecached() );
return g_pMaterialSystem && m_Material && m_Material->NeedsPowerOfTwoFrameBufferTexture( false ); // The false checks if it will ever need the frame buffer, not just this frame
}
//----------------------------------------------------------------------------------
// Does the particle system use the power of two frame buffer texture (refraction?)
//----------------------------------------------------------------------------------
bool CParticleSystemDefinition::UsesFullFrameBufferTexture()
{
// NOTE: This has to be this way to ensure we don't load every freaking material @ startup
Assert( IsPrecached() );
return g_pMaterialSystem && m_Material && m_Material->NeedsFullFrameBufferTexture( false ); // The false checks if it will ever need the frame buffer, not just this frame
}
//-----------------------------------------------------------------------------
// Helper methods to write particle systems
//-----------------------------------------------------------------------------
void CParticleSystemDefinition::WriteOperators( CDmxElement *pElement,
const char *pOpKeyName, const CUtlVector<CParticleOperatorInstance *> &inList )
{
CDmxElementModifyScope modify( pElement );
CDmxAttribute* pAttribute = pElement->AddAttribute( pOpKeyName );
CUtlVector< CDmxElement* >& ops = pAttribute->GetArrayForEdit<CDmxElement*>( );
int nCount = inList.Count();
for ( int i = 0; i < nCount; ++i )
{
CDmxElement *pOperator = CreateDmxElement( "DmeParticleOperator" );
ops.AddToTail( pOperator );
const IParticleOperatorDefinition *pDef = inList[i]->GetDefinition();
pOperator->SetValue( "name", pDef->GetName() );
pOperator->SetValue( "functionName", pDef->GetName() );
const DmxElementUnpackStructure_t *pUnpack = pDef->GetUnpackStructure();
if ( pUnpack )
{
pOperator->AddAttributesFromStructure( inList[i], pUnpack );
}
}
}
void CParticleSystemDefinition::WriteChildren( CDmxElement *pElement )
{
CDmxElementModifyScope modify( pElement );
CDmxAttribute* pAttribute = pElement->AddAttribute( "children" );
CUtlVector< CDmxElement* >& children = pAttribute->GetArrayForEdit<CDmxElement*>( );
int nCount = m_Children.Count();
for ( int i = 0; i < nCount; ++i )
{
CDmxElement *pChildRef = CreateDmxElement( "DmeParticleChild" );
children.AddToTail( pChildRef );
children[i]->AddAttributesFromStructure( &m_Children[i], s_ChildrenInfoUnpack );
CDmxElement *pChildParticleSystem;
if ( m_Children[i].m_bUseNameBasedLookup )
{
pChildParticleSystem = g_pParticleSystemMgr->CreateParticleDmxElement(
g_pParticleSystemMgr->GetParticleSystemNameFromIndex( m_Children[i].m_Name ) );
}
else
{
pChildParticleSystem = g_pParticleSystemMgr->CreateParticleDmxElement( m_Children[i].m_Id );
}
pChildRef->SetValue( "name", pChildParticleSystem->GetName() );
pChildRef->SetValue( "child", pChildParticleSystem );
}
}
CDmxElement *CParticleSystemDefinition::Write()
{
const char *pName = GetName();
CDmxElement *pElement = CreateDmxElement( "DmeParticleSystemDefinition" );
pElement->SetValue( "name", pName );
pElement->AddAttributesFromStructure( this, s_pParticleSystemDefinitionUnpack );
WriteOperators( pElement, "renderers",m_Renderers );
WriteOperators( pElement, "operators", m_Operators );
WriteOperators( pElement, "initializers", m_Initializers );
WriteOperators( pElement, "emitters", m_Emitters );
WriteChildren( pElement );
WriteOperators( pElement, "forces", m_ForceGenerators );
WriteOperators( pElement, "constraints", m_Constraints );
return pElement;
}
void CParticleSystemDefinition::SetupContextData( void )
{
// calculate sizes and offsets for context data
CUtlVector<CParticleOperatorInstance *> *olists[] = {
&m_Operators, &m_Renderers, &m_Initializers, &m_Emitters, &m_ForceGenerators,
&m_Constraints
};
CUtlVector<size_t> *offsetLists[] = {
&m_nOperatorsCtxOffsets, &m_nRenderersCtxOffsets,
&m_nInitializersCtxOffsets, &m_nEmittersCtxOffsets,
&m_nForceGeneratorsCtxOffsets, &m_nConstraintsCtxOffsets,
};
// loop through all operators, fill in offset entries, and calulate total data needed
m_nContextDataSize = 0;
for( int i = 0; i < NELEMS( olists ); i++ )
{
int nCount = olists[i]->Count();
for( int j = 0; j < nCount; j++ )
{
offsetLists[i]->AddToTail( m_nContextDataSize );
m_nContextDataSize += (*olists[i])[j]->GetRequiredContextBytes();
// align context data
m_nContextDataSize = (m_nContextDataSize + 15) & (~0xf );
}
}
}
//-----------------------------------------------------------------------------
// Finds an operator by id
//-----------------------------------------------------------------------------
CUtlVector<CParticleOperatorInstance *> *CParticleSystemDefinition::GetOperatorList( ParticleFunctionType_t type )
{
switch( type )
{
case FUNCTION_EMITTER:
return &m_Emitters;
case FUNCTION_RENDERER:
return &m_Renderers;
case FUNCTION_INITIALIZER:
return &m_Initializers;
case FUNCTION_OPERATOR:
return &m_Operators;
case FUNCTION_FORCEGENERATOR:
return &m_ForceGenerators;
case FUNCTION_CONSTRAINT:
return &m_Constraints;
default:
Assert(0);
return NULL;
}
}
//-----------------------------------------------------------------------------
// Finds an operator by id
//-----------------------------------------------------------------------------
CParticleOperatorInstance *CParticleSystemDefinition::FindOperatorById( ParticleFunctionType_t type, const DmObjectId_t &id )
{
CUtlVector<CParticleOperatorInstance *> *pVec = GetOperatorList( type );
if ( !pVec )
return NULL;
int nCount = pVec->Count();
for ( int i = 0; i < nCount; ++i )
{
if ( IsUniqueIdEqual( id, pVec->Element(i)->GetId() ) )
return pVec->Element(i);
}
return NULL;
}
//-----------------------------------------------------------------------------
// Finds an operator by name (slow!)
//-----------------------------------------------------------------------------
CParticleOperatorInstance *CParticleSystemDefinition::FindOperatorByName( const char *pOperatorName )
{
for ( int i = 0; i < PARTICLE_FUNCTION_COUNT; i++ )
{
CUtlVector<CParticleOperatorInstance *> *pVec = ( i == FUNCTION_CHILDREN ) ? NULL : GetOperatorList( (ParticleFunctionType_t)i );
if ( !pVec )
continue;
int nCount = pVec->Count();
for ( int j = 0; j < nCount; ++j )
{
if ( !Q_stricmp( pOperatorName, pVec->Element(j)->GetDefinition()->GetName() ) )
return pVec->Element(j);
}
}
return NULL;
}
//-----------------------------------------------------------------------------
//
// CParticleOperatorInstance
//
//-----------------------------------------------------------------------------
void CParticleOperatorInstance::InitNewParticles( CParticleCollection *pParticles,
int nFirstParticle, int nParticleCount,
int nAttributeWriteMask, void *pContext ) const
{
if ( !nParticleCount )
return;
if ( nParticleCount < 16 ) // don't bother with vectorizing
// unless enough particles to bother
{
InitNewParticlesScalar( pParticles, nFirstParticle, nParticleCount, nAttributeWriteMask, pContext );
return;
}
int nHead = nFirstParticle & 3;
if ( nHead )
{
// need to init up to 3 particles before we are block aligned
int nHeadCount = MIN( nParticleCount, 4 - nHead );
InitNewParticlesScalar( pParticles, nFirstParticle, nHeadCount, nAttributeWriteMask, pContext );
nParticleCount -= nHeadCount;
nFirstParticle += nHeadCount;
}
// now, we are aligned
int nBlockCount = nParticleCount / 4;
if ( nBlockCount )
{
InitNewParticlesBlock( pParticles, nFirstParticle / 4, nBlockCount, nAttributeWriteMask, pContext );
nParticleCount -= 4 * nBlockCount;
nFirstParticle += 4 * nBlockCount;
}
// do tail
if ( nParticleCount )
{
InitNewParticlesScalar( pParticles, nFirstParticle, nParticleCount, nAttributeWriteMask, pContext );
}
}
//-----------------------------------------------------------------------------
//
// CParticleCollection
//
//-----------------------------------------------------------------------------
//------------------------------------------------------------------------------
// need custom new/delete for alignment for simd
//------------------------------------------------------------------------------
#include "tier0/memdbgoff.h"
void *CParticleCollection::operator new( size_t nSize )
{
return MemAlloc_AllocAligned( nSize, 16 );
}
void* CParticleCollection::operator new( size_t nSize, int nBlockUse, const char *pFileName, int nLine )
{
return MemAlloc_AllocAlignedFileLine( nSize, 16, pFileName, nLine );
}
void CParticleCollection::operator delete(void *pData)
{
if ( pData )
{
MemAlloc_FreeAligned( pData );
}
}
void CParticleCollection::operator delete( void* pData, int nBlockUse, const char *pFileName, int nLine )
{
if ( pData )
{
MemAlloc_FreeAligned( pData );
}
}
void *CWorldCollideContextData::operator new( size_t nSize )
{
return MemAlloc_AllocAligned( nSize, 16 );
}
void* CWorldCollideContextData::operator new( size_t nSize, int nBlockUse, const char *pFileName, int nLine )
{
return MemAlloc_AllocAlignedFileLine( nSize, 16, pFileName, nLine );
}
void CWorldCollideContextData::operator delete(void *pData)
{
if ( pData )
{
MemAlloc_FreeAligned( pData );
}
}
void CWorldCollideContextData::operator delete( void* pData, int nBlockUse, const char *pFileName, int nLine )
{
if ( pData )
{
MemAlloc_FreeAligned( pData );
}
}
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// Constructor, destructor
//-----------------------------------------------------------------------------
CParticleCollection::CParticleCollection( )
{
COMPILE_TIME_ASSERT( ( MAX_RANDOM_FLOATS & ( MAX_RANDOM_FLOATS - 1 ) ) == 0 );
COMPILE_TIME_ASSERT( sizeof( s_pRandomFloats ) / sizeof( float ) >= MAX_RANDOM_FLOATS );
Plat_FastMemset( this, 0, sizeof(CParticleCollection) );
m_flOOMaxDistSqr = 1.0f;
m_flPreviousDt = 0.05f;
m_nParticleFlags = PCFLAGS_FIRST_FRAME;
m_LocalLighting = Color(255, 255, 255, 255);
m_LocalLightingCP = -1;
m_bPendingRestart = false;
m_flTargetDrawTime = 0;
m_bQueuedStartEmission = false;
m_bFrozen = false;
m_pCPInfo = NULL;
m_pCachedParticleBatches = NULL;
m_bTriedLoadingSheet = false;
}
CParticleCollection::~CParticleCollection( void )
{
UnlinkFromDefList();
m_Children.Purge();
if ( m_pParticleMemory )
{
MemAlloc_FreeAligned( m_pParticleMemory );
m_pParticleMemory = NULL;
}
if ( m_pPreviousAttributeMemory )
{
MemAlloc_FreeAligned( m_pPreviousAttributeMemory );
m_pPreviousAttributeMemory = NULL;
}
if ( m_pParticleInitialMemory )
{
MemAlloc_FreeAligned( m_pParticleInitialMemory );
m_pParticleInitialMemory = NULL;
}
if ( m_pConstantMemory )
{
MemAlloc_FreeAligned( m_pConstantMemory );
m_pConstantMemory = NULL;
}
if ( m_pOperatorContextData )
{
MemAlloc_FreeAligned( m_pOperatorContextData );
m_pOperatorContextData = NULL;
}
for( int i = 0 ; i < ARRAYSIZE( m_pCollisionCacheData ) ; i++ )
{
if ( m_pCollisionCacheData[i] )
{
delete m_pCollisionCacheData[i];
m_pCollisionCacheData[i] = NULL;
}
}
if ( m_pCPInfo )
{
delete[] m_pCPInfo;
m_pCPInfo = NULL;
}
if ( m_pCachedParticleBatches )
{
delete m_pCachedParticleBatches;
m_pCachedParticleBatches = NULL;
}
}
//-----------------------------------------------------------------------------
// Initialization
//-----------------------------------------------------------------------------
void CParticleCollection::Init( CParticleSystemDefinition *pDef, float flDelay, int nRandomSeed )
{
m_pDef = pDef;
// Link into def list
LinkIntoDefList();
m_pRenderable = NULL;
InitStorage( pDef );
// Initialize sheet data
m_Sheet.Set( g_pParticleSystemMgr->FindOrLoadSheet( pDef ) );
m_bTriedLoadingSheet = false;
// FIXME: This seed needs to be recorded per instance!
m_bIsScrubbable = ( nRandomSeed != 0 );
if ( m_bIsScrubbable )
{
m_nRandomSeed = nRandomSeed;
}
else
{
m_nRandomSeed = (int)(intp)this;
#ifndef _DEBUG
m_nRandomSeed += Plat_MSTime();
#endif
}
SetAttributeToConstant( PARTICLE_ATTRIBUTE_XYZ, 0.0f, 0.0f, 0.0f );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_PREV_XYZ, 0.0f, 0.0f, 0.0f );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_LIFE_DURATION, 1.0f );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_RADIUS, pDef->m_flConstantRadius );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_ROTATION, pDef->m_flConstantRotation );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_ROTATION_SPEED, pDef->m_flConstantRotationSpeed );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_TINT_RGB,
pDef->m_ConstantColor.r() / 255.0f, pDef->m_ConstantColor.g() / 255.0f,
pDef->m_ConstantColor.b() / 255.0f );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_ALPHA, pDef->m_ConstantColor.a() / 255.0f );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_CREATION_TIME, 0.0f );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER, pDef->m_nConstantSequenceNumber );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER1, pDef->m_nConstantSequenceNumber1 );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_TRAIL_LENGTH, 0.1f );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_PARTICLE_ID, 0 );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_YAW, 0.0f );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_ALPHA2, 1.0f );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_PITCH, 0.0f );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_NORMAL,
pDef->m_ConstantNormal.x, pDef->m_ConstantNormal.y, pDef->m_ConstantNormal.z );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_GLOW_RGB, 1.0f, 1.0f, 1.0f );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_GLOW_ALPHA, 1.0f );
// Offset the child in time
m_flCurTime = -flDelay;
m_fl4CurTime = ReplicateX4( m_flCurTime );
if ( m_pDef->m_nContextDataSize )
{
m_pOperatorContextData = reinterpret_cast<uint8 *>
( MemAlloc_AllocAligned( m_pDef->m_nContextDataSize, 16 ) );
}
m_flNextSleepTime = g_pParticleSystemMgr->GetLastSimulationTime() + pDef->m_flNoDrawTimeToGoToSleep;
m_nControlPointReadMask = pDef->m_nControlPointReadMask;
m_nControlPointNonPositionalMask = pDef->m_nControlPointNonPositionalMask;
// Instance child particle systems
int nChildCount = pDef->m_Children.Count();
for ( int i = 0; i < nChildCount; ++i )
{
if ( nRandomSeed != 0 )
{
nRandomSeed += 129;
}
CParticleCollection *pChild;
if ( pDef->m_Children[i].m_bUseNameBasedLookup )
{
pChild = g_pParticleSystemMgr->CreateParticleCollection( pDef->m_Children[i].m_Name, -m_flCurTime + pDef->m_Children[i].m_flDelay, nRandomSeed );
}
else
{
pChild = g_pParticleSystemMgr->CreateParticleCollection( pDef->m_Children[i].m_Id, -m_flCurTime + pDef->m_Children[i].m_flDelay, nRandomSeed );
}
if ( pChild )
{
pChild->m_pParent = this;
m_Children.AddToTail( pChild );
m_nControlPointReadMask |= pChild->m_nControlPointReadMask;
m_nControlPointNonPositionalMask |= pChild->m_nControlPointNonPositionalMask;
if ( pDef->m_Children[i].m_bEndCap )
{
pChild->m_flCurTime = -FLT_MAX;
}
}
}
if ( !IsValid() )
return;
m_bIsTranslucent = ComputeIsTranslucent();
m_bIsTwoPass = ComputeIsTwoPass();
m_bIsBatchable = ComputeIsBatchable();
m_bIsOrderImportant = ComputeIsOrderImportant();
m_bRunForParentApplyKillList = ComputeRunForParentApplyKillList();
LabelTextureUsage();
m_bAnyUsesPowerOfTwoFrameBufferTexture = ComputeUsesPowerOfTwoFrameBufferTexture();
m_bAnyUsesFullFrameBufferTexture = ComputeUsesFullFrameBufferTexture();
m_bInEndCap = false;
// now, allocate the control point data.
// we will always allocate on extra, so that if someone sets a control point that the particle system doesn't
// use, then there will be a dummy one to write to.
int nHighest;
for( nHighest = 63; nHighest > 0 ; nHighest-- )
{
if ( m_nControlPointReadMask & ( 1ll << nHighest ) )
{
break;
}
}
m_nNumControlPointsAllocated = MIN( MAX_PARTICLE_CONTROL_POINTS, 2 + nHighest );
//Warning( " save %d bytes by only allocating %d cp's\n", ( 64 - m_nNumControlPointsAllocated ) * sizeof( CParticleCPInfo ), m_nNumControlPointsAllocated );
m_pCPInfo = new CParticleCPInfo[ m_nNumControlPointsAllocated];
// align all control point orientations with the global world
Plat_FastMemset( m_pCPInfo, 0, sizeof( CParticleCPInfo ) * m_nNumControlPointsAllocated );
for( int i=0; i < m_nNumControlPointsAllocated; i++ )
{
ControlPoint(i).m_ForwardVector.y = 1.0f;
ControlPoint(i).m_UpVector.z = 1.0f;
ControlPoint(i).m_RightVector.x = 1.0f;
}
// now, init context data
CUtlVector<CParticleOperatorInstance *> *olists[] =
{
&(m_pDef->m_Operators), &(m_pDef->m_Renderers),
&(m_pDef->m_Initializers), &(m_pDef->m_Emitters),
&(m_pDef->m_ForceGenerators),
&(m_pDef->m_Constraints),
};
CUtlVector<size_t> *offsetlists[]=
{
&(m_pDef->m_nOperatorsCtxOffsets), &(m_pDef->m_nRenderersCtxOffsets),
&(m_pDef->m_nInitializersCtxOffsets), &(m_pDef->m_nEmittersCtxOffsets),
&(m_pDef->m_nForceGeneratorsCtxOffsets),
&(m_pDef->m_nConstraintsCtxOffsets),
};
for( int i=0; i<NELEMS( olists ); i++ )
{
int nOperatorCount = olists[i]->Count();
for( int j=0; j < nOperatorCount; j++ )
{
(*olists[i])[j]->InitializeContextData( this, m_pOperatorContextData+ (*offsetlists)[i][j] );
}
}
}
int CParticleCollection::GetCurrentParticleDefCount( CParticleSystemDefinition* pDef )
{
int nDefCount = pDef->m_nFallbackCurrentCount;
CParticleSystemDefinition *pFallback = pDef;
while ( pFallback && pFallback->HasFallback() )
{
pFallback = pFallback->GetFallbackReplacementDefinition();
if ( !pFallback )
{
break;
}
nDefCount += pFallback->m_nFallbackCurrentCount;
}
return nDefCount;
}
bool CParticleCollection::Init( CParticleSystemDefinition *pDef )
{
if ( pDef->GetMinCPULevel() > g_pParticleSystemMgr->GetParticleCPULevel() || pDef->GetMinGPULevel() > g_pParticleSystemMgr->GetParticleGPULevel() )
{
pDef = NULL;
return false;
}
float flFallbackMultiplier = g_pParticleSystemMgr->GetFallbackMultiplier();
float flFallbackBase = g_pParticleSystemMgr->GetFallbackBase();
float flThresholdSimMS = g_pParticleSystemMgr->GetSimFallbackThresholdMs();
int nFallbackCount = GetCurrentParticleDefCount( pDef );
// Determine if we've gone past our maximum allowable time for simulating particles.
// NOTE: If particle simulation starts overlapping client operations, then we'll need to
// make setting and querying of sim duration threadsafe.
float flPreviousSimMS = g_pParticleSystemMgr->GetLastSimulationDuration() * 1000.0f;
if ( flPreviousSimMS > flThresholdSimMS )
{
float flMSOver = flPreviousSimMS - flThresholdSimMS;
float flSimFallbackBaseMultiplier = g_pParticleSystemMgr->GetSimFallbackBaseMultiplier();
// Increase the fallback base by a factor of r_particle_sim_fallback_base_multiplier
// for each millisecond we're over the threshold
flFallbackBase += flMSOver * flSimFallbackBaseMultiplier;
// Uncomment to spew when we're trying to fall back because sim time took too long
//Warning( "Particle sim took too long: %f, threshold %f\n", flPreviousSimMS, flThresholdSimMS );
}
// If our maximum number of simultaneous definitions has been exceeded fallback to the appropriate def
while ( pDef && pDef->HasFallback() &&
( ( nFallbackCount * flFallbackMultiplier ) + flFallbackBase ) >= pDef->m_nFallbackMaxCount )
{
nFallbackCount -= pDef->m_nFallbackCurrentCount;
pDef = pDef->GetFallbackReplacementDefinition();
}
if ( !pDef ) // || !pDef->IsPrecached() )
{
Warning( "Particlelib: Missing precache for particle system type \"%s\"!\n", pDef ? pDef->GetName() : "unknown" );
CParticleSystemDefinition *pErrorDef = g_pParticleSystemMgr->FindParticleSystem( "error" );
if ( pErrorDef )
{
pDef = pErrorDef;
}
}
Init( pDef, 0.0f, 0 );
return IsValid();
}
bool CParticleCollection::Init( const char *pParticleSystemName )
{
if ( !pParticleSystemName )
return false;
CParticleSystemDefinition *pDef = g_pParticleSystemMgr->FindParticleSystem( pParticleSystemName );
if ( !pDef )
{
Warning( "Attempted to create unknown particle system type \"%s\"!\n", pParticleSystemName );
return false;
}
return Init( pDef );
}
bool CParticleCollection::IsFullyValid( void ) const
{
if ( m_pDef.GetObject() == NULL )
return false;
for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext )
{
if ( !p->IsFullyValid() )
return false;
}
return true;
}
bool CParticleCollection::DependsOnSystem( const char *pName ) const
{
if ( m_pDef.GetObject() == NULL )
return false;
if ( m_pDef->m_Name == pName )
return true;
for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext )
{
if ( p->DependsOnSystem(pName) )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// List management for collections sharing the same particle definition
//-----------------------------------------------------------------------------
void CParticleCollection::LinkIntoDefList( )
{
Assert( !m_pPrevDef && !m_pNextDef );
++( m_pDef->m_nFallbackCurrentCount );
m_pPrevDef = NULL;
m_pNextDef = m_pDef->m_pFirstCollection;
m_pDef->m_pFirstCollection = this;
if ( m_pNextDef )
{
m_pNextDef->m_pPrevDef = this;
}
#ifdef _DEBUG
CParticleCollection *pCollection = m_pDef->FirstCollection();
while ( pCollection )
{
Assert( pCollection->m_pDef == m_pDef );
pCollection = pCollection->GetNextCollectionUsingSameDef();
}
#endif
}
void CParticleCollection::UnlinkFromDefList( )
{
if ( !m_pDef )
return;
--( m_pDef->m_nFallbackCurrentCount );
if ( m_pDef->m_pFirstCollection == this )
{
m_pDef->m_pFirstCollection = m_pNextDef;
Assert( !m_pPrevDef );
}
else
{
Assert( m_pPrevDef );
m_pPrevDef->m_pNextDef = m_pNextDef;
}
if ( m_pNextDef )
{
m_pNextDef->m_pPrevDef = m_pPrevDef;
}
m_pNextDef = m_pPrevDef = NULL;
#ifdef _DEBUG
CParticleCollection *pCollection = m_pDef->FirstCollection();
while ( pCollection )
{
Assert( pCollection->m_pDef == m_pDef );
pCollection = pCollection->GetNextCollectionUsingSameDef();
}
#endif
m_pDef = NULL;
}
//-----------------------------------------------------------------------------
// Reset the particle cache if the frame has changed
//-----------------------------------------------------------------------------
void CParticleCollection::ResetParticleCache()
{
if ( m_pCachedParticleBatches )
{
uint32 nCurrentFrame = g_pMaterialSystem->GetCurrentFrameCount();
if ( m_pCachedParticleBatches->m_nLastValidParticleCacheFrame != nCurrentFrame )
{
m_pCachedParticleBatches->ClearBatches();
m_pCachedParticleBatches->m_nLastValidParticleCacheFrame = nCurrentFrame;
}
}
}
//-----------------------------------------------------------------------------
// Get the cached particle batches for this particular particle collection
//-----------------------------------------------------------------------------
CCachedParticleBatches *CParticleCollection::GetCachedParticleBatches()
{
if ( !m_pCachedParticleBatches )
{
m_pCachedParticleBatches = new CCachedParticleBatches();
}
return m_pCachedParticleBatches;
}
//-----------------------------------------------------------------------------
// Particle memory initialization
//-----------------------------------------------------------------------------
void CParticleCollection::InitStorage( CParticleSystemDefinition *pDef )
{
Assert( pDef->m_nMaxParticles < 65536 );
m_nMaxAllowedParticles = MIN( MAX_PARTICLES_IN_A_SYSTEM, pDef->m_nMaxParticles );
m_nAllocatedParticles = 4 + 4 * ( ( m_nMaxAllowedParticles + 3 ) / 4 );
int nConstantMemorySize = 3 * 4 * MAX_PARTICLE_ATTRIBUTES * sizeof(float) + 16;
// Align allocation for constant attributes to 16 byte boundaries
m_pConstantMemory = (uint8 * ) MemAlloc_AllocAligned( nConstantMemorySize, 16 );
m_pConstantAttributes = ( float * ) m_pConstantMemory;
m_nPerParticleInitializedAttributeMask = pDef->m_nPerParticleInitializedAttributeMask;
m_nPerParticleUpdatedAttributeMask = pDef->m_nPerParticleUpdatedAttributeMask;
// Only worry about initial attributes that are per-particle *and* are updated at a later time
// If they aren't updated at a later time, then we can just point the initial + current pointers at the same memory
m_nPerParticleReadInitialAttributeMask = pDef->m_nInitialAttributeReadMask &
( pDef->m_nPerParticleInitializedAttributeMask & pDef->m_nPerParticleUpdatedAttributeMask );
// This is the mask of attributes which are initialized per-particle, but never updated
// *and* where operators want to read initial particle state
int nPerParticleReadConstantAttributeMask = pDef->m_nInitialAttributeReadMask &
( pDef->m_nPerParticleInitializedAttributeMask & ( ~pDef->m_nPerParticleUpdatedAttributeMask ) );
int sz = 0;
int nInitialAttributeSize = 0;
int nPerParticleAttributeMask = m_nPerParticleInitializedAttributeMask | m_nPerParticleUpdatedAttributeMask;
for( int bit = 0; bit < MAX_PARTICLE_ATTRIBUTES; bit++ )
{
int nAttrSize = ( ( 1 << bit ) & ATTRIBUTES_WHICH_ARE_VEC3S_MASK ) ? 3 : 1;
if ( nPerParticleAttributeMask & ( 1 << bit ) )
{
sz += nAttrSize;
}
if ( m_nPerParticleReadInitialAttributeMask & ( 1 << bit ) )
{
nInitialAttributeSize += nAttrSize;
}
}
// Gotta allocate a couple extra floats to account for padding
int nAllocationSize = m_nAllocatedParticles * sz * sizeof(float) + sizeof( FourVectors );
m_pParticleMemory = ( uint8 * ) MemAlloc_AllocAligned( nAllocationSize, 16 );
m_nAttributeMemorySize = nAllocationSize;
Plat_FastMemset( m_pParticleMemory, 0, nAllocationSize );
// Allocate space for the initial attributes
if ( nInitialAttributeSize != 0 )
{
int nInitialAllocationSize = m_nAllocatedParticles * nInitialAttributeSize * sizeof(float) + sizeof( FourVectors );
m_pParticleInitialMemory = ( uint8 * ) MemAlloc_AllocAligned( nInitialAllocationSize, 16 );
Plat_FastMemset( m_pParticleInitialMemory, 0, nInitialAllocationSize );
}
// Align allocation to 16-byte boundaries
float *pMem = ( float* ) m_pParticleMemory;
float *pInitialMem = ( float* )( m_pParticleInitialMemory );
// Point each attribute to memory associated with that attribute
for( int bit = 0; bit < MAX_PARTICLE_ATTRIBUTES; bit++ )
{
int nAttrSize = ( ( 1 << bit ) & ATTRIBUTES_WHICH_ARE_VEC3S_MASK ) ? 3 : 1;
if ( nPerParticleAttributeMask & ( 1 << bit ) )
{
m_ParticleAttributes.m_pAttributes[ bit ] = pMem;
m_ParticleAttributes.m_nFloatStrides[ bit ] = nAttrSize * 4;
pMem += nAttrSize * m_nAllocatedParticles;
}
else
{
m_ParticleAttributes.m_pAttributes[ bit ] = GetConstantAttributeMemory( bit );
m_ParticleAttributes.m_nFloatStrides[ bit ] = 0;
}
// Are we reading
if ( pDef->m_nInitialAttributeReadMask & ( 1 << bit ) )
{
if ( m_nPerParticleReadInitialAttributeMask & ( 1 << bit ) )
{
Assert( pInitialMem );
m_ParticleInitialAttributes.m_pAttributes[ bit ] = pInitialMem;
m_ParticleInitialAttributes.m_nFloatStrides[ bit ] = nAttrSize * 4;
pInitialMem += nAttrSize * m_nAllocatedParticles;
}
else if ( nPerParticleReadConstantAttributeMask & ( 1 << bit ) )
{
m_ParticleInitialAttributes.m_pAttributes[ bit ] = m_ParticleAttributes.m_pAttributes[ bit ];
m_ParticleInitialAttributes.m_nFloatStrides[ bit ] = m_ParticleAttributes.m_nFloatStrides[ bit ];
}
else
{
m_ParticleInitialAttributes.m_pAttributes[ bit ] = GetConstantAttributeMemory( bit );
m_ParticleInitialAttributes.m_nFloatStrides[ bit ] = 0;
}
}
else
{
// Catch errors where code is reading data it didn't request
m_ParticleInitialAttributes.m_pAttributes[ bit ] = NULL;
m_ParticleInitialAttributes.m_nFloatStrides[ bit ] = 0;
}
}
}
//-----------------------------------------------------------------------------
// Returns the particle collection name
//-----------------------------------------------------------------------------
const char *CParticleCollection::GetName() const
{
return m_pDef ? m_pDef->GetName() : "";
}
//-----------------------------------------------------------------------------
// Does the particle system use the frame buffer texture (refraction?)
//-----------------------------------------------------------------------------
bool CParticleCollection::UsesPowerOfTwoFrameBufferTexture( bool bThisFrame ) const
{
if ( ! m_bAnyUsesPowerOfTwoFrameBufferTexture ) // quick out if neither us or our children ever use
{
return false;
}
if ( bThisFrame )
{
return SystemContainsParticlesWithBoolSet( &CParticleCollection::m_bUsesPowerOfTwoFrameBufferTexture );
}
return true;
}
//-----------------------------------------------------------------------------
// Does the particle system use the full frame buffer texture (soft particles)
//-----------------------------------------------------------------------------
bool CParticleCollection::UsesFullFrameBufferTexture( bool bThisFrame ) const
{
if ( ! m_bAnyUsesFullFrameBufferTexture ) // quick out if neither us or our children ever use
{
return false;
}
if ( bThisFrame )
{
return SystemContainsParticlesWithBoolSet( &CParticleCollection::m_bUsesFullFrameBufferTexture );
}
return true;
}
bool CParticleCollection::SystemContainsParticlesWithBoolSet( bool CParticleCollection::*pField ) const
{
if ( m_nActiveParticles && ( this->*pField ) )
return true;
for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext )
{
if ( p->SystemContainsParticlesWithBoolSet( pField ) )
return true;
}
return false;
}
void CParticleCollection::LabelTextureUsage( void )
{
if ( m_pDef )
{
m_bUsesPowerOfTwoFrameBufferTexture = m_pDef->UsesPowerOfTwoFrameBufferTexture();
m_bUsesFullFrameBufferTexture = m_pDef->UsesFullFrameBufferTexture();
}
for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext )
{
p->LabelTextureUsage();
}
}
bool CParticleCollection::ComputeUsesPowerOfTwoFrameBufferTexture()
{
if ( !m_pDef )
return false;
if ( m_pDef->UsesPowerOfTwoFrameBufferTexture() )
return true;
for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext )
{
if ( p->UsesPowerOfTwoFrameBufferTexture( false ) )
return true;
}
return false;
}
bool CParticleCollection::ComputeUsesFullFrameBufferTexture()
{
if ( !m_pDef )
return false;
if ( m_pDef->UsesFullFrameBufferTexture() )
return true;
for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext )
{
if ( p->UsesFullFrameBufferTexture( false ) )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Is the particle system two-pass?
//-----------------------------------------------------------------------------
bool CParticleCollection::ContainsOpaqueCollections()
{
if ( !m_pDef )
return false;
IMaterial *pMaterial = m_pDef->GetMaterial();
if ( pMaterial && ( !m_pDef->GetMaterial()->IsTranslucent() ) )
return true;
for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext )
{
if ( p->ContainsOpaqueCollections( ) )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Is the particle system two-pass?
//-----------------------------------------------------------------------------
bool CParticleCollection::IsTwoPass() const
{
return m_bIsTwoPass;
}
bool CParticleCollection::ComputeIsTwoPass()
{
if ( !ComputeIsTranslucent() )
return false;
return ContainsOpaqueCollections();
}
//-----------------------------------------------------------------------------
// Is the particle system translucent
//-----------------------------------------------------------------------------
bool CParticleCollection::IsTranslucent() const
{
return m_bIsTranslucent;
}
bool CParticleCollection::ComputeIsTranslucent()
{
if ( !m_pDef )
return false;
IMaterial *pMaterial = m_pDef->GetMaterial();
if ( pMaterial && ( m_pDef->GetMaterial()->IsTranslucent() ) )
return true;
for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext )
{
if ( p->IsTranslucent( ) )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Is the particle system batchable
//-----------------------------------------------------------------------------
bool CParticleCollection::IsBatchable() const
{
return m_bIsBatchable;
}
bool CParticleCollection::ComputeIsBatchable()
{
int nRendererCount = GetRendererCount();
for( int i = 0; i < nRendererCount; i++ )
{
if ( !GetRenderer( i )->IsBatchable() )
return false;
}
for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext )
{
if ( !p->IsBatchable() )
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// Is the order of the particles important
//-----------------------------------------------------------------------------
bool CParticleCollection::IsOrderImportant() const
{
return m_bIsOrderImportant;
}
bool CParticleCollection::ComputeIsOrderImportant()
{
int nRendererCount = GetRendererCount();
for( int i = 0; i < nRendererCount; i++ )
{
if ( GetRenderer( i )->IsOrderImportant() )
return true;
}
for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext )
{
if ( p->IsOrderImportant() )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Does this system want to run inside its parent's ApplyKillList?
//-----------------------------------------------------------------------------
bool CParticleCollection::ShouldRunForParentApplyKillList() const
{
return m_bRunForParentApplyKillList;
}
bool CParticleCollection::ComputeRunForParentApplyKillList()
{
// Only run the system during ApplyKillList if an emitter operator wants to run then
// (initializers may then run subsequently, but they won't without an emitter!)
bool bApplyingKillList = true;
int nEmitterCount = m_pDef->m_Emitters.Count();
for( int i = 0; i < nEmitterCount; i++ )
{
if ( m_pDef->m_Emitters[i]->ShouldRun( bApplyingKillList ) )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Renderer iteration
//-----------------------------------------------------------------------------
int CParticleCollection::GetRendererCount() const
{
return IsValid() ? m_pDef->m_Renderers.Count() : 0;
}
CParticleOperatorInstance *CParticleCollection::GetRenderer( int i )
{
return IsValid() ? m_pDef->m_Renderers[i] : NULL;
}
void *CParticleCollection::GetRendererContext( int i )
{
return IsValid() ? m_pOperatorContextData + m_pDef->m_nRenderersCtxOffsets[i] : NULL;
}
//-----------------------------------------------------------------------------
// Visualize operators (for editing/debugging)
//-----------------------------------------------------------------------------
void CParticleCollection::VisualizeOperator( const DmObjectId_t *pOpId )
{
m_pRenderOp = NULL;
if ( !pOpId || !m_pDef )
return;
m_pRenderOp = m_pDef->FindOperatorById( FUNCTION_EMITTER, *pOpId );
if ( !m_pRenderOp )
{
m_pRenderOp = m_pDef->FindOperatorById( FUNCTION_INITIALIZER, *pOpId );
if ( !m_pRenderOp )
{
m_pRenderOp = m_pDef->FindOperatorById( FUNCTION_OPERATOR, *pOpId );
}
}
}
float FadeInOut( float flFadeInStart, float flFadeInEnd, float flFadeOutStart, float flFadeOutEnd, float flCurTime )
{
if ( flFadeInStart > flCurTime ) // started yet?
return 0.0;
if ( ( flFadeOutEnd > 0. ) && ( flFadeOutEnd < flCurTime ) ) // timed out?
return 0.;
// handle out of order cases
flFadeInEnd = MAX( flFadeInEnd, flFadeInStart );
flFadeOutStart = MAX( flFadeOutStart, flFadeInEnd );
flFadeOutEnd = MAX( flFadeOutEnd, flFadeOutStart );
float flStrength = 1.0;
if (
( flFadeInEnd > flCurTime ) &&
( flFadeInEnd > flFadeInStart ) )
flStrength = MIN( flStrength, FLerp( 0, 1, flFadeInStart, flFadeInEnd, flCurTime ) );
if ( ( flCurTime > flFadeOutStart) &&
( flFadeOutEnd > flFadeOutStart) )
flStrength = MIN( flStrength, FLerp( 0, 1, flFadeOutEnd, flFadeOutStart, flCurTime ) );
return flStrength;
}
bool CParticleCollection::CheckIfOperatorShouldRun(
CParticleOperatorInstance const * pOp ,
float *pflCurStrength,
bool bApplyingParentKillList )
{
if ( !pOp->ShouldRun( bApplyingParentKillList ) )
return false;
if ( pOp->m_bStrengthFastPath )
{
*pflCurStrength = 1.0;
return true;
}
if ( pOp->m_nOpEndCapState != -1 )
{
if ( m_bInEndCap != ( pOp->m_nOpEndCapState == 1 ) )
return false;
}
float flTime=m_flCurTime;
if ( pOp->m_nOpTimeOffsetSeed ) // allow per-instance-of-particle-system random phase control for operator strength.
{
float flOffset = RandomFloat( pOp->m_nOpTimeOffsetSeed, pOp->m_flOpTimeOffsetMin, pOp->m_flOpTimeOffsetMax );
flTime += flOffset;
flTime = MAX( 0.0, flTime );
}
if ( pOp->m_nOpTimeScaleSeed && ( flTime > pOp->m_flOpStartFadeInTime ) )
{
float flTimeScalar = 1.0 / MAX( .0001, RandomFloat( pOp->m_nOpTimeScaleSeed, pOp->m_flOpTimeScaleMin, pOp->m_flOpTimeScaleMax ) );
flTime = pOp->m_flOpStartFadeInTime + flTimeScalar * ( flTime - pOp->m_flOpStartFadeInTime );
}
if ( pOp->m_flOpFadeOscillatePeriod > 0.0 )
{
flTime = fmod( m_flCurTime*( 1.0/pOp->m_flOpFadeOscillatePeriod ), 1.0 );
}
float flStrength = FadeInOut( pOp->m_flOpStartFadeInTime, pOp->m_flOpEndFadeInTime,
pOp->m_flOpStartFadeOutTime, pOp->m_flOpEndFadeOutTime,
flTime );
if ( pOp->m_nOpStrengthScaleSeed )
{
float flStrengthMultiplier = RandomFloat( pOp->m_nOpStrengthScaleSeed, pOp->m_flOpStrengthMinScale, pOp->m_flOpStrengthMaxScale );
flStrength *= MAX( 0., flStrength * flStrengthMultiplier );
}
*pflCurStrength = flStrength;
return ( flStrength > 0.0 );
}
void CParticleOperatorInstance::CheckForFastPath( void )
{
// store away whether this operator has any of the operator modulation params set (most ops dont)
if (
( m_flOpStartFadeInTime == 0. ) &&
( m_flOpEndFadeOutTime == 0. ) &&
( m_flOpStartFadeOutTime == 0. ) &&
( m_flOpEndFadeOutTime == 0. ) &&
( m_flOpTimeOffsetMin == 0 ) &&
( m_flOpTimeOffsetMax == 0 ) &&
( m_flOpTimeScaleMin == 1 ) &&
( m_flOpTimeScaleMax == 1 ) &&
( m_flOpStrengthMaxScale == 1 ) &&
( m_flOpStrengthMinScale == 1 ) &&
( m_nOpEndCapState == -1 ) )
{
m_bStrengthFastPath = true;
}
else
{
m_bStrengthFastPath = false;
}
}
bool CParticleOperatorInstance::HasAttribute( CParticleCollection *pParticles, int nAttribute ) const
{
return ( pParticles->m_ParticleAttributes.Stride( nAttribute ) > 0 );
}
KillListItem_t *CParticleOperatorInstance::GetParentKillList( CParticleCollection *pParticles, int &nNumParticlesToKill ) const
{
if ( pParticles->m_pParent )
{
nNumParticlesToKill = pParticles->m_pParent->m_nNumParticlesToKill;
return pParticles->m_pParent->m_pParticleKillList;
}
nNumParticlesToKill = 0;
return NULL;
}
#ifdef NDEBUG
#define CHECKSYSTEM( p ) 0
#else
static void CHECKSYSTEM( CParticleCollection *pParticles )
{
// Assert( pParticles->m_nActiveParticles <= pParticles->m_pDef->m_nMaxParticles );
for ( int i = 0; i < pParticles->m_nActiveParticles; ++i )
{
const float *xyz = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_XYZ, i );
const float *rad = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_RADIUS, i );
const float *xyz_prev = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_PREV_XYZ, i );
Assert( IsFinite( rad[0] ) );
Assert( IsFinite( xyz[0] ) );
Assert( IsFinite( xyz[4] ) );
Assert( IsFinite( xyz[8] ) );
Assert( IsFinite( xyz_prev[0] ) );
Assert( IsFinite( xyz_prev[4] ) );
Assert( IsFinite( xyz_prev[8] ) );
}
}
#endif
void CParticleCollection::RunRestartedEmitters( void )
{
// run all emitters once that want to respond to a restart
if ( m_nParticleFlags & PCFLAGS_FIRST_FRAME ) // in case we aggregated twice before _any_ sim
{
SimulateFirstFrame();
m_nParticleFlags &= ~PCFLAGS_FIRST_FRAME;
}
else
{
UpdatePrevControlPoints( m_flPreviousDt ); // make sure control points are virgin
}
int nEmitterCount = m_pDef->m_Emitters.Count();
for( int i=0; i < nEmitterCount; i++ )
{
int nOldParticleCount = m_nActiveParticles;
float flEmitStrength = 0;
if ( CheckIfOperatorShouldRun( m_pDef->m_Emitters[i], &flEmitStrength ) )
{
uint32 nInittedMask = m_pDef->m_Emitters[i]->Emit(
this, flEmitStrength,
m_pOperatorContextData + m_pDef->m_nEmittersCtxOffsets[i] );
if ( nOldParticleCount != m_nActiveParticles )
{
// init newly emitted particles
InitializeNewParticles( nOldParticleCount, m_nActiveParticles - nOldParticleCount, nInittedMask );
CHECKSYSTEM( this );
}
}
}
for( CParticleCollection *pChild = m_Children.m_pHead; pChild != NULL; pChild = pChild->m_pNext )
pChild->RunRestartedEmitters();
}
// rj: this may not be the correct thing to do, to set all of the children to the same renderable. particles_new may need to set the renderable to themselves instead.
void CParticleCollection::SetRenderable( void *pRenderable )
{
m_pRenderable = pRenderable;
for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext )
{
p->SetRenderable( pRenderable );
}
}
//-----------------------------------------------------------------------------
// Restarts a particle system
//-----------------------------------------------------------------------------
void CParticleCollection::Restart( EParticleRestartMode_t eMode )
{
// Always reset the framecount for tick rates - this needs to be reset so that systems which are framerate dependent get updated frame counts
m_nDrawnFrames = 0;
// if we already have a pending restart, process it now
if ( m_bPendingRestart )
{
RunRestartedEmitters();
m_bPendingRestart = false;
}
if ( eMode == RESTART_RESET_AND_MAKE_SURE_EMITS_HAPPEN )
{
m_bPendingRestart = true;
m_nParticleFlags &= ~PCFLAGS_PREV_CONTROL_POINTS_INITIALIZED;
}
int nEmitterCount = m_pDef->m_Emitters.Count();
for( int i = 0; i < nEmitterCount; i++ )
{
m_pDef->m_Emitters[i]->Restart( this, m_pOperatorContextData + m_pDef->m_nEmittersCtxOffsets[i] );
}
int nInitializerCount = m_pDef->m_Initializers.Count();
for( int i = 0; i < nInitializerCount; i++ )
{
m_pDef->m_Initializers[i]->Restart( this, m_pOperatorContextData + m_pDef->m_nInitializersCtxOffsets[i] );
}
// Update all children
for( CParticleCollection *pChild = m_Children.m_pHead; pChild != NULL; pChild = pChild->m_pNext )
{
// Remove any delays from the time (otherwise we're offset by it oddly)
pChild->Restart( eMode );
}
}
//-----------------------------------------------------------------------------
// Main entry point for rendering
//-----------------------------------------------------------------------------
void CParticleCollection::Render( int nViewRecursionLevel, IMatRenderContext *pRenderContext, const Vector4D &vecDiffuseModulation, bool bTranslucentOnly, void *pCameraObject )
{
if ( !IsValid() )
return;
if ( !m_Sheet() && !m_bTriedLoadingSheet )
{
m_bTriedLoadingSheet = true;
m_Sheet.Set( g_pParticleSystemMgr->FindOrLoadSheet( m_pDef, true ) );
}
m_flNextSleepTime = MAX( m_flNextSleepTime, ( g_pParticleSystemMgr->GetLastSimulationTime() + m_pDef->m_flNoDrawTimeToGoToSleep ));
if ( m_nActiveParticles != 0 )
{
Vector4D vecActualModulation;
Vector4DMultiply( vecDiffuseModulation, m_pDef->m_vecMaterialModulation, vecActualModulation );
IMaterial *pMaterial = m_pDef->GetMaterial();
if ( pMaterial &&
( !bTranslucentOnly || m_pDef->GetMaterial()->IsTranslucent() || ( vecActualModulation[3] != 1.0f ) ) )
{
#if MEASURE_PARTICLE_PERF
double flSTime = Plat_FloatTime();
#endif
int nCount = m_pDef->m_Renderers.Count();
for( int i = 0; i < nCount; i++ )
{
float flStrength;
if ( !CheckIfOperatorShouldRun( m_pDef->m_Renderers[i], &flStrength ) )
continue;
if ( m_pDef->IsScreenSpaceEffect() )
{
pRenderContext->MatrixMode( MATERIAL_VIEW );
pRenderContext->PushMatrix();
pRenderContext->LoadIdentity();
pRenderContext->MatrixMode( MATERIAL_PROJECTION );
pRenderContext->PushMatrix();
pRenderContext->LoadIdentity();
pRenderContext->Ortho( -100, -100, 100, 100, -100, 100 );
m_pDef->m_Renderers[i]->Render(
pRenderContext, this, vecActualModulation, m_pOperatorContextData + m_pDef->m_nRenderersCtxOffsets[i], nViewRecursionLevel );
pRenderContext->MatrixMode( MATERIAL_VIEW );
pRenderContext->PopMatrix();
pRenderContext->MatrixMode( MATERIAL_PROJECTION );
pRenderContext->PopMatrix();
}
else
{
m_pDef->m_Renderers[i]->Render(
pRenderContext, this, vecActualModulation, m_pOperatorContextData + m_pDef->m_nRenderersCtxOffsets[i], nViewRecursionLevel );
}
}
#if MEASURE_PARTICLE_PERF
float flETime = Plat_FloatTime() - flSTime;
m_pDef->m_flUncomittedTotalRenderTime += flETime;
m_pDef->m_flMaxMeasuredRenderTime = MAX( m_pDef->m_flMaxMeasuredRenderTime, flETime );
#endif
}
}
// let children render
for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext )
{
p->Render( nViewRecursionLevel, pRenderContext, vecDiffuseModulation, bTranslucentOnly, pCameraObject );
}
// Visualize specific ops for debugging/editing
if ( m_pRenderOp )
{
m_pRenderOp->Render( this );
}
}
void CParticleCollection::UpdatePrevControlPoints( float dt )
{
m_flPreviousDt = dt;
for(int i=0; i <= m_nHighestCP; i++ )
ControlPoint( i ).m_PrevPosition = ControlPoint( i ).m_Position;
m_nParticleFlags |= PCFLAGS_PREV_CONTROL_POINTS_INITIALIZED;
}
#if MEASURE_PARTICLE_PERF
#if VPROF_LEVEL > 0
#define START_OP float flOpStartTime = Plat_FloatTime(); VPROF_ENTER_SCOPE(pOp->GetDefinition()->GetName())
#else
#define START_OP float flOpStartTime = Plat_FloatTime();
#endif
#if VPROF_LEVEL > 0
#define END_OP if ( 1 ) { \
float flETime = Plat_FloatTime() - flOpStartTime; \
IParticleOperatorDefinition *pDef = (IParticleOperatorDefinition *) pOp->m_pDef; \
pDef->RecordExecutionTime( flETime ); \
} \
VPROF_EXIT_SCOPE()
#else
#define END_OP if ( 1 ) { \
float flETime = Plat_FloatTime() - flOpStartTime; \
IParticleOperatorDefinition *pDef = (IParticleOperatorDefinition *) pOp->m_pDef; \
pDef->RecordExecutionTime( flETime ); \
}
#endif
#else
#define START_OP
#define END_OP
#endif
void CParticleCollection::InitializeNewParticles( int nFirstParticle, int nParticleCount, uint32 nInittedMask, bool bApplyingParentKillList )
{
VPROF_BUDGET( "CParticleCollection::InitializeNewParticles", VPROF_BUDGETGROUP_PARTICLE_SIMULATION );
#ifdef _DEBUG
m_bIsRunningInitializers = true;
#endif
// now, initialize the attributes of all the new particles
int nPerParticleAttributeMask = m_nPerParticleInitializedAttributeMask | m_nPerParticleUpdatedAttributeMask;
int nAttrsLeftToInit = nPerParticleAttributeMask & ~nInittedMask;
int nInitializerCount = m_pDef->m_Initializers.Count();
for ( int i = 0; i < nInitializerCount; i++ )
{
CParticleOperatorInstance *pOp = m_pDef->m_Initializers[i];
int nInitializerAttrMask = pOp->GetWrittenAttributes();
if ( ( ( nInitializerAttrMask & nAttrsLeftToInit ) == 0 ) || pOp->InitMultipleOverride() )
continue;
if ( !pOp->ShouldRun( bApplyingParentKillList ) )
continue;
void *pContext = m_pOperatorContextData + m_pDef->m_nInitializersCtxOffsets[i];
START_OP;
if ( m_bIsScrubbable && !pOp->IsScrubSafe() )
{
for ( int j = 0; j < nParticleCount; ++j )
{
pOp->InitNewParticles( this, nFirstParticle + j, 1, nAttrsLeftToInit, pContext );
}
}
else
{
pOp->InitNewParticles( this, nFirstParticle, nParticleCount, nAttrsLeftToInit, pContext );
}
END_OP;
nAttrsLeftToInit &= ~nInitializerAttrMask;
}
// always run second tier initializers (modifiers) after first tier - this ensures they don't get stomped.
for ( int i = 0; i < nInitializerCount; i++ )
{
int nInitializerAttrMask = m_pDef->m_Initializers[i]->GetWrittenAttributes();
CParticleOperatorInstance *pOp = m_pDef->m_Initializers[i];
if ( !pOp->InitMultipleOverride() )
continue;
if ( !pOp->ShouldRun( bApplyingParentKillList ) )
continue;
void *pContext = m_pOperatorContextData + m_pDef->m_nInitializersCtxOffsets[i];
START_OP;
if ( m_bIsScrubbable && !pOp->IsScrubSafe() )
{
for ( int j = 0; j < nParticleCount; ++j )
{
pOp->InitNewParticles( this, nFirstParticle + j, 1, nAttrsLeftToInit, pContext );
}
}
else
{
pOp->InitNewParticles( this, nFirstParticle, nParticleCount, nAttrsLeftToInit, pContext );
}
END_OP;
nAttrsLeftToInit &= ~nInitializerAttrMask;
}
#ifdef _DEBUG
m_bIsRunningInitializers = false;
#endif
InitParticleAttributes( nFirstParticle, nParticleCount, nAttrsLeftToInit );
CopyInitialAttributeValues( nFirstParticle, nParticleCount );
}
void CParticleCollection::SkipToTime( float t )
{
if ( t > m_flCurTime )
{
UpdatePrevControlPoints( t - m_flCurTime );
m_flCurTime = t;
m_fl4CurTime = ReplicateX4( t );
m_nParticleFlags &= ~PCFLAGS_FIRST_FRAME;
// FIXME: In future, we may have to tell operators, initializers about this too
int nEmitterCount = m_pDef->m_Emitters.Count();
int i;
for( i = 0; i < nEmitterCount; i++ )
{
m_pDef->m_Emitters[i]->SkipToTime( t, this, m_pOperatorContextData + m_pDef->m_nEmittersCtxOffsets[i] );
}
CParticleCollection *pChild;
// Update all children
for( i = 0, pChild = m_Children.m_pHead; pChild != NULL; pChild = pChild->m_pNext, i++ )
{
// Remove any delays from the time (otherwise we're offset by it oddly)
pChild->SkipToTime( t - m_pDef->m_Children[i].m_flDelay );
}
}
}
void CParticleCollection::SimulateFirstFrame( )
{
m_flPrevSimTime = 1.0e23;
m_flDt = 0.0f;
m_nDrawnFrames = 0;
// For the first frame, copy over the initial control points
if ( ( m_nParticleFlags & PCFLAGS_PREV_CONTROL_POINTS_INITIALIZED ) == 0 )
{
UpdatePrevControlPoints( 0.05f );
}
m_nOperatorRandomSampleOffset = 0;
int nCount = m_pDef->m_Operators.Count();
for( int i = 0; i < nCount; i++ )
{
float flStrength = 0;
CParticleOperatorInstance *pOp = m_pDef->m_Operators[i];
if ( pOp->ShouldRunBeforeEmitters() &&
CheckIfOperatorShouldRun( pOp, &flStrength ) )
{
pOp->Operate( this, flStrength, m_pOperatorContextData + m_pDef->m_nOperatorsCtxOffsets[i] );
CHECKSYSTEM( this );
UpdatePrevControlPoints( 0.05f );
}
m_nOperatorRandomSampleOffset += 17;
}
// first, create initial particles
int nNumToCreate = MIN( m_pDef->m_nInitialParticles, m_nMaxAllowedParticles );
if ( nNumToCreate > 0 )
{
SetNActiveParticles( nNumToCreate );
InitializeNewParticles( 0, nNumToCreate, 0 );
CHECKSYSTEM( this );
}
}
void CParticleCollection::EmitAndInit( CParticleCollection *pCollection, bool bApplyingParentKillList ) // static
{
if ( bApplyingParentKillList && !pCollection->ShouldRunForParentApplyKillList() )
return;
int nEmitterCount = pCollection->m_pDef->m_Emitters.Count();
for( int i = 0; i < nEmitterCount; i++ )
{
int nOldParticleCount = pCollection->m_nActiveParticles;
float flEmitStrength = 0;
if ( pCollection->CheckIfOperatorShouldRun( pCollection->m_pDef->m_Emitters[i], &flEmitStrength, bApplyingParentKillList ) )
{
uint32 nInittedMask = pCollection->m_pDef->m_Emitters[i]->Emit(
pCollection, flEmitStrength, pCollection->m_pOperatorContextData + pCollection->m_pDef->m_nEmittersCtxOffsets[i] );
if ( nOldParticleCount != pCollection->m_nActiveParticles )
{
// init newly emitted particles
pCollection->InitializeNewParticles( nOldParticleCount, pCollection->m_nActiveParticles - nOldParticleCount, nInittedMask, bApplyingParentKillList );
CHECKSYSTEM( pCollection );
}
}
}
}
void CParticleCollection::Simulate( float dt )
{
VPROF_BUDGET( "CParticleCollection::Simulate", VPROF_BUDGETGROUP_PARTICLE_SIMULATION );
if ( ( dt < 0.0f ) || ( m_bFrozen ) )
return;
if ( !m_pDef )
return;
// Don't do anything until we've hit t == 0
// This is used for delayed children
if ( m_flCurTime < 0.0f )
{
if ( dt >= 1.0e-22 )
{
m_flCurTime += dt;
m_fl4CurTime = ReplicateX4( m_flCurTime );
UpdatePrevControlPoints( dt );
}
return;
}
// run initializers if necessary (once we hit t == 0)
if ( m_nParticleFlags & PCFLAGS_FIRST_FRAME )
{
SimulateFirstFrame();
m_nParticleFlags &= ~PCFLAGS_FIRST_FRAME;
}
else
{
// if the system has been Reset, we need to copy the control points to prev control points
if ( ! ( m_nParticleFlags & PCFLAGS_PREV_CONTROL_POINTS_INITIALIZED ) )
UpdatePrevControlPoints( m_flPreviousDt );
}
if ( dt < 1.0e-22 )
return;
m_bPendingRestart = false;
#if MEASURE_PARTICLE_PERF
float flStartSimTime = Plat_FloatTime();
#endif
bool bAttachedKillList = false;
if ( ! HasAttachedKillList() )
{
g_pParticleSystemMgr->AttachKillList( this );
bAttachedKillList = true;
}
float flRemainingDt = dt;
float flMaxDT = 0.1; // default
if ( m_pDef->m_flMaximumTimeStep > 0.0 )
flMaxDT = m_pDef->m_flMaximumTimeStep;
// Limit timestep if needed (prevents short lived particles from being created and destroyed before being rendered.
//if ( m_pDef->m_flMaximumSimTime != 0.0 && !m_bHasDrawnOnce )
if ( m_pDef->m_flMaximumSimTime != 0.0 && ( m_nDrawnFrames <= m_pDef->m_nMinimumFrames ) )
{
if ( ( flRemainingDt + m_flCurTime ) > m_pDef->m_flMaximumSimTime )
{
//if delta+current > checkpoint then delta = checkpoint - current
flRemainingDt = m_pDef->m_flMaximumSimTime - m_flCurTime;
flRemainingDt = MAX( m_pDef->m_flMinimumSimTime, flRemainingDt );
}
m_nDrawnFrames += 1;
}
flRemainingDt = MIN( flRemainingDt, 10 * flMaxDT ); // no more than 10 passes ever
m_flTargetDrawTime += flRemainingDt;
if ( ( m_flTargetDrawTime >= m_flPrevSimTime ) && ( m_flTargetDrawTime < m_flCurTime ) )
{
// we can skip simulation
flRemainingDt = 0;
}
float flMinTime = m_pDef->m_flMinimumTimeStep;
bool bSaveOldValuesForInterpolation = false;
while( flRemainingDt > 0.0 )
{
float flDT_ThisStep = flRemainingDt;
if ( flDT_ThisStep > flMaxDT )
{
flDT_ThisStep = flMaxDT;
}
else
{
if ( flMinTime > flDT_ThisStep ) // can't do lerping if its going to take multiple steps?
{
flDT_ThisStep = flMinTime;
bSaveOldValuesForInterpolation = true;
}
}
flRemainingDt -= flDT_ThisStep;
if ( m_flDt )
m_flPreviousDt = m_flDt;
m_flDt = flDT_ThisStep;
m_flPrevSimTime = m_flCurTime;
m_flCurTime += flDT_ThisStep;
m_fl4CurTime = ReplicateX4( m_flCurTime );
// now, if we are oging to interpolate, copy the current values of all attributes away
if ( bSaveOldValuesForInterpolation )
{
// !! speed - we could copy just the active region.
if ( ! m_pPreviousAttributeMemory )
{
m_pPreviousAttributeMemory = ( uint8 * ) MemAlloc_AllocAligned( m_nAttributeMemorySize, 16 );
memset( m_pPreviousAttributeMemory, 0, m_nAttributeMemorySize );
// set up the pointers
m_PreviousFrameAttributes = m_ParticleAttributes;
for( int i = 0; i < MAX_PARTICLE_ATTRIBUTES; i++ )
{
if ( m_ParticleAttributes.Stride( i ) )
{
m_PreviousFrameAttributes.m_pAttributes[i] = ( float * )
( GetPrevAttributeMemory() +
( m_ParticleAttributes.ByteAddress( i ) - GetAttributeMemory() ) );
}
}
}
CopyParticleAttributesToPreviousAttributes();
}
#ifdef _DEBUG
m_bIsRunningOperators = true;
#endif
m_nOperatorRandomSampleOffset = 0;
int nCount = m_pDef->m_Operators.Count();
for( int i = 0; i < nCount; i++ )
{
float flStrength;
CParticleOperatorInstance *pOp = m_pDef->m_Operators[i];
if ( pOp->ShouldRunBeforeEmitters() &&
CheckIfOperatorShouldRun( pOp, &flStrength ) )
{
START_OP;
pOp->Operate( this, flStrength, m_pOperatorContextData + m_pDef->m_nOperatorsCtxOffsets[i] );
END_OP;
CHECKSYSTEM( this );
if ( m_nNumParticlesToKill )
{
ApplyKillList();
}
m_nOperatorRandomSampleOffset += 17;
}
}
#ifdef _DEBUG
m_bIsRunningOperators = false;
#endif
// Run emitters and initializers:
EmitAndInit( this );
#ifdef _DEBUG
m_bIsRunningOperators = true;
#endif
m_nOperatorRandomSampleOffset = 0;
nCount = m_pDef->m_Operators.Count();
if ( m_nActiveParticles )
for( int i = 0; i < nCount; i++ )
{
float flStrength;
CParticleOperatorInstance *pOp = m_pDef->m_Operators[i];
if ( (! pOp->ShouldRunBeforeEmitters() ) &&
CheckIfOperatorShouldRun( pOp, &flStrength ) )
{
START_OP;
pOp->Operate( this, flStrength, m_pOperatorContextData + m_pDef->m_nOperatorsCtxOffsets[i] );
END_OP;
CHECKSYSTEM( this );
if ( m_nNumParticlesToKill )
{
ApplyKillList();
if ( ! m_nActiveParticles )
break; // don't run any more operators
}
m_nOperatorRandomSampleOffset += 17;
}
}
#ifdef _DEBUG
m_bIsRunningOperators = false;
#endif
nCount = m_pDef->m_Renderers.Count();
for( int i = 0; i < nCount; i++ )
{
CParticleOperatorInstance *pOp = m_pDef->m_Renderers[i];
START_OP;
pOp->PostSimulate( this, m_pOperatorContextData + m_pDef->m_nRenderersCtxOffsets[i] );
END_OP;
}
}
#if MEASURE_PARTICLE_PERF
m_pDef->m_nMaximumActiveParticles = MAX( m_pDef->m_nMaximumActiveParticles, m_nActiveParticles );
float flETime = Plat_FloatTime() - flStartSimTime;
m_pDef->m_flUncomittedTotalSimTime += flETime;
m_pDef->m_flMaxMeasuredSimTime = MAX( m_pDef->m_flMaxMeasuredSimTime, flETime );
#endif
// let children simulate
for( CParticleCollection *i = m_Children.m_pHead; i; i = i->m_pNext )
{
LoanKillListTo( i ); // re-use the allocated kill list for the children
i->Simulate( dt );
i->m_pParticleKillList = NULL;
}
if ( bAttachedKillList )
g_pParticleSystemMgr->DetachKillList( this );
UpdatePrevControlPoints( dt );
// Bloat the bounding box by bounds around the control point
BloatBoundsUsingControlPoint();
// check for freezing
if ( m_pDef->m_flStopSimulationAfterTime < m_flCurTime )
{
m_bFrozen = true;
}
// FIXME: Is there a way of doing this iteratively?
// RecomputeBounds();
}
//-----------------------------------------------------------------------------
// Copies the constant attributes into the per-particle attributes
//-----------------------------------------------------------------------------
void CParticleCollection::InitParticleAttributes( int nStartParticle, int nNumParticles, int nAttrsLeftToInit )
{
if ( nAttrsLeftToInit == 0 )
return;
// !! speed!! do sse init here
for( int i = nStartParticle; i < nStartParticle + nNumParticles; i++ )
{
for ( int nAttr = 0; nAttr < MAX_PARTICLE_ATTRIBUTES; ++nAttr )
{
if ( ( nAttrsLeftToInit & ( 1 << nAttr ) ) == 0 )
continue;
float *pAttrData = GetFloatAttributePtrForWrite( nAttr, i );
// Special case for particle id
if ( nAttr == PARTICLE_ATTRIBUTE_PARTICLE_ID )
{
*( (int*)pAttrData ) = ( m_nRandomSeed + m_nUniqueParticleId ) & RANDOM_FLOAT_MASK;
m_nUniqueParticleId++;
continue;
}
// Special case for the creation time mask
if ( nAttr == PARTICLE_ATTRIBUTE_CREATION_TIME )
{
*pAttrData = m_flCurTime;
continue;
}
// If this assertion fails, it means we're writing into constant memory, which is a nono
Assert( m_ParticleAttributes.Stride( nAttr ) != 0 );
float *pConstantAttr = GetConstantAttributeMemory( nAttr );
*pAttrData = *pConstantAttr;
if ( m_ParticleAttributes.Stride( nAttr ) == 12 )
{
pAttrData[4] = pConstantAttr[4];
pAttrData[8] = pConstantAttr[8];
}
}
}
}
void CParticleCollection::CopyInitialAttributeValues( int nStartParticle, int nNumParticles )
{
// if doinginterpolated sim, update the previous values to be the current ones, otherwise we may interpolate between
// old values based upon a previous particle that was in this slot.
if ( m_nPerParticleReadInitialAttributeMask == 0 )
return;
// FIXME: Do SSE copy here
for( int i = nStartParticle; i < nStartParticle + nNumParticles; i++ )
{
for ( int nAttr = 0; nAttr < MAX_PARTICLE_ATTRIBUTES; ++nAttr )
{
if ( m_nPerParticleReadInitialAttributeMask & (1 << nAttr) )
{
const float *pSrcAttribute = GetFloatAttributePtr( nAttr, i );
float *pDestAttribute = GetInitialFloatAttributePtrForWrite( nAttr, i );
Assert( m_ParticleInitialAttributes.Stride( nAttr ) != 0 );
Assert( m_ParticleAttributes.Stride( nAttr ) == m_ParticleInitialAttributes.Stride( nAttr ) );
*pDestAttribute = *pSrcAttribute;
if ( m_ParticleAttributes.Stride( nAttr ) == 12 )
{
pDestAttribute[4] = pSrcAttribute[4];
pDestAttribute[8] = pSrcAttribute[8];
}
}
}
}
}
void CParticleCollection::CopyParticleAttributesToPreviousAttributes( void ) const
{
for( int i = 0; i < MAX_PARTICLE_ATTRIBUTES; i++ )
{
if ( m_PreviousFrameAttributes.Stride( i ) )
{
int nSz = m_nPaddedActiveParticles * 4 * m_PreviousFrameAttributes.Stride( i );
memcpy( m_PreviousFrameAttributes.Address( i ), m_ParticleAttributes.Address( i ), nSz );
}
}
}
//-----------------------------------------------------------------------------e
// Computes a random vector inside a sphere
//-----------------------------------------------------------------------------
float CParticleCollection::RandomVectorInUnitSphere( int nRandomSampleId, Vector *pVector )
{
// Guarantee uniform random distribution within a sphere
// Graphics gems III contains this algorithm ("Nonuniform random point sets via warping")
float u = RandomFloat( nRandomSampleId, 0.0001f, 1.0f );
float v = RandomFloat( nRandomSampleId+1, 0.0001f, 1.0f );
float w = RandomFloat( nRandomSampleId+2, 0.0001f, 1.0f );
float flPhi = acos( 1 - 2 * u );
float flTheta = 2 * M_PI * v;
float flRadius = powf( w, 1.0f / 3.0f );
float flSinPhi, flCosPhi;
float flSinTheta, flCosTheta;
SinCos( flPhi, &flSinPhi, &flCosPhi );
SinCos( flTheta, &flSinTheta, &flCosTheta );
pVector->x = flRadius * flSinPhi * flCosTheta;
pVector->y = flRadius * flSinPhi * flSinTheta;
pVector->z = flRadius * flCosPhi;
return flRadius;
}
//-----------------------------------------------------------------------------
// Used to retrieve the position of a control point
// somewhere between m_flCurTime and m_flCurTime - m_fPreviousDT
//-----------------------------------------------------------------------------
void CParticleCollection::GetControlPointAtTime( int nControlPoint, float flTime, Vector *pControlPoint )
{
Assert( m_pDef->ReadsControlPoint( nControlPoint ) );
// if ( nControlPoint > GetHighestControlPoint() )
// {
// DevWarning(2, "Warning : Particle system (%s) using unassigned ControlPoint %d!\n", GetName(), nControlPoint );
// }
float flPrevTime = m_flCurTime - m_flDt;
// While this assert is valid, the below if statement is a good enough band-aid to make it so that
// particles aren't appearing a weird locations.
// Assert( flTime + 0.5f >= flPrevTime && flTime <= m_flCurTime );
if ( flTime < flPrevTime )
{
flTime = flPrevTime;
}
float deltaTime = ( flTime - flPrevTime );
if ( m_flDt == 0.0f || ( deltaTime == 0.0f ) )
{
VectorCopy( ControlPoint( nControlPoint ).m_Position, *pControlPoint );
return;
}
float t = deltaTime / m_flDt;
VectorLerp( ControlPoint( nControlPoint ).m_PrevPosition, ControlPoint( nControlPoint ).m_Position, t, *pControlPoint );
Assert( IsFinite(pControlPoint->x) && IsFinite(pControlPoint->y) && IsFinite(pControlPoint->z) );
}
//-----------------------------------------------------------------------------
// Used to retrieve the previous position of a control point
//
//-----------------------------------------------------------------------------
void CParticleCollection::GetControlPointAtPrevTime( int nControlPoint, Vector *pControlPoint )
{
Assert( m_pDef->ReadsControlPoint( nControlPoint ) );
*pControlPoint = ControlPoint( nControlPoint ).m_PrevPosition;
}
void CParticleCollection::GetControlPointTransformAtCurrentTime( int nControlPoint, matrix3x4_t *pMat )
{
Assert( m_pDef->ReadsControlPoint( nControlPoint ) );
const Vector &vecControlPoint = GetControlPointAtCurrentTime( nControlPoint );
// FIXME: Use quaternion lerp to get control point transform at time
Vector left;
VectorMultiply( ControlPoint( nControlPoint ).m_RightVector, -1.0f, left );
pMat->Init( ControlPoint( nControlPoint ).m_ForwardVector, left, ControlPoint( nControlPoint ).m_UpVector, vecControlPoint );
}
void CParticleCollection::GetControlPointTransformAtCurrentTime( int nControlPoint, VMatrix *pMat )
{
GetControlPointTransformAtCurrentTime( nControlPoint, &pMat->As3x4() );
pMat->m[3][0] = pMat->m[3][1] = pMat->m[3][2] = 0.0f; pMat->m[3][3] = 1.0f;
}
void CParticleCollection::GetControlPointOrientationAtTime( int nControlPoint, float flTime, Vector *pForward, Vector *pRight, Vector *pUp )
{
Assert( m_pDef->ReadsControlPoint( nControlPoint ) );
// FIXME: Use quaternion lerp to get control point transform at time
*pForward = ControlPoint( nControlPoint ).m_ForwardVector;
*pRight = ControlPoint( nControlPoint ).m_RightVector;
*pUp = ControlPoint( nControlPoint ).m_UpVector;
}
void CParticleCollection::GetControlPointTransformAtTime( int nControlPoint, float flTime, matrix3x4_t *pMat )
{
Assert( m_pDef->ReadsControlPoint( nControlPoint ) );
Vector vecControlPoint;
GetControlPointAtTime( nControlPoint, flTime, &vecControlPoint );
// FIXME: Use quaternion lerp to get control point transform at time
Vector left;
VectorMultiply( ControlPoint(nControlPoint).m_RightVector, -1.0f, left );
pMat->Init( ControlPoint( nControlPoint ).m_ForwardVector, left, ControlPoint( nControlPoint ).m_UpVector, vecControlPoint );
}
void CParticleCollection::GetControlPointTransformAtTime( int nControlPoint, float flTime, VMatrix *pMat )
{
GetControlPointTransformAtTime( nControlPoint, flTime, &pMat->As3x4() );
pMat->m[3][0] = pMat->m[3][1] = pMat->m[3][2] = 0.0f; pMat->m[3][3] = 1.0f;
}
void CParticleCollection::GetControlPointTransformAtTime( int nControlPoint, float flTime, CParticleSIMDTransformation *pXForm )
{
Assert( m_pDef->ReadsControlPoint( nControlPoint ) );
Vector vecControlPoint;
GetControlPointAtTime( nControlPoint, flTime, &vecControlPoint );
pXForm->m_v4Origin.DuplicateVector( vecControlPoint );
pXForm->m_v4Fwd.DuplicateVector( ControlPoint( nControlPoint ).m_ForwardVector );
pXForm->m_v4Up.DuplicateVector( ControlPoint( nControlPoint ).m_UpVector );
//Vector left;
//VectorMultiply( ControlPoint(nControlPoint).m_RightVector, -1.0f, left );
pXForm->m_v4Right.DuplicateVector( ControlPoint( nControlPoint ).m_RightVector );
}
int CParticleCollection::GetHighestControlPoint( void ) const
{
return m_nHighestCP;
}
//-----------------------------------------------------------------------------
// Returns the render bounds
//-----------------------------------------------------------------------------
void CParticleCollection::GetBounds( Vector *pMin, Vector *pMax )
{
*pMin = m_MinBounds;
*pMax = m_MaxBounds;
}
//-----------------------------------------------------------------------------
// Bloat the bounding box by bounds around the control point
//-----------------------------------------------------------------------------
void CParticleCollection::BloatBoundsUsingControlPoint()
{
// more specifically, some particle systems were using "start" as an input, so it got set as control point 1,
// so other particle systems had an extra point in their bounding box, that generally remained at the world origin
RecomputeBounds();
// Deal with children
// NOTE: Bounds have been recomputed for children prior to this call in Simulate
bool bIsValid = m_bBoundsValid;
Vector vecMins, vecMaxs;
for( CParticleCollection *i = m_Children.m_pHead; i; i = i->m_pNext )
{
// mdonofrio - skip screen space effects, they have CPs at the origin
if( (i->m_nActiveParticles > 0 ) &&
(!i->m_pDef->IsScreenSpaceEffect()) )
{
i->GetBounds( &vecMins, &vecMaxs );
VectorMin( m_MinBounds, vecMins, m_MinBounds );
VectorMax( m_MaxBounds, vecMaxs, m_MaxBounds );
bIsValid = ( bIsValid || i->m_bBoundsValid );
}
}
m_bBoundsValid = bIsValid;
}
//-----------------------------------------------------------------------------
// Recomputes the bounds
//-----------------------------------------------------------------------------
inline void UpdateBounds( fltx4 &min_, fltx4 &max_, fltx4 &sum_, fltx4 val )
{
min_ = MinSIMD( min_, val );
max_ = MaxSIMD( max_, val );
sum_ = AddSIMD( sum_, val );
}
inline void UpdateBounds( float &min_, float &max_, float &sum_, float val )
{
min_ = MIN( min_, val );
max_ = MAX( max_, val );
sum_ = sum_ + val;
}
void CParticleCollection::RecomputeBounds( void )
{
// mdonofrio - skip screen space effects (as they have CPs at the origin) as well as those with 0 active particles
if( ( m_nActiveParticles == 0 ) ||
m_pDef->IsScreenSpaceEffect() )
{
m_bBoundsValid = false;
m_MinBounds.Init( FLT_MAX, FLT_MAX, FLT_MAX );
m_MaxBounds.Init( -FLT_MAX, -FLT_MAX, -FLT_MAX );
m_Center.Init();
return;
}
fltx4 min_x = ReplicateX4(1.0e23);
fltx4 min_y = min_x;
fltx4 min_z = min_x;
fltx4 max_x = ReplicateX4(-1.0e23);
fltx4 max_y = max_x;
fltx4 max_z = max_x;
fltx4 sum_x = Four_Zeros;
fltx4 sum_y = Four_Zeros;
fltx4 sum_z = Four_Zeros;
float flMaxTail = m_pDef->GetMaxTailLength();
float flOODt = ( m_flDt != 0.0f ) ? ( 1.0f / m_flDt ) : 1.0f;
fltx4 maxtail = ReplicateX4( flMaxTail );
fltx4 oodt = ReplicateX4( flOODt );
size_t xyz_stride, prev_stride, trail_stride;
const fltx4 *xyz = GetM128AttributePtr( PARTICLE_ATTRIBUTE_XYZ, &xyz_stride );
const fltx4 *prev = GetM128AttributePtr( PARTICLE_ATTRIBUTE_PREV_XYZ, &prev_stride );
const fltx4 *trail = GetM128AttributePtr( PARTICLE_ATTRIBUTE_TRAIL_LENGTH, &trail_stride );
int ctr = m_nActiveParticles/4;
bool bHasTail = ( flMaxTail > 0.0f );
if ( bHasTail )
{
while ( ctr-- )
{
UpdateBounds( min_x, max_x, sum_x, xyz[0] );
UpdateBounds( min_y, max_y, sum_y, xyz[1] );
UpdateBounds( min_z, max_z, sum_z, xyz[2] );
fltx4 delta_x = SubSIMD( prev[0], xyz[0] );
fltx4 delta_y = SubSIMD( prev[1], xyz[1] );
fltx4 delta_z = SubSIMD( prev[2], xyz[2] );
fltx4 d2_x = MulSIMD( delta_x, delta_x );
fltx4 d2_y = MulSIMD( delta_y, delta_y );
fltx4 d2_z = MulSIMD( delta_z, delta_z );
fltx4 lensq = AddSIMD( d2_z, AddSIMD( d2_y, d2_x ) );
fltx4 len = MaxSIMD( ReplicateX4( 0.001f ), SqrtSIMD( lensq ) );
fltx4 invlen = ReciprocalSIMD( len );
delta_x = MulSIMD( delta_x, invlen );
delta_y = MulSIMD( delta_y, invlen );
delta_z = MulSIMD( delta_z, invlen );
len = MulSIMD( len, trail[0] );
len = MulSIMD( len, oodt );
len = MinSIMD( len, maxtail );
delta_x = MulSIMD( delta_x, len );
delta_y = MulSIMD( delta_y, len );
delta_z = MulSIMD( delta_z, len );
fltx4 tail_x = AddSIMD( xyz[0], delta_x );
fltx4 tail_y = AddSIMD( xyz[1], delta_y );
fltx4 tail_z = AddSIMD( xyz[2], delta_z );
UpdateBounds( min_x, max_x, sum_x, tail_x );
UpdateBounds( min_y, max_y, sum_y, tail_y );
UpdateBounds( min_z, max_z, sum_z, tail_z );
xyz += xyz_stride;
prev += prev_stride;
trail += trail_stride;
}
}
else
{
while ( ctr-- )
{
UpdateBounds( min_x, max_x, sum_x, xyz[0] );
UpdateBounds( min_y, max_y, sum_y, xyz[1] );
UpdateBounds( min_z, max_z, sum_z, xyz[2] );
xyz += xyz_stride;
}
}
m_bBoundsValid = true;
m_MinBounds.x = MIN( MIN( SubFloat( min_x, 0 ), SubFloat( min_x, 1 ) ), MIN( SubFloat( min_x, 2 ), SubFloat( min_x, 3 ) ) );
m_MinBounds.y = MIN( MIN( SubFloat( min_y, 0 ), SubFloat( min_y, 1 ) ), MIN( SubFloat( min_y, 2 ), SubFloat( min_y, 3 ) ) );
m_MinBounds.z = MIN( MIN( SubFloat( min_z, 0 ), SubFloat( min_z, 1 ) ), MIN( SubFloat( min_z, 2 ), SubFloat( min_z, 3 ) ) );
m_MaxBounds.x = MAX( MAX( SubFloat( max_x, 0 ), SubFloat( max_x, 1 ) ), MAX( SubFloat( max_x, 2 ), SubFloat( max_x, 3 ) ) );
m_MaxBounds.y = MAX( MAX( SubFloat( max_y, 0 ), SubFloat( max_y, 1 ) ), MAX( SubFloat( max_y, 2 ), SubFloat( max_y, 3 ) ) );
m_MaxBounds.z = MAX( MAX( SubFloat( max_z, 0 ), SubFloat( max_z, 1 ) ), MAX( SubFloat( max_z, 2 ), SubFloat( max_z, 3 ) ) );
float fsum_x = SubFloat( sum_x, 0 ) + SubFloat( sum_x, 1 ) + SubFloat( sum_x, 2 ) + SubFloat( sum_x, 3 );
float fsum_y = SubFloat( sum_y, 0 ) + SubFloat( sum_y, 1 ) + SubFloat( sum_y, 2 ) + SubFloat( sum_y, 3 );
float fsum_z = SubFloat( sum_z, 0 ) + SubFloat( sum_z, 1 ) + SubFloat( sum_z, 2 ) + SubFloat( sum_z, 3 );
// now, handle "tail" in a non-sse manner
for( int i=0; i < ( m_nActiveParticles & 3 ); i++)
{
Vector pos( SubFloat( xyz[0], i ), SubFloat( xyz[1], i ), SubFloat( xyz[2], i ) );
UpdateBounds( m_MinBounds.x, m_MaxBounds.x, fsum_x, pos.x );
UpdateBounds( m_MinBounds.y, m_MaxBounds.y, fsum_y, pos.y );
UpdateBounds( m_MinBounds.z, m_MaxBounds.z, fsum_z, pos.z );
if ( bHasTail )
if ( flMaxTail > 0.0f )
{
Vector pos_prev( SubFloat( prev[0], i ), SubFloat( prev[1], i ), SubFloat( prev[2], i ) );
Vector dir = pos_prev - pos;
float len = VectorNormalize( dir );
len = MIN( MAX( len, 0.001f ) * SubFloat( trail[0], i ) * flOODt, flMaxTail );
Vector tail = pos + dir * len;
UpdateBounds( m_MinBounds.x, m_MaxBounds.x, fsum_x, tail.x );
UpdateBounds( m_MinBounds.y, m_MaxBounds.y, fsum_y, tail.y );
UpdateBounds( m_MinBounds.z, m_MaxBounds.z, fsum_z, tail.z );
}
}
VectorAdd( m_MinBounds, m_pDef->m_BoundingBoxMin, m_MinBounds );
VectorAdd( m_MaxBounds, m_pDef->m_BoundingBoxMax, m_MaxBounds );
// calculate center
float flOONumParticles = 1.0 / ( ( flMaxTail > 0.0f ) ? 2 * m_nActiveParticles : m_nActiveParticles );
m_Center.x = flOONumParticles * fsum_x;
m_Center.y = flOONumParticles * fsum_y;
m_Center.z = flOONumParticles * fsum_z;
}
//-----------------------------------------------------------------------------
// Is the particle system finished emitting + all its particles are dead?
//-----------------------------------------------------------------------------
bool CParticleCollection::IsFinished( void ) const
{
if ( !m_pDef )
return true;
if ( m_nParticleFlags & PCFLAGS_FIRST_FRAME )
return false;
if ( m_nActiveParticles )
return false;
if ( m_bDormant )
return false;
// no particles. See if any emmitters intead to create more particles
int nEmitterCount = m_pDef->m_Emitters.Count();
for( int i=0; i < nEmitterCount; i++ )
{
if ( m_pDef->m_Emitters[i]->MayCreateMoreParticles( this, m_pOperatorContextData+m_pDef->m_nEmittersCtxOffsets[i] ) )
return false;
}
// make sure all children are finished
CParticleCollection *pChild = m_Children.Head();
for( int i = 0; pChild != NULL; pChild = pChild->m_pNext, i++ )
{
if ( !pChild->IsFinished() && !m_pDef->m_Children[i].m_bEndCap )
return false;
// return false if we're currently playing our endcap effect and not finished with it
if ( m_pDef->m_Children[i].m_bEndCap && !pChild->IsFinished() && m_bInEndCap )
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Stop emitting particles
//-----------------------------------------------------------------------------
void CParticleCollection::StopEmission( bool bInfiniteOnly, bool bRemoveAllParticles, bool bWakeOnStop, bool bPlayEndCap )
{
if ( !m_pDef )
return;
// Whenever we call stop emission, we clear out our dormancy. This ensures we
// get deleted if we're told to stop emission while dormant. SetDormant() ensures
// dormancy is set to true after stopping out emission.
m_bDormant = false;
if ( bWakeOnStop )
{
// Set next sleep time - an additional fudge factor is added over the normal time
// so that existing particles have a chance to go away.
m_flNextSleepTime = MAX( m_flNextSleepTime, ( g_pParticleSystemMgr->GetLastSimulationTime() + 10 ));
}
m_bEmissionStopped = true;
for( int i=0; i < m_pDef->m_Emitters.Count(); i++ )
{
m_pDef->m_Emitters[i]->StopEmission( this, m_pOperatorContextData + m_pDef->m_nEmittersCtxOffsets[i], bInfiniteOnly );
}
if ( bRemoveAllParticles )
{
SetNActiveParticles( 0 );
}
// Stop our children as well
if ( bPlayEndCap )
{
CParticleCollection *pChild;
int i;
m_bInEndCap = true;
for( i = 0, pChild = m_Children.m_pHead; pChild != NULL; pChild = pChild->m_pNext, i++ )
{
pChild->m_bInEndCap = true;
if ( m_pDef->m_Children[i].m_bEndCap )
{
pChild->m_flCurTime = 0.0f;
pChild->StartEmission( bInfiniteOnly );
}
else
pChild->StopEmission( bInfiniteOnly, bRemoveAllParticles, bWakeOnStop, bPlayEndCap );
}
}
else
{
for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext )
{
p->StopEmission( bInfiniteOnly, bRemoveAllParticles );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Stop emitting particles
//-----------------------------------------------------------------------------
void CParticleCollection::StartEmission( bool bInfiniteOnly )
{
if ( !m_pDef )
return;
m_bEmissionStopped = false;
for( int i=0; i < m_pDef->m_Emitters.Count(); i++ )
{
m_pDef->m_Emitters[i]->StartEmission( this, m_pOperatorContextData + m_pDef->m_nEmittersCtxOffsets[i], bInfiniteOnly );
}
// Start our children as well
CParticleCollection *pChild = m_Children.Head();
for( int i = 0; pChild != NULL; pChild = pChild->m_pNext, i++ )
{
// Don't start End Cap Effects - these only play when stopping emission.
if ( !m_pDef->m_Children[i].m_bEndCap )
{
pChild->StartEmission( bInfiniteOnly );
}
}
// Set our sleep time to some time in the future so we update again
m_flNextSleepTime = g_pParticleSystemMgr->GetLastSimulationTime() + m_pDef->m_flNoDrawTimeToGoToSleep;
}
//-----------------------------------------------------------------------------
// Purpose: Dormant particle systems simulate their particles, but don't emit
// new ones. Unlike particle systems that have StopEmission() called
// dormant particle systems don't remove themselves when they're
// out of particles, assuming they'll return at some point.
//-----------------------------------------------------------------------------
void CParticleCollection::SetDormant( bool bDormant )
{
// Don't stop or start emission if we are not changing dormancy state
if ( bDormant == m_bDormant )
return;
// If emissions have already been stopped, don't go dormant, we're supposed to be dying.
if ( m_bEmissionStopped && bDormant )
return;
if ( bDormant )
{
StopEmission();
m_bQueuedStartEmission = false;
}
else
{
//StartEmission();
m_bQueuedStartEmission = true; // start emission during next sim step
// Set our sleep time to some time in the future so we update again
m_flNextSleepTime = g_pParticleSystemMgr->GetLastSimulationTime() + m_pDef->m_flNoDrawTimeToGoToSleep;
}
m_bDormant = bDormant;
}
bool CParticleCollection::IsEmitting() const
{
return !m_bEmissionStopped;
}
void CParticleAttributeAddressTable::CopyParticleAttributes( int nSrcIndex, int nDestIndex ) const
{
for( int p = 0; p < ARRAYSIZE( m_pAttributes ); p++ )
{
switch( m_nFloatStrides[p] )
{
case 4: // move a float
m_pAttributes[p][nDestIndex] = m_pAttributes[p][nSrcIndex];
break;
case 12: // move a vec3
{
// sse weirdness
int oldidxsse = 12 * ( nDestIndex >> 2 );
int oldofs = oldidxsse + ( nDestIndex & 3 );
int lastidxsse = 12 * ( nSrcIndex >> 2 );
int lastofs = lastidxsse + ( nSrcIndex & 3 );
m_pAttributes[p][oldofs] = m_pAttributes[p][lastofs];
m_pAttributes[p][4 + oldofs] = m_pAttributes[p][4 + lastofs];
m_pAttributes[p][8 + oldofs] = m_pAttributes[p][8 + lastofs];
break;
}
}
}
}
void CParticleCollection::MoveParticle( int nInitialIndex, int nNewIndex )
{
// Copy the per-particle attributes
m_ParticleAttributes.CopyParticleAttributes( nInitialIndex, nNewIndex );
m_ParticleInitialAttributes.CopyParticleAttributes( nInitialIndex, nNewIndex );
if ( m_pPreviousAttributeMemory )
{
m_PreviousFrameAttributes.CopyParticleAttributes( nInitialIndex, nNewIndex );
}
}
//-----------------------------------------------------------------------------
// Kill List processing.
//-----------------------------------------------------------------------------
#define THREADED_PARTICLES 1
#if THREADED_PARTICLES
#define MAX_SIMULTANEOUS_KILL_LISTS 16
static volatile int g_nKillBufferInUse[MAX_SIMULTANEOUS_KILL_LISTS];
static KillListItem_t *g_pKillBuffers[MAX_SIMULTANEOUS_KILL_LISTS];
void CParticleSystemMgr::DetachKillList( CParticleCollection *pParticles )
{
if ( pParticles->m_pParticleKillList )
{
// find which it is
for(int i=0; i < NELEMS( g_pKillBuffers ); i++)
{
if ( g_pKillBuffers[i] == pParticles->m_pParticleKillList )
{
pParticles->m_pParticleKillList = NULL;
g_nKillBufferInUse[i] = 0; // no need to interlock
return;
}
}
Assert( 0 ); // how did we get here?
}
}
void CParticleSystemMgr::AttachKillList( CParticleCollection *pParticles )
{
// look for a free slot
for(;;)
{
for(int i=0; i < NELEMS( g_nKillBufferInUse ); i++)
{
if ( ! g_nKillBufferInUse[i] ) // available?
{
// try to take it!
if ( ThreadInterlockedAssignIf( &( g_nKillBufferInUse[i]), 1, 0 ) )
{
if ( ! g_pKillBuffers[i] )
{
g_pKillBuffers[i] = new KillListItem_t[MAX_PARTICLES_IN_A_SYSTEM];
}
pParticles->m_pParticleKillList = g_pKillBuffers[i];
return; // done!
}
}
}
Assert(0); // why don't we have enough buffers?
ThreadSleep();
}
}
#else
// use one static kill list. no worries because of not threading
static KillListItem_t g_nParticleKillList[MAX_PARTICLES_IN_A_SYSTEM];
void CParticleSystemMgr::AttachKillList( CParticleCollection *pParticles )
{
pParticles->m_pParticleKillList = g_nParticleKillList;
}
void CParticleCollection::DetachKillList( CParticleCollection *pParticles )
{
Assert( pParticles->m_nNumParticlesToKill == 0 );
pParticles->m_pParticleKillList = NULL;
}
#endif
void CParticleCollection::ApplyKillList( void )
{
// first, kill particles past bounds
const KillListItem_t *pCurKillListSlot = m_pParticleKillList;
while( m_nNumParticlesToKill && pCurKillListSlot[ m_nNumParticlesToKill-1 ].nIndex >= (uint)m_nActiveParticles )
{
m_nNumParticlesToKill--;
}
Assert( m_nNumParticlesToKill <= m_nActiveParticles );
if ( m_nNumParticlesToKill == 0 )
return;
// next, run any child system emitter/initializer operators which request the parent's kill list:
bool bApplyingParentKillList = true;
for( CParticleCollection *pChild = m_Children.m_pHead; pChild != NULL; pChild = pChild->m_pNext )
{
// TODO: make this more general (there's a bunch of "first frame" and "frame-to-frame" setup that happens in Simulate() which is skipped here)
EmitAndInit( pChild, bApplyingParentKillList );
}
// now, execute kill list (NOTE: this algorithm assumes the particles listed
// in the kill list are in ascending order - this is checked in KillParticle)
unsigned int nParticlesActiveNow = m_nActiveParticles;
int nLeftInKillList = m_nNumParticlesToKill;
if ( nLeftInKillList == m_nActiveParticles )
{
nParticlesActiveNow = 0; // Simply discard all particles
}
// TODO: check KILL_LIST_FLAG_DONT_KILL here (take no action for those kill list entries)
else if ( IsOrderImportant() )
{
// shift
m_pParticleKillList[ nLeftInKillList ].nIndex = m_nActiveParticles;
for ( int nKilled = 0; nKilled < nLeftInKillList; )
{
int nWriteIndex = m_pParticleKillList[ nKilled ].nIndex - nKilled;
nKilled++;
int nNextIndexToKill = m_pParticleKillList[ nKilled ].nIndex - nKilled;
for ( nWriteIndex; nWriteIndex < nNextIndexToKill; nWriteIndex++ )
{
MoveParticle( ( nWriteIndex + nKilled ), nWriteIndex );
}
}
nParticlesActiveNow -= nLeftInKillList;
}
else
{
while( nLeftInKillList )
{
unsigned int nKillIndex = (pCurKillListSlot++)->nIndex;
nLeftInKillList--;
// now, we will move a particle from the end to where we are
// first, we have to find the last particle (which is not in the kill list)
while ( nLeftInKillList &&
( pCurKillListSlot[ nLeftInKillList-1 ].nIndex == nParticlesActiveNow-1 ))
{
nLeftInKillList--;
nParticlesActiveNow--;
}
// we might be killing the last particle
if ( nKillIndex == nParticlesActiveNow-1 )
{
// killing last one
nParticlesActiveNow--;
break; // we are done
}
// move the last particle to this one and chop off the end of the list
MoveParticle( nParticlesActiveNow-1, nKillIndex );
nParticlesActiveNow--;
}
}
// set count in system and wipe kill list
SetNActiveParticles( nParticlesActiveNow );
m_nNumParticlesToKill = 0;
}
void CParticleCollection::CalculatePathValues( CPathParameters const &PathIn,
float flTimeStamp,
Vector *pStartPnt,
Vector *pMidPnt,
Vector *pEndPnt
)
{
Vector StartPnt;
GetControlPointAtTime( PathIn.m_nStartControlPointNumber, flTimeStamp, &StartPnt );
Vector EndPnt;
GetControlPointAtTime( PathIn.m_nEndControlPointNumber, flTimeStamp, &EndPnt );
Vector MidP;
VectorLerp(StartPnt, EndPnt, PathIn.m_flMidPoint, MidP);
if ( PathIn.m_nBulgeControl )
{
Vector vTarget=(EndPnt-StartPnt);
float flBulgeScale = 0.0;
int nCP=PathIn.m_nStartControlPointNumber;
if ( PathIn.m_nBulgeControl == 2)
nCP = PathIn.m_nEndControlPointNumber;
Vector Fwd = ControlPoint( nCP ).m_ForwardVector;
float len=VectorLength( vTarget);
if ( len > 1.0e-6 )
{
vTarget *= (1.0/len); // normalize
flBulgeScale = 1.0-fabs( DotProduct( vTarget, Fwd )); // bulge inversely scaled
}
Vector Potential_MidP=Fwd;
float flOffsetDist = VectorLength( Potential_MidP );
if ( flOffsetDist > 1.0e-6 )
{
Potential_MidP *= (PathIn.m_flBulge*len*flBulgeScale)/flOffsetDist;
MidP += Potential_MidP;
}
}
else
{
Vector RndVector;
RandomVector( 0, -PathIn.m_flBulge, PathIn.m_flBulge, &RndVector);
MidP+=RndVector;
}
*pStartPnt = StartPnt;
*pMidPnt = MidP;
*pEndPnt = EndPnt;
}
//-----------------------------------------------------------------------------
//
// Default impelemtation of the query
//
//-----------------------------------------------------------------------------
class CDefaultParticleSystemQuery : public CBaseAppSystem< IParticleSystemQuery >
{
public:
virtual bool IsEditor( ) { return false; }
virtual void GetLightingAtPoint( const Vector& vecOrigin, Color &tint )
{
tint.SetColor( 255, 255, 255, 255 );
}
virtual void TraceLine( const Vector& vecAbsStart,
const Vector& vecAbsEnd, unsigned int mask,
const class IHandleEntity *ignore,
int collisionGroup, CBaseTrace *ptr )
{
ptr->fraction = 1.0; // no hit
}
virtual bool IsPointInSolid( const Vector& vecPos, const int nContentsMask )
{
return false;
}
virtual void GetRandomPointsOnControllingObjectHitBox(
CParticleCollection *pParticles,
int nControlPointNumber,
int nNumPtsOut,
float flBBoxScale,
int nNumTrysToGetAPointInsideTheModel,
Vector *pPntsOut,
Vector vecDirectionBias,
Vector *pHitBoxRelativeCoordOut, int *pHitBoxIndexOut, int nDesiredHitbox,
const char *pszHitboxSetName )
{
for ( int i = 0; i < nNumPtsOut; ++i )
{
pPntsOut[i].Init();
}
}
virtual void GetClosestControllingObjectHitBox( CParticleCollection *pParticles,
int nControlPointNumber,
int nNumPtsIn,
float flBBoxScale,
Vector *pPntsIn,
Vector *pHitBoxRelativeCoordOut,
int *pHitBoxIndexOut,
int nDesiredHitbox,
const char *pszHitboxSetName )
{
for ( int i=0; i < nNumPtsIn; i++ )
{
if ( pHitBoxIndexOut )
pHitBoxIndexOut[i] = 0;
if ( pHitBoxRelativeCoordOut )
pHitBoxRelativeCoordOut[i].Init();
}
}
virtual void TraceAgainstRayTraceEnv(
int envnumber,
const FourRays &rays, fltx4 TMin, fltx4 TMax,
RayTracingResult *rslt_out, int32 skip_id ) const
{
rslt_out->HitDistance = Four_Ones;
rslt_out->surface_normal.DuplicateVector( vec3_origin );
}
virtual Vector GetCurrentViewOrigin()
{
return vec3_origin;
}
virtual int GetActivityCount() { return 0; }
virtual const char *GetActivityNameFromIndex( int nActivityIndex ) { return 0; }
virtual int GetActivityNumber( void *pModel, const char *m_pszActivityName ) { return -1; }
virtual float GetPixelVisibility( int *pQueryHandle, const Vector &vecOrigin, float flScale ) { return 0.0f; }
virtual void PreSimulate( ) { }
virtual void PostSimulate( ) { }
virtual void DebugDrawLine( const Vector &origin, const Vector &target, int r, int g, int b, bool noDepthTest, float duration )
{
}
virtual void DrawModel( void *pModel, const matrix3x4_t &DrawMatrix, CParticleCollection *pParticles, int nParticleNumber, int nBodyPart, int nSubModel,
int nSkin, int nAnimationSequence = 0, float flAnimationRate = 30.0f, float r = 1.0f, float g = 1.0f, float b = 1.0f, float a = 1.0f ) { }
virtual void UpdateProjectedTexture( const int nParticleID, IMaterial *pMaterial, Vector &vOrigin, float flRadius, float flRotation, float r, float g, float b, float a, void *&pUserVar ) { }
};
static CDefaultParticleSystemQuery s_DefaultParticleSystemQuery;
//-----------------------------------------------------------------------------
//
// Particle system manager
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
CParticleSystemMgr::CParticleSystemMgr()
// m_SheetList( DefLessFunc( ITexture * ) )
{
m_pQuery = &s_DefaultParticleSystemQuery;
m_bDidInit = false;
m_bUsingDefaultQuery = true;
m_bShouldLoadSheets = true;
m_bAllowPrecache = true;
m_pParticleSystemDictionary = NULL;
m_nNumFramesMeasured = 0;
m_flLastSimulationTime = 0.0f;
m_flLastSimulationDuration = 0.0f;
m_pShadowDepthMaterial = NULL;
// Init the attribute table
InitAttributeTable();
}
CParticleSystemMgr::~CParticleSystemMgr()
{
FlushAllSheets();
if ( m_pParticleSystemDictionary )
{
delete m_pParticleSystemDictionary;
m_pParticleSystemDictionary = NULL;
}
}
//-----------------------------------------------------------------------------
// Initialize the particle system
//-----------------------------------------------------------------------------
bool CParticleSystemMgr::Init( IParticleSystemQuery *pQuery, bool bAllowPrecache )
{
if ( g_pMaterialSystem && ( !g_pMaterialSystem->QueryInterface( MATERIAL_SYSTEM_INTERFACE_VERSION ) ) )
{
Msg( "CParticleSystemMgr compiled using an old IMaterialSystem\n" );
return false;
}
if ( m_bUsingDefaultQuery && pQuery )
{
m_pQuery = pQuery;
m_bUsingDefaultQuery = false;
}
if ( !m_bDidInit )
{
m_pParticleSystemDictionary = new CParticleSystemDictionary;
// NOTE: This is for the editor only
AddParticleOperator( FUNCTION_CHILDREN, &s_ChildOperatorDefinition );
if ( g_pMaterialSystem )
{
MEM_ALLOC_CREDIT();
KeyValues *pVMTKeyValues = new KeyValues( "DepthWrite" );
pVMTKeyValues->SetInt( "$no_fullbright", 1 );
pVMTKeyValues->SetInt( "$model", 0 );
pVMTKeyValues->SetInt( "$alphatest", 0 );
m_pShadowDepthMaterial = g_pMaterialSystem->CreateMaterial( "__particlesDepthWrite", pVMTKeyValues );
}
SeedRandSIMD( 12345678 );
m_bDidInit = true;
}
m_bAllowPrecache = bAllowPrecache;
return true;
}
//-----------------------------------------------------------------------------
void CParticleSystemMgr::Shutdown()
{
if ( m_pShadowDepthMaterial )
{
m_pShadowDepthMaterial->Release();
m_pShadowDepthMaterial = NULL;
}
}
//-----------------------------------------------------------------------------
// Init the attribute table
//-----------------------------------------------------------------------------
void CParticleSystemMgr::InitAttributeTable( void )
{
// Init the attribute table
#define INITPARTICLE_ATTRIBUTE( name ) \
{ \
int bit = PARTICLE_ATTRIBUTE_##name; \
s_AttributeTable[ bit ].nDataType = PARTICLE_ATTRIBUTE_##name##_DATATYPE; \
s_AttributeTable[ bit ].pName = #name; \
}
memset( s_AttributeTable, 0, sizeof( s_AttributeTable ) );
INITPARTICLE_ATTRIBUTE( XYZ );
INITPARTICLE_ATTRIBUTE( LIFE_DURATION );
INITPARTICLE_ATTRIBUTE( PREV_XYZ );
INITPARTICLE_ATTRIBUTE( RADIUS );
INITPARTICLE_ATTRIBUTE( ROTATION );
INITPARTICLE_ATTRIBUTE( ROTATION_SPEED );
INITPARTICLE_ATTRIBUTE( TINT_RGB );
INITPARTICLE_ATTRIBUTE( ALPHA );
INITPARTICLE_ATTRIBUTE( CREATION_TIME );
INITPARTICLE_ATTRIBUTE( SEQUENCE_NUMBER );
INITPARTICLE_ATTRIBUTE( TRAIL_LENGTH );
INITPARTICLE_ATTRIBUTE( PARTICLE_ID );
INITPARTICLE_ATTRIBUTE( YAW );
INITPARTICLE_ATTRIBUTE( SEQUENCE_NUMBER1 );
INITPARTICLE_ATTRIBUTE( HITBOX_INDEX );
INITPARTICLE_ATTRIBUTE( HITBOX_RELATIVE_XYZ );
INITPARTICLE_ATTRIBUTE( ALPHA2 );
INITPARTICLE_ATTRIBUTE( SCRATCH_VEC );
INITPARTICLE_ATTRIBUTE( SCRATCH_FLOAT );
INITPARTICLE_ATTRIBUTE( UNUSED );
INITPARTICLE_ATTRIBUTE( PITCH );
INITPARTICLE_ATTRIBUTE( NORMAL );
INITPARTICLE_ATTRIBUTE( GLOW_RGB );
INITPARTICLE_ATTRIBUTE( GLOW_ALPHA );
for ( int i = 0; i < MAX_PARTICLE_ATTRIBUTES; i++ )
{
if ( !s_AttributeTable[ i ].pName )
{
// The above list of initializers needs updating!
Warning( "CParticleSystemMgr::InitAttributeTable has an out-of-date attribute list! (element %d not set up)\n", i );
Assert( 0 );
}
}
}
//----------------------------------------------------------------------------------
// String -> Attribute mapping
//----------------------------------------------------------------------------------
int CParticleSystemMgr::GetParticleAttributeByName( const char *pName ) const
{
// TODO: OPTIMIZATION: use Chris's CUtlStringToken class here to speed this up
for ( int i = 0; i < MAX_PARTICLE_ATTRIBUTES; i++ )
{
if ( !Q_stricmp( pName, s_AttributeTable[ i ].pName ) )
return i;
}
return -1;
}
//----------------------------------------------------------------------------------
// Attribute -> String mapping
//----------------------------------------------------------------------------------
const char *CParticleSystemMgr::GetParticleAttributeName( int nAttribute ) const
{
if ( ( nAttribute < 0 ) || ( nAttribute >= MAX_PARTICLE_ATTRIBUTES ) )
{
Assert( 0 );
return "unknown";
}
return s_AttributeTable[ nAttribute ].pName;
}
//----------------------------------------------------------------------------------
// Get the data type of a given attribute
//----------------------------------------------------------------------------------
EAttributeDataType CParticleSystemMgr::GetParticleAttributeDataType( int nAttribute ) const
{
Assert( nAttribute >= 0 );
Assert( nAttribute < MAX_PARTICLE_ATTRIBUTES );
return s_AttributeTable[ nAttribute ].nDataType;
}
//----------------------------------------------------------------------------------
// Cache/uncache materials used by particle systems
//----------------------------------------------------------------------------------
void CParticleSystemMgr::PrecacheParticleSystem( int nStringNumber, const char *pName )
{
if ( !pName || !pName[0] )
{
return;
}
ParticleSystemHandle_t hParticleSystem = GetParticleSystemIndex( pName );
// Used to display an error system if the requested one isn't known from the manifest
if ( hParticleSystem == UTL_INVAL_SYMBOL )
{
Warning( "Attempted to precache unknown particle system \"%s\"!\n", pName );
hParticleSystem = GetParticleSystemIndex( "error" );
}
CParticleSystemDefinition* pDef = FindParticleSystem( hParticleSystem );
CUtlVector< ParticleSystemHandle_t > &lookup = ( nStringNumber >= 0 ) ? m_PrecacheLookup : m_ClientPrecacheLookup;
if ( nStringNumber < 0 )
{
nStringNumber = - nStringNumber - 1;
}
int nCountToAdd = nStringNumber + 1 - lookup.Count();
for ( int i = 0; i < nCountToAdd; ++i )
{
lookup.AddToTail( UTL_INVAL_SYMBOL );
}
lookup[ nStringNumber ] = hParticleSystem;
if ( !pDef )
{
Warning( "Attempted to precache unknown particle system \"%s\"!\n", pName );
return;
}
pDef->Precache();
}
void CParticleSystemMgr::LevelShutdown( void )
{
#ifndef SERVER_PARTICLE_LIB
// InvalidateGlobalCollisionCache(); // keep the collision cahce out of the server binary for now
#endif
}
void CParticleSystemMgr::UncacheAllParticleSystems()
{
m_PrecacheLookup.RemoveAll();
m_ClientPrecacheLookup.RemoveAll();
if ( m_pParticleSystemDictionary )
{
int nCount = m_pParticleSystemDictionary->Count();
for ( int i = 0; i < nCount; ++i )
{
m_pParticleSystemDictionary->GetParticleSystem( i )->Uncache();
}
nCount = m_pParticleSystemDictionary->NameCount();
for ( ParticleSystemHandle_t h = 0; h < nCount; ++h )
{
m_pParticleSystemDictionary->FindParticleSystem( h )->Uncache();
}
}
// Flush sheets, as they can accumulate several MB of memory per map
FlushAllSheets();
}
//-----------------------------------------------------------------------------
// return the particle field name
//-----------------------------------------------------------------------------
static const char *s_pParticleFieldNames[MAX_PARTICLE_ATTRIBUTES] =
{
"Position", // XYZ, 0
"Life Duration", // LIFE_DURATION, 1 );
"Position Previous",// PREV_XYZ
"Radius", // RADIUS, 3 );
"Roll", // ROTATION, 4 );
"Roll Speed", // ROTATION_SPEED, 5 );
"Color", // TINT_RGB, 6 );
"Alpha", // ALPHA, 7 );
"Creation Time", // CREATION_TIME, 8 );
"Sequence Number", // SEQUENCE_NUMBER, 9 );
"Trail Length", // TRAIL_LENGTH, 10 );
"Particle ID", // PARTICLE_ID, 11 );
"Yaw", // YAW, 12 );
"Sequence Number 1",// SEQUENCE_NUMBER1, 13 );
"Hitbox Index", // HITBOX_INDEX, 14
"Hitbox Offset Position", // HITBOX_XYZ_RELATIVE 15
"Alpha Alternate", // ALPHA2, 16
"Scratch Vector", // SCRATCH_VEC 17
"Scratch Float", // SCRATCH_FLOAT 18
NULL,
"Pitch", // PITCH, 20
"Normal", // NORMAL, 21
"Glow RGB", //GLOW_RGB,22
"Glow Alpha", //GLOW_ALPHA,23
};
const char* CParticleSystemMgr::GetParticleFieldName( int nParticleField ) const
{
return s_pParticleFieldNames[nParticleField];
}
//-----------------------------------------------------------------------------
// Returns the available particle operators
//-----------------------------------------------------------------------------
void CParticleSystemMgr::AddParticleOperator( ParticleFunctionType_t nOpType,
IParticleOperatorDefinition *pOpFactory )
{
m_ParticleOperators[nOpType].AddToTail( pOpFactory );
}
static const char *s_pFilterNames[ ] =
{
"All",
"Position and Velocity",
"Life Duration",
"Parameter Remapping",
"Rotation",
"Size",
"Color and Opacity",
"Animation Sequence",
"Hitbox",
"Normal",
"Control Points"
};
const char *CParticleSystemMgr::GetFilterName( ParticleFilterType_t nFilterType ) const
{
COMPILE_TIME_ASSERT( ARRAYSIZE( s_pFilterNames ) == FILTER_COUNT );
return s_pFilterNames[nFilterType];
}
CUtlVector< IParticleOperatorDefinition *> &CParticleSystemMgr::GetAvailableParticleOperatorList( ParticleFunctionType_t nWhichList )
{
return m_ParticleOperators[nWhichList];
}
const DmxElementUnpackStructure_t *CParticleSystemMgr::GetParticleSystemDefinitionUnpackStructure()
{
return s_pParticleSystemDefinitionUnpack;
}
//------------------------------------------------------------------------------
// custom allocators for operators so simd aligned
//------------------------------------------------------------------------------
#include "tier0/memdbgoff.h"
void *CParticleOperatorInstance::operator new( size_t nSize )
{
return MemAlloc_AllocAligned( nSize, 16 );
}
void* CParticleOperatorInstance::operator new( size_t nSize, int nBlockUse, const char *pFileName, int nLine )
{
return MemAlloc_AllocAlignedFileLine( nSize, 16, pFileName, nLine );
}
void CParticleOperatorInstance::operator delete(void *pData)
{
if ( pData )
{
MemAlloc_FreeAligned( pData );
}
}
void CParticleOperatorInstance::operator delete( void* pData, int nBlockUse, const char *pFileName, int nLine )
{
if ( pData )
{
MemAlloc_FreeAligned( pData );
}
}
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// Load a PCF file and list the particle systems in it
//-----------------------------------------------------------------------------
void CParticleSystemMgr::GetParticleSystemsInFile( const char *pFileName, CUtlVector< CUtlString > *pOutSystemNameList )
{
if( pOutSystemNameList == NULL )
return;
pOutSystemNameList->RemoveAll();
CUtlBuffer buf;
if ( !g_pFullFileSystem->ReadFile( pFileName, "GAME", buf ) )
{
return;
}
GetParticleSystemsInBuffer( buf, pOutSystemNameList );
}
void CParticleSystemMgr::GetParticleSystemsInBuffer( CUtlBuffer &buf, CUtlVector<CUtlString> *pOutSystemNameList )
{
if( pOutSystemNameList == NULL )
return;
pOutSystemNameList->RemoveAll();
DECLARE_DMX_CONTEXT( );
CDmxElement *pRoot;
if ( !UnserializeDMX( buf, &pRoot ) )
{
return;
}
if ( !Q_stricmp( pRoot->GetTypeString(), "DmeParticleSystemDefinition" ) )
{
pOutSystemNameList->AddToTail( pRoot->GetName() );
CleanupDMX( pRoot );
return;
}
const CDmxAttribute *pDefinitions = pRoot->GetAttribute( "particleSystemDefinitions" );
if ( !pDefinitions || pDefinitions->GetType() != AT_ELEMENT_ARRAY )
{
CleanupDMX( pRoot );
return;
}
const CUtlVector< CDmxElement* >& definitions = pDefinitions->GetArray<CDmxElement*>( );
int nCount = definitions.Count();
for ( int i = 0; i < nCount; ++i )
{
pOutSystemNameList->AddToTail( definitions[i]->GetName() );
}
CleanupDMX( pRoot );
}
//-----------------------------------------------------------------------------
// Read the particle config file from a utlbuffer
//-----------------------------------------------------------------------------
bool CParticleSystemMgr::ReadParticleDefinitions( CUtlBuffer &buf, const char *pFileName, bool bPrecache, bool bDecommitTempMemory )
{
DECLARE_DMX_CONTEXT_DECOMMIT( bDecommitTempMemory );
CDmxElement *pRoot;
if ( !UnserializeDMX( buf, &pRoot, pFileName ) )
{
Warning( "Unable to read particle definition %s! UtlBuffer is probably the wrong type!\n", pFileName );
return false;
}
if ( !Q_stricmp( pRoot->GetTypeString(), "DmeParticleSystemDefinition" ) )
{
CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->AddParticleSystem( pRoot );
if ( pDef && bPrecache )
{
pDef->m_bAlwaysPrecache = true;
if ( m_bAllowPrecache )
{
pDef->Precache();
}
}
CleanupDMX( pRoot );
return true;
}
const CDmxAttribute *pDefinitions = pRoot->GetAttribute( "particleSystemDefinitions" );
if ( !pDefinitions || pDefinitions->GetType() != AT_ELEMENT_ARRAY )
{
CleanupDMX( pRoot );
return false;
}
const CUtlVector< CDmxElement* >& definitions = pDefinitions->GetArray<CDmxElement*>( );
int nCount = definitions.Count();
for ( int i = 0; i < nCount; ++i )
{
CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->AddParticleSystem( definitions[i] );
if ( pDef && bPrecache )
{
pDef->m_bAlwaysPrecache = true;
if ( m_bAllowPrecache )
{
pDef->Precache();
}
}
}
CleanupDMX( pRoot );
return true;
}
//-----------------------------------------------------------------------------
// Decommits temporary memory
//-----------------------------------------------------------------------------
void CParticleSystemMgr::DecommitTempMemory()
{
DecommitDMXMemory();
}
//-----------------------------------------------------------------------------
// Sets the last simulation time, used for particle system sleeping logic
//-----------------------------------------------------------------------------
void CParticleSystemMgr::SetLastSimulationTime( float flTime )
{
m_flLastSimulationTime = flTime;
}
float CParticleSystemMgr::GetLastSimulationTime() const
{
return m_flLastSimulationTime;
}
//-----------------------------------------------------------------------------
// Sets the last simulation duration ( the amount of time we spent simulating particle ) last frame
// Used to fallback to cheaper particle systems under load
//-----------------------------------------------------------------------------
void CParticleSystemMgr::SetLastSimulationDuration( float flDuration )
{
m_flLastSimulationDuration = flDuration;
}
float CParticleSystemMgr::GetLastSimulationDuration() const
{
return m_flLastSimulationDuration;
}
//-----------------------------------------------------------------------------
// GPU/CPU Level
//-----------------------------------------------------------------------------
void CParticleSystemMgr::SetSystemLevel( int nCPULevel, int nGPULevel )
{
m_nCPULevel = nCPULevel;
m_nGPULevel = nGPULevel;
}
int CParticleSystemMgr::GetParticleCPULevel() const
{
return m_nCPULevel;
}
int CParticleSystemMgr::GetParticleGPULevel() const
{
return m_nGPULevel;
}
//-----------------------------------------------------------------------------
// Fallback paramters
//-----------------------------------------------------------------------------
void CParticleSystemMgr::SetFallbackParameters( float flBase, float flMultiplier, float flSimFallbackBaseMultiplier, float flSimThresholdMs )
{
m_flFallbackBase = flBase;
m_flFallbackMultiplier = flMultiplier;
m_flSimFallbackBaseMultiplier = flSimFallbackBaseMultiplier;
m_flSimThresholdMs = flSimThresholdMs;
}
float CParticleSystemMgr::GetFallbackBase() const
{
return m_flFallbackBase;
}
float CParticleSystemMgr::GetFallbackMultiplier() const
{
return m_flFallbackMultiplier;
}
float CParticleSystemMgr::GetSimFallbackThresholdMs() const
{
return m_flSimThresholdMs;
}
float CParticleSystemMgr::GetSimFallbackBaseMultiplier() const
{
return m_flSimFallbackBaseMultiplier;
}
//-----------------------------------------------------------------------------
// Unserialization-related methods
//-----------------------------------------------------------------------------
void CParticleSystemMgr::AddParticleSystem( CDmxElement *pParticleSystem )
{
m_pParticleSystemDictionary->AddParticleSystem( pParticleSystem );
}
CParticleSystemDefinition* CParticleSystemMgr::FindParticleSystem( const char *pName )
{
return m_pParticleSystemDictionary->FindParticleSystem( pName );
}
CParticleSystemDefinition* CParticleSystemMgr::FindParticleSystem( const DmObjectId_t& id )
{
return m_pParticleSystemDictionary->FindParticleSystem( id );
}
CParticleSystemDefinition* CParticleSystemMgr::FindParticleSystem( ParticleSystemHandle_t hParticleSystem )
{
return m_pParticleSystemDictionary->FindParticleSystem( hParticleSystem );
}
CParticleSystemDefinition* CParticleSystemMgr::FindPrecachedParticleSystem( int nPrecacheIndex )
{
CUtlVector< ParticleSystemHandle_t > &lookup = ( nPrecacheIndex >= 0 ) ? m_PrecacheLookup : m_ClientPrecacheLookup;
if ( nPrecacheIndex < 0 )
{
nPrecacheIndex = - nPrecacheIndex - 1;
}
if ( nPrecacheIndex >= lookup.Count() )
return NULL;
return FindParticleSystem( lookup[nPrecacheIndex] );
}
//-----------------------------------------------------------------------------
// Read the particle config file from a utlbuffer
//-----------------------------------------------------------------------------
bool CParticleSystemMgr::ReadParticleConfigFile( CUtlBuffer &buf, bool bPrecache, bool bDecommitTempMemory, const char *pFileName )
{
return ReadParticleDefinitions( buf, pFileName, bPrecache, bDecommitTempMemory );
}
//-----------------------------------------------------------------------------
// Read the particle config file from a utlbuffer
//-----------------------------------------------------------------------------
bool CParticleSystemMgr::ReadParticleConfigFile( const char *pFileName, bool bPrecache, bool bDecommitTempMemory )
{
// Names starting with a '!' are always precached.
if ( pFileName[0] == '!' )
{
bPrecache = true;
++pFileName;
}
if ( PLATFORM_EXT[0] )
{
char szTargetName[MAX_PATH];
CreatePlatformFilename( pFileName, szTargetName, sizeof( szTargetName ) );
CUtlBuffer fileBuffer;
bool bHaveParticles = g_pFullFileSystem->ReadFile( szTargetName, "GAME", fileBuffer );
if ( bHaveParticles )
{
fileBuffer.SetBigEndian( false );
return ReadParticleConfigFile( fileBuffer, bPrecache, bDecommitTempMemory, szTargetName );
}
else
{
// 360/PS3 version should have been there, 360/PS3 zips can only have binary particles
Warning( "Particles: Missing '%s'\n", szTargetName );
return false;
}
}
// char pFallbackBuf[MAX_PATH];
if ( IsPC() )
{
// Look for fallback particle systems
char pTemp[MAX_PATH];
Q_StripExtension( pFileName, pTemp, sizeof(pTemp) );
const char *pExt = Q_GetFileExtension( pFileName );
if ( !pExt )
{
pExt = "pcf";
}
/*
// FIXME: Hook GPU level and/or CPU level into fallbacks instead of dxsupport level
if ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 90 )
{
Q_snprintf( pFallbackBuf, sizeof(pFallbackBuf), "%s_dx80.%s", pTemp, pExt );
if ( g_pFullFileSystem->FileExists( pFallbackBuf ) )
{
pFileName = pFallbackBuf;
}
}
else if ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() == 90 && g_pMaterialSystemHardwareConfig->PreferReducedFillrate() )
{
Q_snprintf( pFallbackBuf, sizeof(pFallbackBuf), "%s_dx90_slow.%s", pTemp, pExt );
if ( g_pFullFileSystem->FileExists( pFallbackBuf ) )
{
pFileName = pFallbackBuf;
}
}
*/
}
CUtlBuffer buf( 0, 0, 0 );
if ( IsX360() || IsPS3() )
{
// fell through, load as pc particle resource file
buf.ActivateByteSwapping( true );
}
if ( g_pFullFileSystem->ReadFile( pFileName, "GAME", buf ) )
{
return ReadParticleConfigFile( buf, bPrecache, bDecommitTempMemory, pFileName );
}
Warning( "Particles: Missing '%s'\n", pFileName );
return false;
}
//-----------------------------------------------------------------------------
// Write a specific particle config to a utlbuffer
//-----------------------------------------------------------------------------
bool CParticleSystemMgr::WriteParticleConfigFile( const char *pParticleSystemName, CUtlBuffer &buf, bool bPreventNameBasedLookup )
{
DECLARE_DMX_CONTEXT();
// Create DMX elements representing the particle system definition
CDmxElement *pParticleSystem = CreateParticleDmxElement( pParticleSystemName );
return WriteParticleConfigFile( pParticleSystem, buf, bPreventNameBasedLookup );
}
bool CParticleSystemMgr::WriteParticleConfigFile( const DmObjectId_t& id, CUtlBuffer &buf, bool bPreventNameBasedLookup )
{
DECLARE_DMX_CONTEXT();
// Create DMX elements representing the particle system definition
CDmxElement *pParticleSystem = CreateParticleDmxElement( id );
return WriteParticleConfigFile( pParticleSystem, buf, bPreventNameBasedLookup );
}
bool CParticleSystemMgr::WriteParticleConfigFile( CDmxElement *pParticleSystem, CUtlBuffer &buf, bool bPreventNameBasedLookup )
{
pParticleSystem->SetValue( "preventNameBasedLookup", bPreventNameBasedLookup );
CDmxAttribute* pAttribute = pParticleSystem->GetAttribute( "children" );
const CUtlVector< CDmxElement* >& children = pAttribute->GetArray<CDmxElement*>( );
int nCount = children.Count();
for ( int i = 0; i < nCount; ++i )
{
CDmxElement *pChildRef = children[ i ];
CDmxElement *pChild = pChildRef->GetValue<CDmxElement*>( "child" );
pChild->SetValue( "preventNameBasedLookup", bPreventNameBasedLookup );
}
// Now write the DMX elements out
bool bOk = SerializeDMX( buf, pParticleSystem );
CleanupDMX( pParticleSystem );
return bOk;
}
ParticleSystemHandle_t CParticleSystemMgr::GetParticleSystemIndex( const char *pParticleSystemName )
{
if ( !pParticleSystemName )
return UTL_INVAL_SYMBOL;
return m_pParticleSystemDictionary->FindParticleSystemHandle( pParticleSystemName );
}
ParticleSystemHandle_t CParticleSystemMgr::FindOrAddParticleSystemIndex( const char *pParticleSystemName )
{
if ( !pParticleSystemName )
return UTL_INVAL_SYMBOL;
return m_pParticleSystemDictionary->FindOrAddParticleSystemHandle( pParticleSystemName );
}
const char *CParticleSystemMgr::GetParticleSystemNameFromIndex( ParticleSystemHandle_t iIndex )
{
CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->FindParticleSystem( iIndex );
return pDef ? pDef->GetName() : "Unknown";
}
int CParticleSystemMgr::GetParticleSystemCount( void )
{
return m_pParticleSystemDictionary->NameCount();
}
//-----------------------------------------------------------------------------
// Factory method for creating particle collections
//-----------------------------------------------------------------------------
CParticleCollection *CParticleSystemMgr::CreateParticleCollection( const char *pParticleSystemName, float flDelay, int nRandomSeed )
{
if ( !pParticleSystemName )
return NULL;
CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->FindParticleSystem( pParticleSystemName );
if ( !pDef )
{
Warning( "Attempted to create unknown particle system type %s\n", pParticleSystemName );
return NULL;
}
CParticleCollection *pParticleCollection = new CParticleCollection;
pParticleCollection->Init( pDef, flDelay, nRandomSeed );
return pParticleCollection;
}
CParticleCollection *CParticleSystemMgr::CreateParticleCollection( ParticleSystemHandle_t particleSystemName, float flDelay, int nRandomSeed )
{
if ( particleSystemName == UTL_INVAL_SYMBOL )
return NULL;
CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->FindParticleSystem( particleSystemName );
if ( !pDef )
{
Warning( "Attempted to create unknown particle system with unknown symbol\n" );
return NULL;
}
CParticleCollection *pParticleCollection = new CParticleCollection;
pParticleCollection->Init( pDef, flDelay, nRandomSeed );
return pParticleCollection;
}
CParticleCollection *CParticleSystemMgr::CreateParticleCollection( const DmObjectId_t &id, float flDelay, int nRandomSeed )
{
if ( !IsUniqueIdValid( id ) )
return NULL;
CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->FindParticleSystem( id );
if ( !pDef )
{
char pBuf[256];
UniqueIdToString( id, pBuf, sizeof(pBuf) );
Warning( "Attempted to create unknown particle system id %s\n", pBuf );
return NULL;
}
CParticleCollection *pParticleCollection = new CParticleCollection;
pParticleCollection->Init( pDef, flDelay, nRandomSeed );
return pParticleCollection;
}
//--------------------------------------------------------------------------------
// Is a particular particle system defined?
//--------------------------------------------------------------------------------
bool CParticleSystemMgr::IsParticleSystemDefined( const DmObjectId_t &id )
{
if ( !IsUniqueIdValid( id ) )
return false;
CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->FindParticleSystem( id );
return ( pDef != NULL );
}
//--------------------------------------------------------------------------------
// Is a particular particle system defined?
//--------------------------------------------------------------------------------
bool CParticleSystemMgr::IsParticleSystemDefined( const char *pName )
{
if ( !pName || !pName[0] )
return false;
CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->FindParticleSystem( pName );
return ( pDef != NULL );
}
//--------------------------------------------------------------------------------
// Particle kill list
//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------
// Serialization-related methods
//--------------------------------------------------------------------------------
CDmxElement *CParticleSystemMgr::CreateParticleDmxElement( const DmObjectId_t &id )
{
CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->FindParticleSystem( id );
// Create DMX elements representing the particle system definition
return pDef->Write( );
}
CDmxElement *CParticleSystemMgr::CreateParticleDmxElement( const char *pParticleSystemName )
{
CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->FindParticleSystem( pParticleSystemName );
// Create DMX elements representing the particle system definition
return pDef->Write( );
}
//--------------------------------------------------------------------------------
// Particle sheets
//--------------------------------------------------------------------------------
static unsigned int s_nBaseTextureVarCache = 0;
CSheet *CParticleSystemMgr::FindOrLoadSheet( CParticleSystemDefinition *pDef, bool bTryReloading )
{
if ( !m_bShouldLoadSheets )
return NULL;
if ( !bTryReloading )
{
if ( pDef->IsSheetSymbolCached() )
{
if ( !pDef->GetSheetSymbol().IsValid() )
return NULL;
return m_SheetList[ pDef->GetSheetSymbol() ];
}
pDef->CacheSheetSymbol( UTL_INVAL_SYMBOL );
}
IMaterial *pMaterial = pDef->GetMaterial();
if ( !pMaterial )
return NULL;
IMaterialVar *pVar = pMaterial->FindVarFast( "$basetexture", &s_nBaseTextureVarCache );
if ( !pVar || !pVar->IsDefined() )
return NULL;
ITexture *pTex = pVar->GetTextureValue();
if ( !pTex || pTex->IsError() )
return NULL;
CSheet *pNewSheet = NULL;
int nCurCount = m_SheetList.GetNumStrings();
CUtlSymbol sheetName = m_SheetList.AddString( pTex->GetName() );
if ( ( sheetName < nCurCount ) && ( !bTryReloading ) )
{
// Means the string was already known
pNewSheet = m_SheetList[ sheetName ];
}
else
{
// get compact sheet representation held by texture
size_t numBytes;
void const *pSheetData = pTex->GetResourceData( VTF_RSRC_SHEET, &numBytes );
if ( pSheetData )
{
// expand compact sheet into fatter runtime form
CUtlBuffer bufLoad( pSheetData, numBytes, CUtlBuffer::READ_ONLY );
pNewSheet = new CSheet( bufLoad );
}
m_SheetList[ sheetName ] = pNewSheet;
}
pDef->CacheSheetSymbol( sheetName );
return pNewSheet;
}
void CParticleSystemMgr::FlushAllSheets( void )
{
m_SheetList.PurgeAndDeleteElements();
if ( !m_pParticleSystemDictionary )
return;
int nCount = m_pParticleSystemDictionary->Count();
for ( int i = 0; i < nCount; ++i )
{
CParticleSystemDefinition* pDef = m_pParticleSystemDictionary->GetParticleSystem( i );
pDef->InvalidateSheetSymbol();
}
nCount = m_pParticleSystemDictionary->NameCount();
for ( ParticleSystemHandle_t h = 0; h < nCount; ++h )
{
CParticleSystemDefinition* pDef = m_pParticleSystemDictionary->FindParticleSystem( h );
pDef->InvalidateSheetSymbol();
}
}
void CParticleSystemMgr::ShouldLoadSheets( bool bLoadSheets )
{
// Client loads sheets for rendering, server doesn't need to.
m_bShouldLoadSheets = bLoadSheets;
}
//-----------------------------------------------------------------------------
// Render cache
//-----------------------------------------------------------------------------
void CParticleSystemMgr::ResetRenderCache( void )
{
int nCount = m_RenderCache.Count();
for ( int i = 0; i < nCount; ++i )
{
m_RenderCache[i].m_ParticleCollections.RemoveAll();
}
}
void CParticleSystemMgr::AddToRenderCache( CParticleCollection *pParticles )
{
if ( !pParticles->IsValid() )
return;
IMaterial *pMaterial = pParticles->m_pDef->GetMaterial();
if ( ( !pMaterial) || pMaterial->IsTranslucent() )
return;
pParticles->m_flNextSleepTime = MAX( pParticles->m_flNextSleepTime, ( g_pParticleSystemMgr->GetLastSimulationTime() + pParticles->m_pDef->m_flNoDrawTimeToGoToSleep ));
// Find the current rope list.
int iRenderCache = 0;
int nRenderCacheCount = m_RenderCache.Count();
for ( ; iRenderCache < nRenderCacheCount; ++iRenderCache )
{
if ( ( pParticles->m_pDef->GetMaterial() == m_RenderCache[iRenderCache].m_pMaterial ) )
break;
}
// A full rope list should have been generate in CreateRenderCache
// If we didn't find one, then allocate the mofo.
if ( iRenderCache == nRenderCacheCount )
{
iRenderCache = m_RenderCache.AddToTail();
m_RenderCache[iRenderCache].m_pMaterial = pParticles->m_pDef->GetMaterial();
}
m_RenderCache[iRenderCache].m_ParticleCollections.AddToTail( pParticles );
for( CParticleCollection *p = pParticles->m_Children.m_pHead; p; p = p->m_pNext )
{
AddToRenderCache( p );
}
}
void CParticleSystemMgr::BuildBatchList( int iRenderCache, IMatRenderContext *pRenderContext, CUtlVector< Batch_t >& batches )
{
batches.RemoveAll();
IMaterial *pMaterial = m_RenderCache[iRenderCache].m_pMaterial;
int nMaxVertices = pRenderContext->GetMaxVerticesToRender( pMaterial );
int nMaxIndices = pRenderContext->GetMaxIndicesToRender();
int nRemainingVertices = nMaxVertices;
int nRemainingIndices = nMaxIndices;
int i = batches.AddToTail();
Batch_t* pBatch = &batches[i];
pBatch->m_nVertCount = 0;
pBatch->m_nIndexCount = 0;
// Ask each renderer about the # of verts + ints it will draw
int nCacheCount = m_RenderCache[iRenderCache].m_ParticleCollections.Count();
for ( int iCache = 0; iCache < nCacheCount; ++iCache )
{
CParticleCollection *pParticles = m_RenderCache[iRenderCache].m_ParticleCollections[iCache];
if ( !pParticles->IsValid() )
continue;
int nRenderCount = pParticles->GetRendererCount();
for ( int j = 0; j < nRenderCount; ++j )
{
int nFirstParticle = 0;
while ( nFirstParticle < pParticles->m_nActiveParticles )
{
int i;
BatchStep_t step;
step.m_pParticles = pParticles;
step.m_pRenderer = pParticles->GetRenderer( j );
step.m_pContext = pParticles->GetRendererContext( j );
step.m_nFirstParticle = nFirstParticle;
step.m_nParticleCount = step.m_pRenderer->GetParticlesToRender( pParticles,
step.m_pContext, nFirstParticle, nRemainingVertices, nRemainingIndices, &step.m_nVertCount, &i );
nFirstParticle += step.m_nParticleCount;
if ( step.m_nParticleCount > 0 )
{
pBatch->m_nVertCount += step.m_nVertCount;
pBatch->m_nIndexCount += i;
pBatch->m_BatchStep.AddToTail( step );
Assert( pBatch->m_nVertCount <= nMaxVertices && pBatch->m_nIndexCount <= nMaxIndices );
}
else
{
if ( pBatch->m_nVertCount == 0 )
break;
// Not enough room
Assert( pBatch->m_nVertCount > 0 && pBatch->m_nIndexCount > 0 );
int j = batches.AddToTail();
pBatch = &batches[j];
pBatch->m_nVertCount = 0;
pBatch->m_nIndexCount = 0;
nRemainingVertices = nMaxVertices;
nRemainingIndices = nMaxIndices;
}
}
}
}
if ( pBatch->m_nVertCount <= 0 || pBatch->m_nIndexCount <= 0 )
{
batches.FastRemove( batches.Count() - 1 );
}
}
void CParticleSystemMgr::DumpParticleList( const char *pNameSubstring /* = NULL */ )
{
if ( pNameSubstring )
{
DevMsg( "New Particle Systems Matching '%s':\n", pNameSubstring );
}
else
{
DevMsg( "All Particle Systems:\n" );
}
for ( int i = 0; i < m_pParticleSystemDictionary->NameCount(); i++ )
{
CParticleSystemDefinition *p = ( *m_pParticleSystemDictionary )[ i ];
if ( !pNameSubstring || V_stristr(p->GetName(),pNameSubstring) )
{
for ( CParticleCollection *c = p->FirstCollection(); c; c = c->GetNextCollectionUsingSameDef() )
{
Vector min,max,center;
c->GetBounds( &min, &max );
center = (min+max)*0.5f;
DevMsg( "%40s: Age: %6.2f, NumActive: %3d, Bounds Center: (%.2f,%.2f,%.2f) (0x%p)\n", p->GetName(), c->m_flCurTime, c->m_nActiveParticles, center.x, center.y, center.z, c );
}
}
}
}
void CParticleSystemMgr::DumpProfileInformation( void )
{
#if MEASURE_PARTICLE_PERF
int nTotalTests = 0;
int nActualTests = 0;
FileHandle_t fh = g_pFullFileSystem->Open( "particle_profile.csv", "w", "DEFAULT_WRITE_PATH" );
if ( fh == FILESYSTEM_INVALID_HANDLE )
{
Warning( "*** Unable to open profile file!\n" );
return;
}
g_pFullFileSystem->FPrintf( fh, "numframes,%d\n", m_nNumFramesMeasured );
g_pFullFileSystem->FPrintf( fh, "name, total time, max time, max particles, allocated particles, total render time, max render time, number of intersection tests, number of actual traces\n");
for( int i=0; i < m_pParticleSystemDictionary->NameCount(); i++ )
{
CParticleSystemDefinition *p = ( *m_pParticleSystemDictionary )[ i ];
if ( p->m_nMaximumActiveParticles )
{
nTotalTests += p->m_nNumIntersectionTests;
nActualTests +=p->m_nNumActualRayTraces;
g_pFullFileSystem->FPrintf( fh, "%s,%f,%f,%d,%d,%f,%f,%d,%d\n", p->m_Name.Get(),
p->m_flTotalSimTime, p->m_flMaxMeasuredSimTime, p->m_nMaximumActiveParticles,
p->m_nMaxParticles, p->m_flTotalRenderTime, p->m_flMaxMeasuredRenderTime, p->m_nNumIntersectionTests(), p->m_nNumActualRayTraces() );
}
}
if ( nTotalTests )
{
g_pFullFileSystem->FPrintf( fh, "\n\nTrace cache efficiency = %f%%\n\n", 100.0 * ( 1.0 - ( nActualTests * ( 1.0 / nTotalTests ) ) ) );
}
g_pFullFileSystem->FPrintf( fh, "\n\nopname, total time, max time, total render time, max render time\n");
for(int i=0; i < ARRAYSIZE( m_ParticleOperators ); i++)
{
for(int j=0; j < m_ParticleOperators[i].Count() ; j++ )
{
float flmax = m_ParticleOperators[i][j]->MaximumRecordedExecutionTime();
float fltotal = m_ParticleOperators[i][j]->TotalRecordedExecutionTime();
if ( fltotal > 0.0 )
{
g_pFullFileSystem->FPrintf( fh, "%s,%f,%f\n",
m_ParticleOperators[i][j]->GetName(), fltotal, flmax );
}
}
}
g_pFullFileSystem->Close( fh );
#endif
}
void CParticleSystemMgr::CommitProfileInformation( bool bCommit )
{
#if MEASURE_PARTICLE_PERF
if ( 1 )
{
if ( bCommit )
{
m_nNumFramesMeasured++;
}
for( int i=0; i < m_pParticleSystemDictionary->NameCount(); i++ )
{
CParticleSystemDefinition *p = ( *m_pParticleSystemDictionary )[ i ];
if ( bCommit )
{
p->m_flTotalSimTime += p->m_flUncomittedTotalSimTime;
p->m_flTotalRenderTime += p->m_flUncomittedTotalRenderTime;
}
p->m_flUncomittedTotalSimTime = 0.;
p->m_flUncomittedTotalRenderTime = 0.;
}
for(int i=0; i < ARRAYSIZE( m_ParticleOperators ); i++)
{
for(int j=0; j < m_ParticleOperators[i].Count() ; j++ )
{
if ( bCommit )
{
m_ParticleOperators[i][j]->m_flTotalExecutionTime += m_ParticleOperators[i][j]->m_flUncomittedTime;
}
m_ParticleOperators[i][j]->m_flUncomittedTime = 0;
}
}
}
#endif
}
void CParticleSystemMgr::DrawRenderCache( IMatRenderContext *pRenderContext, bool bShadowDepth )
{
int nRenderCacheCount = m_RenderCache.Count();
if ( nRenderCacheCount == 0 )
return;
VPROF_BUDGET( "CParticleSystemMgr::DrawRenderCache", VPROF_BUDGETGROUP_PARTICLE_RENDERING );
pRenderContext->MatrixMode( MATERIAL_MODEL );
pRenderContext->PushMatrix();
pRenderContext->LoadIdentity();
CUtlVector< Batch_t > batches( 0, 8 );
for ( int iRenderCache = 0; iRenderCache < nRenderCacheCount; ++iRenderCache )
{
int nCacheCount = m_RenderCache[iRenderCache].m_ParticleCollections.Count();
if ( nCacheCount == 0 )
continue;
// FIXME: When rendering shadow depth, do it all in 1 batch
IMaterial *pMaterial = bShadowDepth ? m_pShadowDepthMaterial : m_RenderCache[iRenderCache].m_pMaterial;
BuildBatchList( iRenderCache, pRenderContext, batches );
int nBatchCount = batches.Count();
if ( nBatchCount == 0 )
continue;
pRenderContext->Bind( pMaterial );
CMeshBuilder meshBuilder;
IMesh* pMesh = pRenderContext->GetDynamicMesh( );
for ( int i = 0; i < nBatchCount; ++i )
{
const Batch_t& batch = batches[i];
Assert( batch.m_nVertCount > 0 && batch.m_nIndexCount > 0 );
meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, batch.m_nVertCount, batch.m_nIndexCount );
int nVertexOffset = 0;
int nBatchStepCount = batch.m_BatchStep.Count();
for ( int j = 0; j < nBatchStepCount; ++j )
{
const BatchStep_t &step = batch.m_BatchStep[j];
// FIXME: this will break if it ever calls into C_OP_RenderSprites::Render[TwoSequence]SpriteCard()
// (need to protect against that and/or split the meshBuilder batch to support that path here)
step.m_pRenderer->RenderUnsorted( step.m_pParticles, step.m_pContext, pRenderContext,
meshBuilder, nVertexOffset, step.m_nFirstParticle, step.m_nParticleCount );
nVertexOffset += step.m_nVertCount;
}
meshBuilder.End();
pMesh->Draw();
}
}
ResetRenderCache( );
pRenderContext->MatrixMode( MATERIAL_MODEL );
pRenderContext->PopMatrix();
}
void IParticleSystemQuery::GetRandomPointsOnControllingObjectHitBox(
CParticleCollection *pParticles,
int nControlPointNumber,
int nNumPtsOut,
float flBBoxScale,
int nNumTrysToGetAPointInsideTheModel,
Vector *pPntsOut,
Vector vecDirectionalBias,
Vector *pHitBoxRelativeCoordOut,
int *pHitBoxIndexOut,
int nDesiredHitbox,
const char *pszHitboxSetName
)
{
for(int i=0; i < nNumPtsOut; i++)
{
pPntsOut[i]=pParticles->ControlPoint( nControlPointNumber ).m_Position;
if ( pHitBoxRelativeCoordOut )
pHitBoxRelativeCoordOut[i].Init();
if ( pHitBoxIndexOut )
pHitBoxIndexOut[i] = -1;
}
}
void CParticleCollection::UpdateHitBoxInfo( int nControlPointNumber, const char *pszHitboxSetName )
{
CModelHitBoxesInfo &hb = ControlPointHitBox( nControlPointNumber );
if ( hb.m_flLastUpdateTime == m_flCurTime )
return; // up to date
hb.m_flLastUpdateTime = m_flCurTime;
// make sure space allocated
if ( ! hb.m_pHitBoxes )
hb.m_pHitBoxes = new ModelHitBoxInfo_t[ MAXSTUDIOBONES ];
if ( ! hb.m_pPrevBoxes )
hb.m_pPrevBoxes = new ModelHitBoxInfo_t[ MAXSTUDIOBONES ];
// save current into prev
hb.m_nNumPrevHitBoxes = hb.m_nNumHitBoxes;
hb.m_flPrevLastUpdateTime = hb.m_flLastUpdateTime;
V_swap( hb.m_pHitBoxes, hb.m_pPrevBoxes );
// issue hitbox query
hb.m_nNumHitBoxes = g_pParticleSystemMgr->Query()->GetControllingObjectHitBoxInfo(
this, nControlPointNumber, MAXSTUDIOBONES, hb.m_pHitBoxes, pszHitboxSetName );
}
void GetParticleManifest( CUtlVector<CUtlString>& list )
{
GetParticleManifest( list, "particles/particles_manifest.txt" );
}
void GetParticleManifest( CUtlVector<CUtlString>& list, const char *pFile )
{
// Open the manifest file, and read the particles specified inside it
KeyValues *manifest = new KeyValues( pFile );
if ( manifest->LoadFromFile( g_pFullFileSystem, pFile, "GAME" ) )
{
for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() )
{
if ( !Q_stricmp( sub->GetName(), "file" ) )
{
list.AddToTail( sub->GetString() );
continue;
}
Warning( "CParticleMgr::Init: Manifest '%s' with bogus file type '%s', expecting 'file'\n", pFile, sub->GetName() );
}
}
else
{
Warning( "PARTICLE SYSTEM: Unable to load manifest file '%s'\n", pFile );
}
manifest->deleteThis();
}