source-engine/particles/particles.cpp

3882 lines
122 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: particle system code
//
//===========================================================================//
#include "tier0/platform.h"
#include "particles/particles.h"
#include "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"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// rename table from the great rename
static const 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;
}
// This is the soft limit - if we exceed this, we spit out a report of all the particles in the frame
#define MAX_PARTICLE_VERTS 50000
// These are some limits that control g_pParticleSystemMgr->ParticleThrottleScaling() and g_pParticleSystemMgr->ParticleThrottleRandomEnable()
//ConVar cl_particle_scale_lower ( "cl_particle_scale_lower", "20000", FCVAR_CLIENTDLL | FCVAR_CHEAT );
//ConVar cl_particle_scale_upper ( "cl_particle_scale_upper", "40000", FCVAR_CLIENTDLL | FCVAR_CHEAT );
#define CL_PARTICLE_SCALE_LOWER 20000
#define CL_PARTICLE_SCALE_UPPER 40000
//-----------------------------------------------------------------------------
// Default implementation of particle system mgr
//-----------------------------------------------------------------------------
static CParticleSystemMgr s_ParticleSystemMgr;
CParticleSystemMgr *g_pParticleSystemMgr = &s_ParticleSystemMgr;
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 );
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 );
}
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 )
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 bool IsObsolete() const { return false; }
virtual size_t GetClassSize() const { return 0; }
};
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_STRING_USERDATA( "material", "vgui/white", m_pszMaterialName, "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_STRING( "cull_replacement_definition", "", m_pszCullReplacementName )
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( "sequence_number", "0", int, m_nConstantSequenceNumber )
DMXELEMENT_UNPACK_FIELD( "sequence_number 1", "0", int, m_nConstantSequenceNumber1 )
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( "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 )
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();
}
}
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 SWDS
m_Material.Init( MaterialName(), TEXTURE_GROUP_OTHER, true );
#endif
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 );
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, flist[j]->GetClassSize(), pUnpack );
}
pNewRef->InitParams( this, pElement );
m_nAttributeReadMask |= pNewRef->GetReadAttributes();
m_nControlPointReadMask |= pNewRef->GetReadControlPointMask();
switch( nFunctionType )
{
case FUNCTION_INITIALIZER:
case FUNCTION_EMITTER:
m_nPerParticleInitializedAttributeMask |= pNewRef->GetWrittenAttributes();
Assert( pNewRef->GetReadInitialAttributes() == 0 );
break;
case FUNCTION_OPERATOR:
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], sizeof( 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 = 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, sizeof( *this ), s_pParticleSystemDefinitionUnpack );
#ifndef SWDS // avoid material/ texture load
// NOTE: This makes a X appear for uncached particles.
// m_Material.Init( "debug/particleerror", TEXTURE_GROUP_OTHER, true );
#endif
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 m_Material;
}
//----------------------------------------------------------------------------------
// 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 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 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( 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 )
{
// calcuate 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;
}
//-----------------------------------------------------------------------------
//
// 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_AllocAligned( 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_AllocAligned( 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 );
m_pNextDef = m_pPrevDef = NULL;
m_nUniqueParticleId = 0;
m_nRandomQueryCount = 0;
m_bIsScrubbable = false;
m_bIsRunningInitializers = false;
m_bIsRunningOperators = false;
m_bIsTranslucent = false;
m_bIsTwoPass = false;
m_bIsBatchable = false;
m_bUsesPowerOfTwoFrameBufferTexture = false;
m_bUsesFullFrameBufferTexture = false;
m_pRenderOp = NULL;
m_nControlPointReadMask = 0;
m_flLastMinDistSqr = m_flLastMaxDistSqr = 0.0f;
m_flMinDistSqr = m_flMaxDistSqr = 0.0f;
m_flOOMaxDistSqr = 1.0f;
m_vecLastCameraPos.Init();
m_MinBounds.Init();
m_MaxBounds.Init();
m_bBoundsValid = false;
memset( m_ControlPoints, 0, sizeof(m_ControlPoints) );
// align all control point orientations with the global world
for( int i=0; i < MAX_PARTICLE_CONTROL_POINTS; i++ )
{
m_ControlPoints[i].m_ForwardVector.Init( 0, 1, 0 );
m_ControlPoints[i].m_UpVector.Init( 0, 0, 1 );
m_ControlPoints[i].m_RightVector.Init( 1, 0, 0 );
}
memset( m_pParticleInitialAttributes, 0, sizeof(m_pParticleInitialAttributes) );
m_nPerParticleUpdatedAttributeMask = 0;
m_nPerParticleInitializedAttributeMask = 0;
m_nPerParticleReadInitialAttributeMask = 0;
m_pParticleMemory = NULL;
m_pParticleInitialMemory = NULL;
m_pConstantMemory = NULL;
m_nActiveParticles = 0;
m_nPaddedActiveParticles = 0;
m_flCurTime = 0.0f;
m_fl4CurTime = Four_Zeros;
m_flDt = 0.0f;
m_flPreviousDt = 0.05f;
m_nParticleFlags = PCFLAGS_FIRST_FRAME;
m_pOperatorContextData = NULL;
m_pNext = m_pPrev = NULL;
m_nRandomSeed = 0;
m_pDef = NULL;
m_nAllocatedParticles = 0;
m_nMaxAllowedParticles = 0;
m_bDormant = false;
m_bEmissionStopped = false;
m_bRequiresOrderInvariance = false;
m_nSimulatedFrames = 0;
m_nNumParticlesToKill = 0;
m_pParticleKillList = NULL;
m_nHighestCP = 0;
memset( m_pCollisionCacheData, 0, sizeof( m_pCollisionCacheData ) );
m_pParent = NULL;
m_LocalLighting = Color(255, 255, 255, 255);
m_LocalLightingCP = -1;
}
CParticleCollection::~CParticleCollection( void )
{
UnlinkFromDefList();
m_Children.Purge();
if ( m_pParticleMemory )
{
delete[] m_pParticleMemory;
}
if ( m_pParticleInitialMemory )
{
delete[] m_pParticleInitialMemory;
}
if ( m_pConstantMemory )
{
delete[] m_pConstantMemory;
}
if ( m_pOperatorContextData )
{
MemAlloc_FreeAligned( m_pOperatorContextData );
}
for( int i = 0 ; i < ARRAYSIZE( m_pCollisionCacheData ) ; i++ )
{
if ( m_pCollisionCacheData[i] )
{
delete m_pCollisionCacheData[i];
}
}
}
//-----------------------------------------------------------------------------
// Initialization
//-----------------------------------------------------------------------------
void CParticleCollection::Init( CParticleSystemDefinition *pDef, float flDelay, int nRandomSeed )
{
m_pDef = pDef;
// Link into def list
LinkIntoDefList();
InitStorage( pDef );
// Initialize sheet data
m_Sheet.Set( g_pParticleSystemMgr->FindOrLoadSheet( pDef->GetMaterial() ) );
// FIXME: This seed needs to be recorded per instance!
m_bIsScrubbable = ( nRandomSeed != 0 );
if ( m_bIsScrubbable )
{
m_nRandomSeed = nRandomSeed;
}
else
{
m_nRandomSeed = (int)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.g() / 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 );
SetAttributeToConstant( PARTICLE_ATTRIBUTE_ALPHA2, 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;
// 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] );
}
}
m_nControlPointReadMask = pDef->m_nControlPointReadMask;
// 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;
}
}
if ( !IsValid() )
return;
m_bIsTranslucent = ComputeIsTranslucent();
m_bIsTwoPass = ComputeIsTwoPass();
m_bIsBatchable = ComputeIsBatchable();
LabelTextureUsage();
m_bAnyUsesPowerOfTwoFrameBufferTexture = ComputeUsesPowerOfTwoFrameBufferTexture();
m_bAnyUsesFullFrameBufferTexture = ComputeUsesFullFrameBufferTexture();
m_bRequiresOrderInvariance = ComputeRequiresOrderInvariance();
}
//-----------------------------------------------------------------------------
// Used by client code
//-----------------------------------------------------------------------------
bool CParticleCollection::Init( CParticleSystemDefinition *pDef )
{
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 );
}
//-----------------------------------------------------------------------------
// List management for collections sharing the same particle definition
//-----------------------------------------------------------------------------
void CParticleCollection::LinkIntoDefList( )
{
Assert( !m_pPrevDef && !m_pNextDef );
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;
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
}
//-----------------------------------------------------------------------------
// Determine if this particle has moved since the last time it was simulated,
// which will let us know if the bbox needs to be updated.
//-----------------------------------------------------------------------------
bool CParticleCollection::HasMoved() const
{
// It's weird that this is possible, but it apparently is (see the many other functions that
// check).
if ( !m_pDef )
return false;
Vector prevCP;
for ( int i = 0; i <= m_nHighestCP; ++i )
{
if ( !m_pDef->ReadsControlPoint( i ) )
continue;
GetControlPointAtPrevTime( i, &prevCP );
if ( prevCP != GetControlPointAtCurrentTime( i ) )
{
return true;
}
}
for ( CParticleCollection *child = m_Children.m_pHead; child; child = child->m_pNext )
{
if ( child->HasMoved() )
{
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
// 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 = new unsigned char[nConstantMemorySize];
m_pConstantAttributes = (float*)( (size_t)( m_pConstantMemory + 15 ) & ~0xF );
// We have to zero-init the memory so that any attributes that are not initialized
// get predictable and sensible values.
memset( m_pConstantMemory, 0, nConstantMemorySize );
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
int nAllocationSize = m_nAllocatedParticles * sz * sizeof(float) + 16;
m_pParticleMemory = new unsigned char[ nAllocationSize ];
memset( m_pParticleMemory, 0, nAllocationSize );
// Allocate space for the initial attributes
if ( nInitialAttributeSize != 0 )
{
int nInitialAllocationSize = m_nAllocatedParticles * nInitialAttributeSize * sizeof(float) + 16;
m_pParticleInitialMemory = new unsigned char[ nInitialAllocationSize ];
memset( m_pParticleInitialMemory, 0, nInitialAllocationSize );
}
// Align allocation to 16-byte boundaries
float *pMem = (float*)( (size_t)( m_pParticleMemory + 15 ) & ~0xF );
float *pInitialMem = (float*)( (size_t)( m_pParticleInitialMemory + 15 ) & ~0xF );
// 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_pParticleAttributes[ bit ] = pMem;
m_nParticleFloatStrides[ bit ] = nAttrSize * 4;
pMem += nAttrSize * m_nAllocatedParticles;
}
else
{
m_pParticleAttributes[ bit ] = GetConstantAttributeMemory( bit );
m_nParticleFloatStrides[ bit ] = 0;
}
// Are we reading
if ( pDef->m_nInitialAttributeReadMask & ( 1 << bit ) )
{
if ( m_nPerParticleReadInitialAttributeMask & ( 1 << bit ) )
{
Assert( pInitialMem );
m_pParticleInitialAttributes[ bit ] = pInitialMem;
m_nParticleInitialFloatStrides[ bit ] = nAttrSize * 4;
pInitialMem += nAttrSize * m_nAllocatedParticles;
}
else if ( nPerParticleReadConstantAttributeMask & ( 1 << bit ) )
{
m_pParticleInitialAttributes[ bit ] = m_pParticleAttributes[ bit ];
m_nParticleInitialFloatStrides[ bit ] = m_nParticleFloatStrides[ bit ];
}
else
{
m_pParticleInitialAttributes[ bit ] = GetConstantAttributeMemory( bit );
m_nParticleInitialFloatStrides[ bit ] = 0;
}
}
else
{
// Catch errors where code is reading data it didn't request
m_pParticleInitialAttributes[ bit ] = NULL;
m_nParticleInitialFloatStrides[ 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;
if ( !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;
if ( 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;
}
//-----------------------------------------------------------------------------
// Does this system require order invariance of the particles?
//-----------------------------------------------------------------------------
bool CParticleCollection::ComputeRequiresOrderInvariance()
{
const int nRendererCount = GetRendererCount();
for( int i = 0; i < nRendererCount; i++ )
{
if ( GetRenderer( i )->RequiresOrderInvariance() )
return true;
}
for (CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext)
{
if ( p->m_bRequiresOrderInvariance )
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)
{
float flTime=m_flCurTime;
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 ( pflCurStrength )
*pflCurStrength = flStrength;
return ( flStrength > 0.0 );
}
//-----------------------------------------------------------------------------
// Restarts a particle system
//-----------------------------------------------------------------------------
void CParticleCollection::Restart()
{
int i;
int nEmitterCount = m_pDef->m_Emitters.Count();
for( i = 0; i < nEmitterCount; i++ )
{
m_pDef->m_Emitters[i]->Restart( this, m_pOperatorContextData + m_pDef->m_nEmittersCtxOffsets[i] );
}
// Update all children
CParticleCollection *pChild;
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->Restart( );
}
}
//-----------------------------------------------------------------------------
// Main entry point for rendering
//-----------------------------------------------------------------------------
void CParticleCollection::Render( IMatRenderContext *pRenderContext, bool bTranslucentOnly, void *pCameraObject )
{
if ( !IsValid() )
return;
m_flNextSleepTime = Max ( m_flNextSleepTime, ( g_pParticleSystemMgr->GetLastSimulationTime() + m_pDef->m_flNoDrawTimeToGoToSleep ));
if ( m_nActiveParticles != 0 )
{
if ( !bTranslucentOnly || m_pDef->GetMaterial()->IsTranslucent() )
{
int nCount = m_pDef->m_Renderers.Count();
for( int i = 0; i < nCount; i++ )
{
if ( CheckIfOperatorShouldRun( m_pDef->m_Renderers[i] ) )
{
// 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, m_pOperatorContextData + m_pDef->m_nRenderersCtxOffsets[i] );
// pRenderContext->MatrixMode( MATERIAL_VIEW );
// pRenderContext->PopMatrix();
// pRenderContext->MatrixMode( MATERIAL_PROJECTION );
// pRenderContext->PopMatrix();
}
}
}
}
// let children render
for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext )
{
p->Render( pRenderContext, 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++ )
m_ControlPoints[i].m_PrevPosition = m_ControlPoints[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 )
{
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;
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;
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 );
}
}
}
#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 *xyz_prev = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_PREV_XYZ, i );
/*
const float *rad = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_RADIUS, i );
Assert( IsFinite( rad[0] ) );
RJ: Disabling this assert. While the proper way is to fix the math which leads to the bad number, the fix would result in more particles being drawn and in a post shipping world, users were not happy.
See Changelists #1368648, #1368635, and #1368434 for proper math calculation fixes.
In a post shipping world, as these particles would not render with infinites, code was added C_OP_RenderSprites::RenderSpriteCard() to check for the infinite and not add the vert to meshbuilder.
*/
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::SimulateFirstFrame( )
{
m_flDt = 0.0f;
m_nDrawnFrames = 0;
m_nSimulatedFrames = 1;
// 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;
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::Simulate( float dt, bool updateBboxOnly )
{
VPROF_BUDGET( "CParticleCollection::Simulate", VPROF_BUDGETGROUP_PARTICLE_SIMULATION );
if ( dt < 0.0f )
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;
}
if ( dt < 1.0e-22 )
return;
#if MEASURE_PARTICLE_PERF
float flStartSimTime = Plat_FloatTime();
#endif
bool bAttachedKillList = false;
if (!HasAttachedKillList())
{
g_pParticleSystemMgr->AttachKillList(this);
bAttachedKillList = true;
}
if (!updateBboxOnly)
{
++m_nSimulatedFrames;
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
while( flRemainingDt > 0.0 )
{
float flDT_ThisStep = min( flRemainingDt, flMaxDT );
flRemainingDt -= flDT_ThisStep;
m_flDt = flDT_ThisStep;
m_flCurTime += flDT_ThisStep;
m_fl4CurTime = ReplicateX4( m_flCurTime );
#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
int nEmitterCount = m_pDef->m_Emitters.Count();
for( int i=0; i < nEmitterCount; i++ )
{
int nOldParticleCount = m_nActiveParticles;
float flEmitStrength;
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 );
}
}
}
m_nOperatorRandomSampleOffset = 0;
nCount = m_pDef->m_Operators.Count();
if ( m_nActiveParticles )
{
#ifdef FP_EXCEPTIONS_ENABLED
const int processedParticles = m_nPaddedActiveParticles * 4;
for ( int unusedParticle = m_nActiveParticles; unusedParticle < processedParticles; ++unusedParticle )
{
// Set the unused-but-processed particle lifetimes to a value that
// won't cause division by zero or other madness. This allows us
// to enable floating-point exceptions during particle processing,
// which helps us to find bugs.
float *dtime = GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_LIFE_DURATION, unusedParticle );
*dtime = 1.0f;
}
#endif
#ifdef _DEBUG
m_bIsRunningOperators = true;
#endif
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
}
}
#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, updateBboxOnly);
i->m_pParticleKillList = NULL;
}
if (bAttachedKillList)
g_pParticleSystemMgr->DetachKillList(this);
UpdatePrevControlPoints(dt);
// Bloat the bounding box by bounds around the control point
BloatBoundsUsingControlPoint();
}
//-----------------------------------------------------------------------------
// 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_nParticleFloatStrides[nAttr] != 0 );
float *pConstantAttr = GetConstantAttributeMemory( nAttr );
*pAttrData = *pConstantAttr;
if ( m_nParticleFloatStrides[nAttr] == 12 )
{
pAttrData[4] = pConstantAttr[4];
pAttrData[8] = pConstantAttr[8];
}
}
}
}
void CParticleCollection::CopyInitialAttributeValues( int nStartParticle, int nNumParticles )
{
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_nParticleInitialFloatStrides[nAttr] != 0 );
Assert( m_nParticleFloatStrides[nAttr] == m_nParticleInitialFloatStrides[nAttr] );
*pDestAttribute = *pSrcAttribute;
if ( m_nParticleFloatStrides[nAttr] == 12 )
{
pDestAttribute[4] = pSrcAttribute[4];
pDestAttribute[8] = pSrcAttribute[8];
}
}
}
}
}
//-----------------------------------------------------------------------------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 ) const
{
Assert( m_pDef->ReadsControlPoint( nControlPoint ) );
if ( nControlPoint > GetHighestControlPoint() )
{
DevWarning(2, "Warning : Particle system (%s) using unassigned ControlPoint %d!\n", GetName(), nControlPoint );
}
if ( m_flDt == 0.0f )
{
VectorCopy( m_ControlPoints[nControlPoint].m_Position, *pControlPoint );
return;
}
// The original calculation for 't' was this:
// float flPrevTime = m_flCurTime - m_flDt;
// float t = ( flTime - flPrevTime ) / m_flDt;
// Which is mathematically equivalent to this:
// float t = ( flTime - ( m_flCurTime - m_flDt ) ) / m_flDt;
// However if m_flCurTime and flTime are large then significant precision
// is lost during subtraction -- catastrophic cancellation
// is the technical term. This starts out being an error of one part in
// ten million, but after running for just a few minutes it increases to
// one part in ten thousand -- and continues to get worse.
// This calculation even fails in the simple case where flTime == m_flCurTime,
// giving an answer that is not 1.0 and may be out of range.
// If the calculation is arranged as shown below then, because flTime and
// m_flCurTime are close to each other, the subtraction loses *no* precision.
// The subtraction will not necessarily be 'correct', since eventually flTime
// and m_flCurTime will not have enough precision, but it will give results
// that are as accurate as possible given the inputs.
// flHowLongAgo stores how far before the current time flTime is.
const float flHowLongAgo = m_flCurTime - flTime;
float t = ( m_flDt - flHowLongAgo ) / m_flDt;
// The original code had a comment saying:
// Precision errors can cause this problem
// in regards to issues that can cause t to go negative. Actually this function
// is just sometimes called (from InitNewParticlesScalar) with values that cause
// 't' to go massively negative. So I clamp it.
if ( t < 0.0f )
t = 0.0f;
Assert( t <= 1.0f );
VectorLerp( m_ControlPoints[nControlPoint].m_PrevPosition, m_ControlPoints[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 ) const
{
Assert( m_pDef->ReadsControlPoint( nControlPoint ) );
*pControlPoint = m_ControlPoints[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( m_ControlPoints[nControlPoint].m_RightVector, -1.0f, left );
pMat->Init( m_ControlPoints[nControlPoint].m_ForwardVector, left, m_ControlPoints[nControlPoint].m_UpVector, vecControlPoint );
}
void CParticleCollection::GetControlPointTransformAtCurrentTime( int nControlPoint, VMatrix *pMat )
{
GetControlPointTransformAtCurrentTime( nControlPoint, const_cast<matrix3x4_t *> ( &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 = m_ControlPoints[nControlPoint].m_ForwardVector;
*pRight = m_ControlPoints[nControlPoint].m_RightVector;
*pUp = m_ControlPoints[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( m_ControlPoints[nControlPoint].m_RightVector, -1.0f, left );
pMat->Init( m_ControlPoints[nControlPoint].m_ForwardVector, left, m_ControlPoints[nControlPoint].m_UpVector, vecControlPoint );
}
void CParticleCollection::GetControlPointTransformAtTime( int nControlPoint, float flTime, VMatrix *pMat )
{
GetControlPointTransformAtTime( nControlPoint, flTime, const_cast< matrix3x4_t * > ( &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_v4Fwd.DuplicateVector( m_ControlPoints[nControlPoint].m_ForwardVector );
pXForm->m_v4Up.DuplicateVector( m_ControlPoints[nControlPoint].m_UpVector );
//Vector left;
//VectorMultiply( m_ControlPoints[nControlPoint].m_RightVector, -1.0f, left );
pXForm->m_v4Right.DuplicateVector( m_ControlPoints[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();
// Don't do the bounding box fixup until after the second simulation (first real simulation)
// so that we know they're in their correct position.
if ( m_nSimulatedFrames > 2 )
{
// Include control points in the bbox.
for (int i = 0; i <= m_nHighestCP; ++i) {
if ( !m_pDef->ReadsControlPoint( i ) )
continue;
const Vector& cp = GetControlPointAtCurrentTime(i);
VectorMin( m_MinBounds, cp, m_MinBounds );
VectorMax( m_MaxBounds, cp, m_MaxBounds );
}
}
// Deal with children
// NOTE: Bounds have been recomputed for children prior to this call in Simulate
Vector vecMins, vecMaxs;
for( CParticleCollection *i = m_Children.m_pHead; i; i = i->m_pNext )
{
i->GetBounds( &vecMins, &vecMaxs );
VectorMin( m_MinBounds, vecMins, m_MinBounds );
VectorMax( m_MaxBounds, vecMaxs, m_MaxBounds );
}
}
//-----------------------------------------------------------------------------
// Recomputes the bounds
//-----------------------------------------------------------------------------
void CParticleCollection::RecomputeBounds( void )
{
if ( m_nActiveParticles == 0.0f )
{
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;
size_t xyz_stride;
const fltx4 *xyz = GetM128AttributePtr( PARTICLE_ATTRIBUTE_XYZ, &xyz_stride );
int ctr = m_nActiveParticles/4;
while ( ctr-- )
{
min_x = MinSIMD( min_x, xyz[0] );
max_x = MaxSIMD( max_x, xyz[0] );
sum_x = AddSIMD( sum_x, xyz[0] );
min_y = MinSIMD( min_y, xyz[1] );
max_y = MaxSIMD( max_y, xyz[1] );
sum_y = AddSIMD( sum_y, xyz[1] );
min_z = MinSIMD( min_z, xyz[2] );
max_z = MaxSIMD( max_z, xyz[2] );
sum_z = AddSIMD( 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++)
{
m_MinBounds.x = min( m_MinBounds.x, SubFloat( xyz[0], i ) );
m_MaxBounds.x = max( m_MaxBounds.x, SubFloat( xyz[0], i ) );
fsum_x += SubFloat( xyz[0], i );
m_MinBounds.y = min( m_MinBounds.y, SubFloat( xyz[1], i ) );
m_MaxBounds.y = max( m_MaxBounds.y, SubFloat( xyz[1], i ) );
fsum_y += SubFloat( xyz[1], i );
m_MinBounds.z = min( m_MinBounds.z, SubFloat( xyz[2], i ) );
m_MaxBounds.z = max( m_MaxBounds.z, SubFloat( xyz[2], i ) );
fsum_z += SubFloat( xyz[2], i );
}
VectorAdd( m_MinBounds, m_pDef->m_BoundingBoxMin, m_MinBounds );
VectorAdd( m_MaxBounds, m_pDef->m_BoundingBoxMax, m_MaxBounds );
// calculate center
float flOONumParticles = 1.0 / 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 )
{
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
for( CParticleCollection *i = m_Children.m_pHead; i; i=i->m_pNext )
{
if ( !i->IsFinished() )
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Stop emitting particles
//-----------------------------------------------------------------------------
void CParticleCollection::StopEmission( bool bInfiniteOnly, bool bRemoveAllParticles, bool bWakeOnStop )
{
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
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 );
}
// Stop our children as well
for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext )
{
p->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();
}
else
{
StartEmission();
}
m_bDormant = bDormant;
}
void CParticleCollection::MoveParticle( int nInitialIndex, int nNewIndex )
{
// Copy the per-particle attributes
for( int p = 0; p < MAX_PARTICLE_ATTRIBUTES; ++p )
{
switch( m_nParticleFloatStrides[ p ] )
{
case 4: // move a float
m_pParticleAttributes[p][nNewIndex] = m_pParticleAttributes[p][nInitialIndex];
break;
case 12: // move a vec3
{
// sse weirdness
int oldidxsse = 12 * ( nNewIndex >> 2 );
int oldofs = oldidxsse + ( nNewIndex & 3 );
int lastidxsse = 12 * ( nInitialIndex >> 2 );
int lastofs = lastidxsse + ( nInitialIndex & 3 );
m_pParticleAttributes[p][oldofs] = m_pParticleAttributes[p][lastofs];
m_pParticleAttributes[p][4+oldofs] = m_pParticleAttributes[p][4+lastofs];
m_pParticleAttributes[p][8+oldofs] = m_pParticleAttributes[p][8+lastofs];
break;
}
}
switch( m_nParticleInitialFloatStrides[ p ] )
{
case 4: // move a float
m_pParticleInitialAttributes[p][nNewIndex] = m_pParticleInitialAttributes[p][nInitialIndex];
break;
case 12: // move a vec3
{
// sse weirdness
int oldidxsse = 12 * ( nNewIndex>>2 );
int oldofs = oldidxsse + ( nNewIndex & 3 );
int lastidxsse = 12 * ( nInitialIndex >> 2 );
int lastofs = lastidxsse + ( nInitialIndex & 3 );
m_pParticleInitialAttributes[p][oldofs] = m_pParticleInitialAttributes[p][lastofs];
m_pParticleInitialAttributes[p][4+oldofs] = m_pParticleInitialAttributes[p][4+lastofs];
m_pParticleInitialAttributes[p][8+oldofs] = m_pParticleInitialAttributes[p][8+lastofs];
break;
}
}
}
}
//-----------------------------------------------------------------------------
// 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 int32 *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 int32[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 int 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 )
{
int nLeftInKillList = m_nNumParticlesToKill;
if ( nLeftInKillList == 0 )
return;
int nParticlesActiveNow = m_nActiveParticles;
const int *pCurKillListSlot = m_pParticleKillList;
#ifdef _DEBUG
// This algorithm assumes the particles listed in the kill list are in ascending order
for ( int i = 1; i < nLeftInKillList; ++i )
{
Assert( pCurKillListSlot[i] > pCurKillListSlot[i-1] );
}
#endif
// first, kill particles past bounds
while ( nLeftInKillList && pCurKillListSlot[nLeftInKillList - 1] >= nParticlesActiveNow )
{
nLeftInKillList--;
}
Assert( nLeftInKillList <= m_nActiveParticles );
// now, execute kill list
// Previously, this code would swap the last item that wasn't dead into this slot.
// However, some lists require order invariance, so we need to collapse over holes instead of
// doing the (cheaper) swap from the end to the hole.
if ( !m_bRequiresOrderInvariance )
{
while( nLeftInKillList )
{
int nKillIndex = *(pCurKillListSlot++);
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 ] == 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--;
}
}
else
{
// The calling code may tell us to kill particles that are already out of bounds.
// That causes this code to kill more particles than we're supposed to (possibly even causing a crash).
// So remember how many particles we had left to kill so we can properly decrement the count below.
int decrementValue = nLeftInKillList;
int writeLoc = *(pCurKillListSlot++);
--nLeftInKillList;
for ( int readLoc = 1 + writeLoc; readLoc < nParticlesActiveNow; ++readLoc )
{
if ( nLeftInKillList > 0 && readLoc == *pCurKillListSlot )
{
pCurKillListSlot++;
--nLeftInKillList;
continue;
}
MoveParticle( readLoc, writeLoc );
++writeLoc;
}
nParticlesActiveNow -= decrementValue;
}
// 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 = m_ControlPoints[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 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 void GetRandomPointsOnControllingObjectHitBox(
CParticleCollection *pParticles,
int nControlPointNumber,
int nNumPtsOut,
float flBBoxScale,
int nNumTrysToGetAPointInsideTheModel,
Vector *pPntsOut,
Vector vecDirectionBias,
Vector *pHitBoxRelativeCoordOut, int *pHitBoxIndexOut )
{
for ( int i = 0; i < nNumPtsOut; ++i )
{
pPntsOut[i].Init();
}
}
virtual float GetPixelVisibility( int *pQueryHandle, const Vector &vecOrigin, float flScale ) { return 0.0f; }
};
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_pParticleSystemDictionary = NULL;
m_nNumFramesMeasured = 0;
m_flLastSimulationTime = 0.0f;
m_nParticleVertexCount = m_nParticleIndexCount = 0;
m_bFrameWarningNeeded = false;
for ( int i = 0; i < c_nNumFramesTracked; i++ )
{
m_nParticleVertexCountHistory[i] = 0;
}
m_fParticleCountScaling = 1.0f;
}
CParticleSystemMgr::~CParticleSystemMgr()
{
if ( m_pParticleSystemDictionary )
{
delete m_pParticleSystemDictionary;
m_pParticleSystemDictionary = NULL;
}
FlushAllSheets();
}
//-----------------------------------------------------------------------------
// Initialize the particle system
//-----------------------------------------------------------------------------
bool CParticleSystemMgr::Init( IParticleSystemQuery *pQuery )
{
if ( !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 );
m_pShadowDepthMaterial = NULL;
if( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 90 )
{
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;
}
return true;
}
//----------------------------------------------------------------------------------
// Cache/uncache materials used by particle systems
//----------------------------------------------------------------------------------
void CParticleSystemMgr::PrecacheParticleSystem( const char *pName )
{
if ( !pName || !pName[0] )
{
return;
}
CParticleSystemDefinition* pDef = FindParticleSystem( pName );
if ( !pDef )
{
Warning( "Attemped to precache unknown particle system \"%s\"!\n", pName );
return;
}
pDef->Precache();
}
void CParticleSystemMgr::UncacheAllParticleSystems()
{
if ( !m_pParticleSystemDictionary )
return;
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 );
NULL, // PREV_XYZ is for internal use only
"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 );
NULL, // HITBOX_INDEX is for internal use only
NULL, // HITBOX_XYZ_RELATIVE is for internal use only
"Alpha Alternate", // ALPHA2, 16
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
};
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 );
}
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_AllocAligned( 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"
//-----------------------------------------------------------------------------
// 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 ) || !pRoot )
{
Warning( "Unable to read particle definition %s! UtlBuffer is 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 ( IsPC() )
{
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 ( IsPC() )
{
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;
int nParticleVertexCountHistoryMax = 0;
for ( int i = 0; i < c_nNumFramesTracked-1; i++ )
{
m_nParticleVertexCountHistory[i] = m_nParticleVertexCountHistory[i+1];
nParticleVertexCountHistoryMax = Max ( nParticleVertexCountHistoryMax, m_nParticleVertexCountHistory[i] );
}
m_nParticleVertexCountHistory[c_nNumFramesTracked-1] = m_nParticleVertexCount;
nParticleVertexCountHistoryMax = Max ( nParticleVertexCountHistoryMax, m_nParticleVertexCount );
// We need to take an average over a decent number of frames because this throttling has a direct feedback effect. Worried about oscillation problems!
int nLower = CL_PARTICLE_SCALE_LOWER;//cl_particle_scale_lower.GetInt();
m_fParticleCountScaling = 1.0f;
int nHowManyOver = nParticleVertexCountHistoryMax - nLower;
if ( nHowManyOver > 0 )
{
int nUpper = CL_PARTICLE_SCALE_UPPER;//cl_particle_scale_upper.GetInt();
int nRange = nUpper - nLower;
m_fParticleCountScaling = 1.0f - ( (float)nHowManyOver / (float)nRange );
m_fParticleCountScaling = Clamp ( m_fParticleCountScaling, 0.0f, 1.0f );
}
m_nParticleVertexCount = m_nParticleIndexCount = 0;
}
float CParticleSystemMgr::GetLastSimulationTime() const
{
return m_flLastSimulationTime;
}
bool CParticleSystemMgr::Debug_FrameWarningNeededTestAndReset()
{
bool bTemp = m_bFrameWarningNeeded;
m_bFrameWarningNeeded = false;
return bTemp;
}
int CParticleSystemMgr::Debug_GetTotalParticleCount() const
{
return m_nParticleVertexCountHistory[c_nNumFramesTracked-1];
}
float CParticleSystemMgr::ParticleThrottleScaling() const
{
return m_fParticleCountScaling;
}
bool CParticleSystemMgr::ParticleThrottleRandomEnable() const
{
if ( m_fParticleCountScaling == 1.0f )
{
// No throttling.
return true;
}
else if ( m_fParticleCountScaling > RandomFloat ( 0.0f, 1.0f ) )
{
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// 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 );
}
//-----------------------------------------------------------------------------
// 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 ( IsX360() )
{
char szTargetName[MAX_PATH];
CreateX360Filename( 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 if ( g_pFullFileSystem->GetDVDMode() != DVDMODE_OFF )
{
// 360 version should have been there, 360 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";
}
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() )
{
// 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 );
}
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( 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( );
}
//--------------------------------------------------------------------------------
// Client loads sheets for rendering, server doesn't need to.
//--------------------------------------------------------------------------------
void CParticleSystemMgr::ShouldLoadSheets( bool bLoadSheets )
{
m_bShouldLoadSheets = bLoadSheets;
}
//--------------------------------------------------------------------------------
// Particle sheets
//--------------------------------------------------------------------------------
CSheet *CParticleSystemMgr::FindOrLoadSheet( char const *pszFname, ITexture *pTexture )
{
if ( !m_bShouldLoadSheets )
return NULL;
if ( m_SheetList.Defined( pszFname ) )
return m_SheetList[ pszFname ];
CSheet *pNewSheet = NULL;
size_t numBytes;
void const *pSheet = pTexture->GetResourceData( VTF_RSRC_SHEET, &numBytes );
if ( pSheet )
{
CUtlBuffer bufLoad( pSheet, numBytes, CUtlBuffer::READ_ONLY );
pNewSheet = new CSheet( bufLoad );
}
m_SheetList[ pszFname ] = pNewSheet;
return pNewSheet;
}
CSheet *CParticleSystemMgr::FindOrLoadSheet( IMaterial *pMaterial )
{
if ( !pMaterial )
return NULL;
bool bFoundVar = false;
IMaterialVar *pVar = pMaterial->FindVar( "$basetexture", &bFoundVar, true );
if ( bFoundVar && pVar && pVar->IsDefined() )
{
ITexture *pTex = pVar->GetTextureValue();
if ( pTex && !pTex->IsError() )
return FindOrLoadSheet( pTex->GetName(), pTex );
}
return NULL;
}
void CParticleSystemMgr::FlushAllSheets( void )
{
// for( int i = 0, iEnd = m_SheetList.Count(); i < iEnd; i++ )
// {
// delete m_SheetList.Element(i);
// }
//
// m_SheetList.RemoveAll();
m_SheetList.PurgeAndDeleteElements();
}
//-----------------------------------------------------------------------------
// 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() || pParticles->m_pDef->GetMaterial()->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 iPart;
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, &iPart );
nFirstParticle += step.m_nParticleCount;
if ( step.m_nParticleCount > 0 )
{
pBatch->m_nVertCount += step.m_nVertCount;
pBatch->m_nIndexCount += iPart;
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 );
pBatch = &batches[batches.AddToTail()];
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::DumpProfileInformation( void )
{
#if MEASURE_PARTICLE_PERF
FileHandle_t fh = g_pFullFileSystem->Open( "particle_profile.csv", "w" );
g_pFullFileSystem->FPrintf( fh, "numframes,%d\n", m_nNumFramesMeasured );
g_pFullFileSystem->FPrintf( fh, "name, total time, max time, max particles, allocated particles\n");
for( int i=0; i < m_pParticleSystemDictionary->NameCount(); i++ )
{
CParticleSystemDefinition *p = ( *m_pParticleSystemDictionary )[ i ];
if ( p->m_nMaximumActiveParticles )
g_pFullFileSystem->FPrintf( fh, "%s,%f,%f,%d,%d\n", p->m_Name.Get(), p->m_flTotalSimTime, p->m_flMaxMeasuredSimTime, p->m_nMaximumActiveParticles, p->m_nMaxParticles );
}
g_pFullFileSystem->FPrintf( fh, "\n\nopname, total time, max 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_flUncomittedTotalSimTime = 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( bool bShadowDepth )
{
int nRenderCacheCount = m_RenderCache.Count();
if ( nRenderCacheCount == 0 )
return;
VPROF_BUDGET( "CParticleSystemMgr::DrawRenderCache", VPROF_BUDGETGROUP_PARTICLE_RENDERING );
CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
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 );
g_pParticleSystemMgr->TallyParticlesRendered( batch.m_nVertCount * 3, batch.m_nIndexCount * 3 );
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 CParticleSystemMgr::TallyParticlesRendered( int nVertexCount, int nIndexCount )
{
m_nParticleIndexCount += nIndexCount;
m_nParticleVertexCount += nVertexCount;
if ( m_nParticleVertexCount > MAX_PARTICLE_VERTS )
{
m_bFrameWarningNeeded = true;
}
}
void IParticleSystemQuery::GetRandomPointsOnControllingObjectHitBox(
CParticleCollection *pParticles,
int nControlPointNumber,
int nNumPtsOut,
float flBBoxScale,
int nNumTrysToGetAPointInsideTheModel,
Vector *pPntsOut,
Vector vecDirectionalBias,
Vector *pHitBoxRelativeCoordOut,
int *pHitBoxIndexOut
)
{
for(int i=0; i < nNumPtsOut; i++)
{
pPntsOut[i]=pParticles->m_ControlPoints[nControlPointNumber].m_Position;
if ( pHitBoxRelativeCoordOut )
pHitBoxRelativeCoordOut[i].Init();
if ( pHitBoxIndexOut )
pHitBoxIndexOut[i] = -1;
}
}
void CParticleCollection::UpdateHitBoxInfo( int nControlPointNumber )
{
CModelHitBoxesInfo &hb = m_ControlPointHitBoxes[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 );
}