mirror of
https://github.com/nillerusr/source-engine.git
synced 2024-12-22 14:16:50 +00:00
3882 lines
122 KiB
C++
3882 lines
122 KiB
C++
//========= 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 );
|
|
|
|
}
|