diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /particles/particles.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'particles/particles.cpp')
| -rw-r--r-- | particles/particles.cpp | 3881 |
1 files changed, 3881 insertions, 0 deletions
diff --git a/particles/particles.cpp b/particles/particles.cpp new file mode 100644 index 0000000..a78417a --- /dev/null +++ b/particles/particles.cpp @@ -0,0 +1,3881 @@ +//========= 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 ); + +} |