aboutsummaryrefslogtreecommitdiff
path: root/sp/src/game/client/particlemgr.h
blob: 3214a55fd81f4a815c5420bd6878e6fa1cd539a9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
//========= 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 "materialsystem/imaterial.h"
#include "materialsystem/imaterialsystem.h"
#include "mathlib/vector.h"
#include "mathlib/vmatrix.h"
#include "mathlib/mathlib.h"
#include "iclientrenderable.h"
#include "clientleafsystem.h"
#include "tier0/fasttimer.h"
#include "utllinkedlist.h"
#include "utldict.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;
};


//-----------------------------------------------------------------------------
// 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< CNewParticleEffect* > &list );
	bool EarlyRetireParticleSystems( int nCount, CNewParticleEffect **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