934 lines
35 KiB
C++
934 lines
35 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//
|
|
//===========================================================================//
|
|
|
|
//
|
|
// This module implements the particle manager for the client DLL.
|
|
// In a nutshell, to create your own effect, implement the ParticleEffect
|
|
// interface and call CParticleMgr::AddEffect to add your effect. Then you can
|
|
// add particles and simulate and render them.
|
|
|
|
/*
|
|
|
|
Particle manager documentation
|
|
-----------------------------------------------------------------------------
|
|
|
|
All particle effects are managed by a class called CParticleMgr. It tracks
|
|
the list of particles, manages their materials, sorts the particles, and
|
|
has callbacks to render them.
|
|
|
|
Conceptually, CParticleMgr is NOT part of VEngine's entity system. It does
|
|
not care about entities, only particle effects. Usually, the two are implemented
|
|
together, but you should be aware the CParticleMgr talks to you through its
|
|
own interfaces and does not talk to entities. Thus, it is possible to have
|
|
particle effects that are not entities.
|
|
|
|
To make a particle effect, you need two things:
|
|
|
|
1. An implementation of the IParticleEffect interface. This is how CParticleMgr
|
|
talks to you for things like rendering and updating your effect.
|
|
|
|
2. A (member) variable of type CParticleEffectBinding. This allows CParticleMgr
|
|
to store its internal data associated with your effect.
|
|
|
|
Once you have those two things, you call CParticleMgr::AddEffect and pass them
|
|
both in. You will then get updates through IParticleEffect::Update, and you will
|
|
be asked to render your particles with IParticleEffect::SimulateAndRender.
|
|
|
|
When you want to remove the effect, call
|
|
CParticleEffectBinding::SetRemoveFlag(), which tells CParticleMgr to remove the
|
|
effect next chance it gets.
|
|
|
|
Example class:
|
|
|
|
class CMyEffect : public IParticleEffect
|
|
{
|
|
public:
|
|
// Call this to start the effect by adding it to the particle
|
|
manager. void Start()
|
|
{
|
|
ParticleMgr()->AddEffect( &m_ParticleEffect, this );
|
|
}
|
|
|
|
// implementation of IParticleEffect functions go here...
|
|
|
|
public:
|
|
CParticleEffectBinding m_ParticleEffect;
|
|
};
|
|
|
|
|
|
|
|
How the particle effects are integrated with the entity system
|
|
-----------------------------------------------------------------------------
|
|
|
|
There are two helper classes that you can use to create particles for your
|
|
entities. Each one is useful under different conditions.
|
|
|
|
1. CSimpleEmitter is a class that does some of the dirty work of using
|
|
particles. If you want, you can just instantiate one of these with
|
|
CSimpleEmitter::Create and call its AddParticle functions to add particles. When
|
|
you are done and want to 'free' it, call its Release function rather than
|
|
deleting it, and it will wait until all of its particles have gone away before
|
|
removing itself (so you don't have to write code to wait for all of the
|
|
particles to go away).
|
|
|
|
In most cases, it is the easiest and most clear to use CSimpleEmitter or
|
|
derive a class from it, then use that class from inside an entity that wants
|
|
to make particles.
|
|
|
|
CSimpleEmitter and derived classes handle adding themselves to the particle
|
|
manager, tracking how many particles in the effect are active, and
|
|
rendering the particles.
|
|
|
|
CSimpleEmitter has code to simulate and render particles in a generic
|
|
fashion, but if you derive a class from it, you can override some of its
|
|
behavior with virtuals like UpdateAlpha, UpdateScale, UpdateColor, etc..
|
|
|
|
Example code:
|
|
CSimpleEmitter *pEmitter = CSimpleEmitter::Create();
|
|
|
|
CEffectMaterialHandle hMaterial = pEmitter->GetCEffectMaterial(
|
|
"mymaterial" );
|
|
|
|
for( int i=0; i < 100; i++ )
|
|
pEmitter->AddParticle( hMaterial, RandomVector(0,10), 4
|
|
);
|
|
|
|
pEmitter->Release();
|
|
|
|
2. Some older effects derive from C_BaseParticleEffect and implement an entity
|
|
and a particle system at the same time. This gets nasty and is not encouraged
|
|
anymore.
|
|
|
|
*/
|
|
|
|
#ifndef PARTICLEMGR_H
|
|
#define PARTICLEMGR_H
|
|
|
|
#ifdef _WIN32
|
|
#pragma once
|
|
#endif
|
|
|
|
#include "clientleafsystem.h"
|
|
#include "iclientrenderable.h"
|
|
#include "materialsystem/imaterial.h"
|
|
#include "materialsystem/imaterialsystem.h"
|
|
#include "mathlib/mathlib.h"
|
|
#include "mathlib/vector.h"
|
|
#include "mathlib/vmatrix.h"
|
|
#include "tier0/fasttimer.h"
|
|
#include "utldict.h"
|
|
#include "utllinkedlist.h"
|
|
#ifdef WIN32
|
|
#include <typeinfo.h>
|
|
#else
|
|
#include <typeinfo>
|
|
#endif
|
|
#include "tier1/utlintrusivelist.h"
|
|
#include "tier1/utlstring.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// forward declarations
|
|
//-----------------------------------------------------------------------------
|
|
|
|
class IParticleEffect;
|
|
class IClientParticleListener;
|
|
struct Particle;
|
|
class ParticleDraw;
|
|
class CMeshBuilder;
|
|
class CUtlMemoryPool;
|
|
class CEffectMaterial;
|
|
class CParticleSimulateIterator;
|
|
class CParticleRenderIterator;
|
|
class IThreadPool;
|
|
class CParticleSystemDefinition;
|
|
class CParticleMgr;
|
|
class CNewParticleEffect;
|
|
class CParticleCollection;
|
|
|
|
#define INVALID_MATERIAL_HANDLE NULL
|
|
|
|
// Various stats, disabled
|
|
// extern int g_nParticlesDrawn;
|
|
// extern CCycleCount g_ParticleTimer;
|
|
|
|
class CParticleSubTexture;
|
|
class CParticleSubTextureGroup;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// The basic particle description; all particles need to inherit from this.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
struct Particle {
|
|
Particle *m_pPrev, *m_pNext;
|
|
|
|
// Which sub texture this particle uses (so we can get at the tcoord mins
|
|
// and maxs).
|
|
CParticleSubTexture *m_pSubTexture;
|
|
|
|
// If m_Pos isn't used to store the world position, then implement
|
|
// IParticleEffect::GetParticlePosition()
|
|
Vector m_Pos; // Position of the particle in world space
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// This is the CParticleMgr's reference to a material in the material system.
|
|
// Particles are sorted by material.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// This indexes CParticleMgr::m_SubTextures.
|
|
typedef CParticleSubTexture *PMaterialHandle;
|
|
|
|
// Each effect stores a list of particles associated with each material. The
|
|
// list is hashed on the IMaterial pointer.
|
|
class CEffectMaterial {
|
|
public:
|
|
CEffectMaterial();
|
|
|
|
public:
|
|
// This provides the material that gets bound for this material in this
|
|
// effect. There can be multiple subtextures all within the same
|
|
// CEffectMaterial.
|
|
CParticleSubTextureGroup *m_pGroup;
|
|
|
|
Particle m_Particles;
|
|
CEffectMaterial *m_pHashedNext;
|
|
};
|
|
|
|
class CParticleSubTextureGroup {
|
|
public:
|
|
CParticleSubTextureGroup();
|
|
~CParticleSubTextureGroup();
|
|
|
|
// Even though each of the subtextures has its own material, they should all
|
|
// basically be the same exact material and just use different texture
|
|
// coordinates, so this is the material of the first subtexture that is
|
|
// bound.
|
|
//
|
|
// This is gotten from GetMaterialPage().
|
|
IMaterial *m_pPageMaterial;
|
|
};
|
|
|
|
// Precalculated data for each material used for particles.
|
|
// This allows us to put multiple subtextures into one VTF and sort them against
|
|
// each other.
|
|
class CParticleSubTexture {
|
|
public:
|
|
CParticleSubTexture();
|
|
|
|
float m_tCoordMins[2]; // bbox in texel space that this particle material
|
|
// uses.
|
|
float m_tCoordMaxs[2]; // Specified in the SubTextureMins/SubTextureMaxs
|
|
// parameter in the materials.
|
|
|
|
// Which group does this subtexture belong to?
|
|
CParticleSubTextureGroup *m_pGroup;
|
|
CParticleSubTextureGroup
|
|
m_DefaultGroup; // This is used as the group if a particle's material
|
|
// isn't using a group.
|
|
|
|
#ifdef _DEBUG
|
|
char *m_szDebugName;
|
|
#endif
|
|
|
|
IMaterial *m_pMaterial;
|
|
};
|
|
|
|
// Particle simulation list, used to determine what particles to simulate and
|
|
// how.
|
|
struct ParticleSimListEntry_t {
|
|
CNewParticleEffect *m_pNewParticleEffect;
|
|
bool m_bBoundingBoxOnly;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// interface IParticleEffect:
|
|
//
|
|
// This is the interface that particles effects must implement. The effect is
|
|
// responsible for starting itself and calling CParticleMgr::AddEffect, then it
|
|
// will get the callbacks it needs to simulate and render the particles.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
abstract_class IParticleEffect {
|
|
// Overridables.
|
|
public:
|
|
virtual ~IParticleEffect() {}
|
|
|
|
// Called at the beginning of a frame to precalculate data for rendering
|
|
// the particles. If you manage your own list of particles and want to
|
|
// simulate them all at once, you can do that here and just render them in
|
|
// the SimulateAndRender call.
|
|
virtual void Update(float fTimeDelta) {}
|
|
|
|
// Called once for the entire effect before the batch of SimulateAndRender()
|
|
// calls. For particle systems using FLAGS_CAMERASPACE (the default),
|
|
// effectMatrix transforms the particles from world space into camera space.
|
|
// You can change this matrix if you want your particles relative to
|
|
// something else like an attachment's space.
|
|
virtual void StartRender(VMatrix & effectMatrix) {}
|
|
|
|
// Simulate the particles.
|
|
virtual bool ShouldSimulate() const = 0;
|
|
virtual void SetShouldSimulate(bool bSim) = 0;
|
|
virtual void SimulateParticles(CParticleSimulateIterator * pIterator) = 0;
|
|
|
|
// Render the particles.
|
|
virtual void RenderParticles(CParticleRenderIterator * pIterator) = 0;
|
|
|
|
// Implementing this is optional. It is called when an effect is removed. It
|
|
// is useful if you hold onto pointers to the particles you created (so when
|
|
// this is called, you should clean up your data so you don't reference the
|
|
// particles again). NOTE: after calling this, the particle manager won't
|
|
// touch the IParticleEffect or its associated CParticleEffectBinding
|
|
// anymore.
|
|
virtual void NotifyRemove() {}
|
|
|
|
// This method notifies the effect a particle is about to be deallocated.
|
|
// Implementations should *not* actually deallocate it.
|
|
// NOTE: The particle effect's GetNumActiveParticles is updated BEFORE this
|
|
// is called
|
|
// so if GetNumActiveParticles returns 0, then you know this is the
|
|
// last particle in the system being removed.
|
|
virtual void NotifyDestroyParticle(Particle * pParticle) {}
|
|
|
|
// Fill in the origin used to sort this entity.
|
|
// This is a world space position.
|
|
virtual const Vector &GetSortOrigin() = 0;
|
|
|
|
// Fill in the origin used to sort this entity.
|
|
// TODO: REMOVE THIS. ALL PARTICLE SYSTEMS SHOULD EITHER SET m_Pos IN
|
|
// CONJUNCTION WITH THE PARTICLE_LOCALSPACE FLAG, OR DO SETBBOX THEMSELVES.
|
|
virtual const Vector *GetParticlePosition(Particle * pParticle) {
|
|
return &pParticle->m_Pos;
|
|
}
|
|
|
|
virtual const char *GetEffectName() { return "???"; }
|
|
};
|
|
|
|
#define REGISTER_EFFECT(effect) \
|
|
IParticleEffect *effect##_Factory() { return new effect; } \
|
|
struct effect##_RegistrationHelper { \
|
|
effect##_RegistrationHelper() { \
|
|
ParticleMgr()->RegisterEffect(typeid(effect).name(), \
|
|
effect##_Factory); \
|
|
} \
|
|
}; \
|
|
static effect##_RegistrationHelper g_##effect##_RegistrationHelper
|
|
|
|
#define REGISTER_EFFECT_USING_CREATE(effect) \
|
|
IParticleEffect *effect##_Factory() { \
|
|
return effect::Create(#effect).GetObject(); \
|
|
} \
|
|
struct effect##_RegistrationHelper { \
|
|
effect##_RegistrationHelper() { \
|
|
ParticleMgr()->RegisterEffect(typeid(effect).name(), \
|
|
effect##_Factory); \
|
|
} \
|
|
}; \
|
|
static effect##_RegistrationHelper g_##effect##_RegistrationHelper
|
|
|
|
// In order to create a particle effect, you must have one of these around and
|
|
// implement IParticleEffect. Pass them both into CParticleMgr::AddEffect and
|
|
// you are good to go.
|
|
class CParticleEffectBinding : public CDefaultClientRenderable {
|
|
friend class CParticleMgr;
|
|
friend class CParticleSimulateIterator;
|
|
friend class CNewParticleEffect;
|
|
|
|
public:
|
|
CParticleEffectBinding();
|
|
~CParticleEffectBinding();
|
|
|
|
// Helper functions to setup, add particles, etc..
|
|
public:
|
|
// Simulate all the particles.
|
|
void SimulateParticles(float flTimeDelta);
|
|
|
|
// Use this to specify materials when adding particles.
|
|
// Returns the index of the material it found or added.
|
|
// Returns INVALID_MATERIAL_HANDLE if it couldn't find or add a material.
|
|
PMaterialHandle FindOrAddMaterial(const char *pMaterialName);
|
|
|
|
// Allocate particles. The Particle manager will automagically
|
|
// deallocate them when the IParticleEffect SimulateAndRender() method
|
|
// returns false. The first argument is the size of the particle
|
|
// structure in bytes
|
|
Particle *AddParticle(int sizeInBytes, PMaterialHandle pMaterial);
|
|
|
|
// This is an optional call you can make if you want to manually manage the
|
|
// effect's bounding box. Normally, the bounding box is managed
|
|
// automatically, but in certain cases it is more efficient to set it
|
|
// manually.
|
|
//
|
|
// Note: this is a WORLD SPACE bounding box, even if you've used
|
|
// SetLocalSpaceTransform.
|
|
//
|
|
// After you make this call, the particle manager will no longer update the
|
|
// bounding box automatically if bDisableAutoUpdate is true.
|
|
void SetBBox(const Vector &bbMin, const Vector &bbMax,
|
|
bool bDisableAutoUpdate = true);
|
|
// gets a copy of the current bbox mins/maxs in worldspace
|
|
void GetWorldspaceBounds(Vector *pMins, Vector *pMaxs);
|
|
|
|
// This tells the particle manager that your particles are transformed by
|
|
// the specified matrix. That way, it can transform the bbox defined by
|
|
// Particle::m_Pos into world space correctly.
|
|
//
|
|
// It also sets up the matrix returned by CParticleMgr::GetModelView() to
|
|
// include this matrix, so you can do TransformParticle with it like any
|
|
// other particle system.
|
|
const matrix3x4_t &GetLocalSpaceTransform() const;
|
|
void SetLocalSpaceTransform(const matrix3x4_t &transform);
|
|
|
|
// This expands the bbox to contain the specified point. Returns true if
|
|
// bbox changed
|
|
bool EnlargeBBoxToContain(const Vector &pt);
|
|
|
|
// The EZ particle singletons use this - they don't want to be added to all
|
|
// the leaves and drawn through the leaf system - they are specifically told
|
|
// to draw each frame at a certain point.
|
|
void SetDrawThruLeafSystem(int bDraw);
|
|
|
|
// Some view model particle effects want to be drawn right before the view
|
|
// model (after everything else is drawn).
|
|
void SetDrawBeforeViewModel(int bDraw);
|
|
|
|
// Call this to have the effect removed whenever it safe to do so.
|
|
// This is a lot safer than calling CParticleMgr::RemoveEffect.
|
|
int GetRemoveFlag() { return GetFlag(FLAGS_REMOVE); }
|
|
void SetRemoveFlag() { SetFlag(FLAGS_REMOVE, 1); }
|
|
|
|
// Set this flag to tell the particle manager to simulate your particles
|
|
// even if the particle system isn't visible. Tempents and fast effects can
|
|
// always use this if they want since they want to simulate their particles
|
|
// until they go away. This flag is ON by default.
|
|
int GetAlwaysSimulate() { return GetFlag(FLAGS_ALWAYSSIMULATE); }
|
|
void SetAlwaysSimulate(int bAlwaysSimulate) {
|
|
SetFlag(FLAGS_ALWAYSSIMULATE, bAlwaysSimulate);
|
|
}
|
|
|
|
void SetIsNewParticleSystem(void) { SetFlag(FLAGS_NEW_PARTICLE_SYSTEM, 1); }
|
|
// Set if the effect was drawn the previous frame.
|
|
// This can be used by particle effect classes
|
|
// to decide whether or not they want to spawn
|
|
// new particles - if they weren't drawn, then
|
|
// they can 'freeze' the particle system to avoid
|
|
// overhead.
|
|
int WasDrawnPrevFrame() { return GetFlag(FLAGS_DRAWN_PREVFRAME); }
|
|
void SetWasDrawnPrevFrame(int bWasDrawnPrevFrame) {
|
|
SetFlag(FLAGS_DRAWN_PREVFRAME, bWasDrawnPrevFrame);
|
|
}
|
|
|
|
// When the effect is in camera space mode, then the transforms are setup
|
|
// such that the particle vertices are specified in camera space (in
|
|
// CParticleDraw) rather than world space.
|
|
//
|
|
// This makes it faster to specify the particles - you only have to
|
|
// transform the center by CParticleMgr::GetModelView then add to X and Y to
|
|
// build the quad.
|
|
//
|
|
// Effects that want to specify verts (in CParticleDraw) in world space
|
|
// should set this to false and ignore CParticleMgr::GetModelView.
|
|
//
|
|
// Camera space mode is ON by default.
|
|
int IsEffectCameraSpace() { return GetFlag(FLAGS_CAMERASPACE); }
|
|
void SetEffectCameraSpace(int bCameraSpace) {
|
|
SetFlag(FLAGS_CAMERASPACE, bCameraSpace);
|
|
}
|
|
|
|
// This tells it whether or not to apply the local transform to the matrix
|
|
// returned by CParticleMgr::GetModelView(). Usually, you'll want this, so
|
|
// you can just say TransformParticle( pMgr->GetModelView(), vPos ), but you
|
|
// may want to manually apply your local transform before saying
|
|
// TransformParticle.
|
|
//
|
|
// This is ON by default.
|
|
int GetAutoApplyLocalTransform() const {
|
|
return GetFlag(FLAGS_AUTOAPPLYLOCALTRANSFORM);
|
|
}
|
|
void SetAutoApplyLocalTransform(int b) {
|
|
SetFlag(FLAGS_AUTOAPPLYLOCALTRANSFORM, b);
|
|
}
|
|
|
|
// If this is true, then the bbox is calculated from particle positions.
|
|
// This works fine if you always simulate (SetAlwaysSimulateFlag) so the
|
|
// system can become visible if it moves into the PVS. If you don't use
|
|
// this, then you should call SetBBox at least once to tell the particle
|
|
// manager where your entity is.
|
|
int GetAutoUpdateBBox() { return GetFlag(FLAGS_AUTOUPDATEBBOX); }
|
|
void SetAutoUpdateBBox(int bAutoUpdate) {
|
|
SetFlag(FLAGS_AUTOUPDATEBBOX, bAutoUpdate);
|
|
}
|
|
|
|
// Get the current number of particles in the effect.
|
|
int GetNumActiveParticles();
|
|
|
|
// The is the max size of the particles for use in bounding computation
|
|
void SetParticleCullRadius(float flMaxParticleRadius);
|
|
|
|
// Build a list of all active particles, returns actual count filled in
|
|
int GetActiveParticleList(int nCount, Particle **ppParticleList);
|
|
|
|
// detect origin/bbox changes and update leaf system if necessary
|
|
void DetectChanges();
|
|
|
|
private:
|
|
// Change flags..
|
|
void SetFlag(int flag, int bOn) {
|
|
if (bOn)
|
|
m_Flags |= flag;
|
|
else
|
|
m_Flags &= ~flag;
|
|
}
|
|
int GetFlag(int flag) const { return m_Flags & flag; }
|
|
|
|
void Init(CParticleMgr *pMgr, IParticleEffect *pSim);
|
|
void Term();
|
|
|
|
// Get rid of the specified particle.
|
|
void RemoveParticle(Particle *pParticle);
|
|
|
|
void StartDrawMaterialParticles(CEffectMaterial *pMaterial,
|
|
float flTimeDelta, IMesh *&pMesh,
|
|
CMeshBuilder &builder,
|
|
ParticleDraw &particleDraw,
|
|
bool bWireframe);
|
|
|
|
int DrawMaterialParticles(bool bBucketSort, CEffectMaterial *pMaterial,
|
|
float flTimeDelta, bool bWireframe);
|
|
|
|
void GrowBBoxFromParticlePositions(CEffectMaterial *pMaterial,
|
|
bool &bboxSet, Vector &bbMin,
|
|
Vector &bbMax);
|
|
|
|
void RenderStart(VMatrix &mTempModel, VMatrix &mTempView);
|
|
void RenderEnd(VMatrix &mModel, VMatrix &mView);
|
|
|
|
void BBoxCalcStart(Vector &bbMin, Vector &bbMax);
|
|
void BBoxCalcEnd(bool bboxSet, Vector &bbMin, Vector &bbMax);
|
|
|
|
void DoBucketSort(CEffectMaterial *pMaterial, float *zCoords, int nZCoords,
|
|
float minZ, float maxZ);
|
|
|
|
int GetRemovalInProgressFlag() { return GetFlag(FLAGS_REMOVALINPROGRESS); }
|
|
void SetRemovalInProgressFlag() { SetFlag(FLAGS_REMOVALINPROGRESS, 1); }
|
|
|
|
// BBox is recalculated before it's put into the tree for the first time.
|
|
int GetNeedsBBoxUpdate() { return GetFlag(FLAGS_NEEDS_BBOX_UPDATE); }
|
|
void SetNeedsBBoxUpdate(int bFirstUpdate) {
|
|
SetFlag(FLAGS_NEEDS_BBOX_UPDATE, bFirstUpdate);
|
|
}
|
|
|
|
// Set on creation and cleared after the first PostRender (whether or not
|
|
// the system was rendered).
|
|
int GetFirstFrameFlag() { return GetFlag(FLAGS_FIRST_FRAME); }
|
|
void SetFirstFrameFlag(int bFirstUpdate) {
|
|
SetFlag(FLAGS_FIRST_FRAME, bFirstUpdate);
|
|
}
|
|
|
|
int WasDrawn() { return GetFlag(FLAGS_DRAWN); }
|
|
void SetDrawn(int bDrawn) { SetFlag(FLAGS_DRAWN, bDrawn); }
|
|
|
|
// Update m_Min/m_Max. Returns false and sets the bbox to the sort origin if
|
|
// there are no particles.
|
|
bool RecalculateBoundingBox();
|
|
|
|
CEffectMaterial *GetEffectMaterial(CParticleSubTexture *pSubTexture);
|
|
|
|
// IClientRenderable overrides.
|
|
public:
|
|
virtual const Vector &GetRenderOrigin(void);
|
|
virtual const QAngle &GetRenderAngles(void);
|
|
virtual const matrix3x4_t &RenderableToWorldTransform();
|
|
virtual void GetRenderBounds(Vector &mins, Vector &maxs);
|
|
virtual bool ShouldDraw(void);
|
|
virtual bool IsTransparent(void);
|
|
virtual int DrawModel(int flags);
|
|
|
|
private:
|
|
enum {
|
|
FLAGS_REMOVE = (1 << 0), // Set in SetRemoveFlag
|
|
FLAGS_REMOVALINPROGRESS =
|
|
(1 << 1), // Set while the effect is being removed to prevent
|
|
// infinite recursion.
|
|
FLAGS_NEEDS_BBOX_UPDATE = (1 << 2), // This is set until the effect's
|
|
// bbox has been updated once.
|
|
FLAGS_AUTOUPDATEBBOX =
|
|
(1 << 3), // Update bbox automatically? Cleared in SetBBox.
|
|
FLAGS_ALWAYSSIMULATE = (1 << 4), // See SetAlwaysSimulate.
|
|
FLAGS_DRAWN =
|
|
(1 << 5), // Set if the effect is drawn through the leaf system.
|
|
FLAGS_DRAWN_PREVFRAME =
|
|
(1 << 6), // Set if the effect was drawn the previous frame.
|
|
// This can be used by particle effect classes
|
|
// to decide whether or not they want to spawn
|
|
// new particles - if they weren't drawn, then
|
|
// they can 'freeze' the particle system to avoid
|
|
// overhead.
|
|
FLAGS_CAMERASPACE = (1 << 7), // See SetEffectCameraSpace.
|
|
FLAGS_DRAW_THRU_LEAF_SYSTEM =
|
|
(1 << 8), // This is the default - do the effect's visibility
|
|
// through the leaf system.
|
|
FLAGS_DRAW_BEFORE_VIEW_MODEL =
|
|
(1 << 9), // Draw before the view model? If this is set, it assumes
|
|
// FLAGS_DRAW_THRU_LEAF_SYSTEM goes off.
|
|
FLAGS_AUTOAPPLYLOCALTRANSFORM =
|
|
(1 << 10), // Automatically apply the local transform to
|
|
// CParticleMgr::GetModelView()'s matrix.
|
|
FLAGS_FIRST_FRAME =
|
|
(1 << 11), // Cleared after the first frame that this system exists
|
|
// (so it can simulate after rendering once).
|
|
FLAGS_NEW_PARTICLE_SYSTEM = (1 << 12) // uses new particle system
|
|
};
|
|
|
|
VMatrix m_LocalSpaceTransform;
|
|
bool m_bLocalSpaceTransformIdentity; // If this is true, then
|
|
// m_LocalSpaceTransform is assumed to
|
|
// be identity.
|
|
|
|
// Bounding box. Stored in WORLD space.
|
|
Vector m_Min;
|
|
Vector m_Max;
|
|
|
|
// paramter copies to detect changes
|
|
Vector m_LastMin;
|
|
Vector m_LastMax;
|
|
|
|
// The particle cull size
|
|
float m_flParticleCullRadius;
|
|
|
|
// Number of active particles.
|
|
unsigned short m_nActiveParticles;
|
|
|
|
// See CParticleMgr::m_FrameCode.
|
|
unsigned short m_FrameCode;
|
|
|
|
// For CParticleMgr's list index.
|
|
unsigned short m_ListIndex;
|
|
|
|
IParticleEffect *m_pSim;
|
|
CParticleMgr *m_pParticleMgr;
|
|
|
|
// Combination of the CParticleEffectBinding::FLAGS_ flags.
|
|
int m_Flags;
|
|
|
|
// Materials this effect is using.
|
|
enum { EFFECT_MATERIAL_HASH_SIZE = 8 };
|
|
CEffectMaterial *m_EffectMaterialHash[EFFECT_MATERIAL_HASH_SIZE];
|
|
|
|
// For faster iteration.
|
|
CUtlLinkedList<CEffectMaterial *, unsigned short> m_Materials;
|
|
|
|
// auto updates the bbox after N frames
|
|
unsigned short m_UpdateBBoxCounter;
|
|
};
|
|
|
|
class CParticleLightInfo {
|
|
public:
|
|
Vector m_vPos;
|
|
Vector m_vColor; // 0-1
|
|
float m_flIntensity;
|
|
};
|
|
|
|
typedef IParticleEffect *(*CreateParticleEffectFN)();
|
|
|
|
enum {
|
|
TOOLPARTICLESYSTEMID_INVALID = -1,
|
|
};
|
|
|
|
class CParticleMgr {
|
|
friend class CParticleEffectBinding;
|
|
friend class CParticleCollection;
|
|
|
|
public:
|
|
CParticleMgr();
|
|
virtual ~CParticleMgr();
|
|
|
|
// Call at init time to preallocate the bucket of particles.
|
|
bool Init(unsigned long nPreallocatedParticles, IMaterialSystem *pMaterial);
|
|
|
|
// Shutdown - free everything.
|
|
void Term();
|
|
|
|
void LevelInit();
|
|
|
|
void RegisterEffect(const char *pEffectType, CreateParticleEffectFN func);
|
|
IParticleEffect *CreateEffect(const char *pEffectType);
|
|
|
|
// Add and remove effects from the active list.
|
|
// Note: once you call AddEffect, CParticleEffectBinding will automatically
|
|
// call
|
|
// RemoveEffect in its destructor.
|
|
// Note: it's much safer to call CParticleEffectBinding::SetRemoveFlag
|
|
// instead of
|
|
// CParticleMgr::RemoveEffect.
|
|
bool AddEffect(CParticleEffectBinding *pEffect, IParticleEffect *pSim);
|
|
void RemoveEffect(CParticleEffectBinding *pEffect);
|
|
|
|
void AddEffect(CNewParticleEffect *pEffect);
|
|
void RemoveEffect(CNewParticleEffect *pEffect);
|
|
|
|
// Called at level shutdown to free all the lingering particle effects
|
|
// (usually CParticleEffect-derived effects that can linger with noone
|
|
// holding onto them).
|
|
void RemoveAllEffects();
|
|
|
|
// This should be called at the start of the frame.
|
|
void IncrementFrameCode();
|
|
|
|
// This updates all the particle effects and inserts them into the leaves.
|
|
void Simulate(float fTimeDelta);
|
|
|
|
// This just marks effects that were drawn so during their next simulation
|
|
// they can know if they were drawn in the previous frame.
|
|
void PostRender();
|
|
|
|
// Draw the effects marked with SetDrawBeforeViewModel.
|
|
void DrawBeforeViewModelEffects();
|
|
|
|
// Returns the modelview matrix
|
|
VMatrix &GetModelView();
|
|
|
|
Particle *AllocParticle(int size);
|
|
void FreeParticle(Particle *);
|
|
|
|
PMaterialHandle GetPMaterial(const char *pMaterialName);
|
|
IMaterial *PMaterialToIMaterial(PMaterialHandle hMaterial);
|
|
|
|
// HACKHACK: quick fix that compensates for the fact that this system was
|
|
// designed to never release materials EVER.
|
|
void RepairPMaterial(PMaterialHandle hMaterial);
|
|
|
|
// Particles drawn with the ParticleSphere material will use this info.
|
|
// This should be set in IParticleEffect.
|
|
void GetDirectionalLightInfo(CParticleLightInfo &info) const;
|
|
void SetDirectionalLightInfo(const CParticleLightInfo &info);
|
|
|
|
// add a class that gets notified of entity events
|
|
void AddEffectListener(IClientParticleListener *pListener);
|
|
void RemoveEffectListener(IClientParticleListener *pListener);
|
|
|
|
// Tool effect ids
|
|
int AllocateToolParticleEffectId();
|
|
|
|
// Remove all new effects
|
|
void RemoveAllNewEffects();
|
|
|
|
// Should particle effects be rendered?
|
|
void RenderParticleSystems(bool bEnable);
|
|
bool ShouldRenderParticleSystems() const;
|
|
|
|
// Quick profiling (counts only, not clock cycles).
|
|
bool m_bStatsRunning;
|
|
int m_nStatsFramesSinceLastAlert;
|
|
|
|
void StatsAccumulateActiveParticleSystems();
|
|
void StatsReset();
|
|
void StatsSpewResults();
|
|
void StatsNewParticleEffectDrawn(CNewParticleEffect *pParticles);
|
|
void StatsOldParticleEffectDrawn(CParticleEffectBinding *pParticles);
|
|
|
|
private:
|
|
struct RetireInfo_t {
|
|
CParticleCollection *m_pCollection;
|
|
float m_flScreenArea;
|
|
bool m_bFirstFrame;
|
|
};
|
|
|
|
// Call Update() on all the effects.
|
|
void UpdateAllEffects(float flTimeDelta);
|
|
|
|
void UpdateNewEffects(float flTimeDelta); // update new particle effects
|
|
|
|
CParticleSubTextureGroup *FindOrAddSubTextureGroup(
|
|
IMaterial *pPageMaterial);
|
|
|
|
int ComputeParticleDefScreenArea(int nInfoCount, RetireInfo_t *pInfo,
|
|
float *pTotalArea,
|
|
CParticleSystemDefinition *pDef,
|
|
const CViewSetup &view,
|
|
const VMatrix &worldToPixels,
|
|
float flFocalDist);
|
|
|
|
bool RetireParticleCollections(CParticleSystemDefinition *pDef, int nCount,
|
|
RetireInfo_t *pInfo, float flScreenArea,
|
|
float flMaxTotalArea);
|
|
|
|
void BuildParticleSimList(CUtlVector<ParticleSimListEntry_t> &list);
|
|
bool EarlyRetireParticleSystems(int nCount,
|
|
ParticleSimListEntry_t *ppEffects);
|
|
static int RetireSort(const void *p1, const void *p2);
|
|
|
|
private:
|
|
int m_nCurrentParticlesAllocated;
|
|
|
|
// Directional lighting info.
|
|
CParticleLightInfo m_DirectionalLight;
|
|
|
|
// Frame code, used to prevent CParticleEffects from simulating multiple
|
|
// times per frame. Their DrawModel can be called multiple times per frame
|
|
// because of water reflections, but we only want to simulate the particles
|
|
// once.
|
|
unsigned short m_FrameCode;
|
|
|
|
bool m_bUpdatingEffects;
|
|
bool m_bRenderParticleEffects;
|
|
|
|
// All the active effects.
|
|
CUtlLinkedList<CParticleEffectBinding *, unsigned short> m_Effects;
|
|
|
|
// all the active effects using the new particle interface
|
|
CUtlIntrusiveDList<CNewParticleEffect> m_NewEffects;
|
|
|
|
CUtlVector<IClientParticleListener *> m_effectListeners;
|
|
|
|
IMaterialSystem *m_pMaterialSystem;
|
|
|
|
// Store the concatenated modelview matrix
|
|
VMatrix m_mModelView;
|
|
|
|
CUtlVector<CParticleSubTextureGroup *>
|
|
m_SubTextureGroups; // lookup by group name
|
|
CUtlDict<CParticleSubTexture *, unsigned short>
|
|
m_SubTextures; // lookup by material name
|
|
CParticleSubTexture
|
|
m_DefaultInvalidSubTexture; // Used when they specify an invalid
|
|
// material name.
|
|
|
|
CUtlMap<const char *, CreateParticleEffectFN> m_effectFactories;
|
|
|
|
int m_nToolParticleEffectId;
|
|
|
|
IThreadPool *m_pThreadPool[2];
|
|
};
|
|
|
|
inline int CParticleMgr::AllocateToolParticleEffectId() {
|
|
return m_nToolParticleEffectId++;
|
|
}
|
|
|
|
// Implement this class and register with CParticleMgr to receive particle
|
|
// effect add/remove notification
|
|
class IClientParticleListener {
|
|
public:
|
|
virtual void OnParticleEffectAdded(IParticleEffect *pEffect) = 0;
|
|
virtual void OnParticleEffectRemoved(IParticleEffect *pEffect) = 0;
|
|
};
|
|
|
|
// Helper functions to abstract out the particle testbed app.
|
|
float Helper_GetTime();
|
|
float Helper_GetFrameTime();
|
|
float Helper_RandomFloat(float minVal, float maxVal);
|
|
int Helper_RandomInt(int minVal, int maxVal);
|
|
|
|
// ------------------------------------------------------------------------ //
|
|
// CParticleMgr inlines
|
|
// ------------------------------------------------------------------------ //
|
|
|
|
inline VMatrix &CParticleMgr::GetModelView() { return m_mModelView; }
|
|
|
|
// ------------------------------------------------------------------------ //
|
|
// CParticleEffectBinding inlines.
|
|
// ------------------------------------------------------------------------ //
|
|
|
|
inline const matrix3x4_t &CParticleEffectBinding::GetLocalSpaceTransform()
|
|
const {
|
|
return m_LocalSpaceTransform.As3x4();
|
|
}
|
|
|
|
// ------------------------------------------------------------------------ //
|
|
// GLOBALS
|
|
// ------------------------------------------------------------------------ //
|
|
|
|
CParticleMgr *ParticleMgr();
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// StandardParticle_t; this is just one type of particle
|
|
// effects may implement their own particle data structures
|
|
//-----------------------------------------------------------------------------
|
|
|
|
struct StandardParticle_t : public Particle {
|
|
// Color and alpha values are 0 - 1
|
|
void SetColor(float r, float g, float b);
|
|
void SetAlpha(float a);
|
|
|
|
Vector m_Velocity;
|
|
|
|
// How this is used is up to the effect's discretion. Some use it for how
|
|
// long it has been alive and others use it to count down until the particle
|
|
// disappears.
|
|
float m_Lifetime;
|
|
|
|
unsigned char m_EffectData; // Data specific to the IParticleEffect. This
|
|
// can be used to distinguish between different
|
|
// types of particles the effect is simulating.
|
|
unsigned short m_EffectDataWord;
|
|
|
|
unsigned char m_Color[4]; // RGBA - not all effects need to use this.
|
|
};
|
|
|
|
// ------------------------------------------------------------------------ //
|
|
// Transform a particle.
|
|
// ------------------------------------------------------------------------ //
|
|
|
|
inline void TransformParticle(const VMatrix &vMat, const Vector &vIn,
|
|
Vector &vOut) {
|
|
// vOut = vMat.VMul4x3(vIn);
|
|
vOut.x = vMat.m[0][0] * vIn.x + vMat.m[0][1] * vIn.y +
|
|
vMat.m[0][2] * vIn.z + vMat.m[0][3];
|
|
vOut.y = vMat.m[1][0] * vIn.x + vMat.m[1][1] * vIn.y +
|
|
vMat.m[1][2] * vIn.z + vMat.m[1][3];
|
|
vOut.z = vMat.m[2][0] * vIn.x + vMat.m[2][1] * vIn.y +
|
|
vMat.m[2][2] * vIn.z + vMat.m[2][3];
|
|
}
|
|
|
|
// ------------------------------------------------------------------------ //
|
|
// CEffectMaterial inlines
|
|
// ------------------------------------------------------------------------ //
|
|
|
|
inline void StandardParticle_t::SetColor(float r, float g, float b) {
|
|
m_Color[0] = (unsigned char)(r * 255.9f);
|
|
m_Color[1] = (unsigned char)(g * 255.9f);
|
|
m_Color[2] = (unsigned char)(b * 255.9f);
|
|
}
|
|
|
|
inline void StandardParticle_t::SetAlpha(float a) {
|
|
m_Color[3] = (unsigned char)(a * 255.9f);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// List functions.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
inline void UnlinkParticle(Particle *pParticle) {
|
|
pParticle->m_pPrev->m_pNext = pParticle->m_pNext;
|
|
pParticle->m_pNext->m_pPrev = pParticle->m_pPrev;
|
|
}
|
|
|
|
inline void InsertParticleBefore(Particle *pInsert, Particle *pNext) {
|
|
// link pCur before pPrev
|
|
pInsert->m_pNext = pNext;
|
|
pInsert->m_pPrev = pNext->m_pPrev;
|
|
pInsert->m_pNext->m_pPrev = pInsert->m_pPrev->m_pNext = pInsert;
|
|
}
|
|
|
|
inline void InsertParticleAfter(Particle *pInsert, Particle *pPrev) {
|
|
pInsert->m_pPrev = pPrev;
|
|
pInsert->m_pNext = pPrev->m_pNext;
|
|
|
|
pInsert->m_pNext->m_pPrev = pInsert->m_pPrev->m_pNext = pInsert;
|
|
}
|
|
|
|
inline void SwapParticles(Particle *pPrev, Particle *pCur) {
|
|
// unlink pCur
|
|
UnlinkParticle(pCur);
|
|
InsertParticleBefore(pCur, pPrev);
|
|
}
|
|
|
|
#include "particle_iterators.h"
|
|
|
|
#endif
|