Browse Source

enhanced-emitter -- numerous enhancements to ParticleEmitter class.
pooled-particles -- optional support for pooled particles which combines multiple emitters in a common sorting pool.

Marc Chapman 8 years ago
parent
commit
43d2399aea
2 changed files with 297 additions and 31 deletions
  1. 241 30
      Engine/source/T3D/fx/particleEmitter.cpp
  2. 56 1
      Engine/source/T3D/fx/particleEmitter.h

+ 241 - 30
Engine/source/T3D/fx/particleEmitter.cpp

@@ -24,6 +24,7 @@
 // Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
 // Copyright (C) 2015 Faust Logic, Inc.
 //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
 #include "platform/platform.h"
 #include "T3D/fx/particleEmitter.h"
 
@@ -42,6 +43,10 @@
 #include "lighting/lightInfo.h"
 #include "console/engineAPI.h"
 
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+#include "afx/util/afxParticlePool.h"
+#endif 
+
 Point3F ParticleEmitter::mWindVelocity( 0.0, 0.0, 0.0 );
 const F32 ParticleEmitter::AgedSpinToRadians = (1.0f/1000.0f) * (1.0f/360.0f) * M_PI_F * 2.0f;
 
@@ -153,6 +158,21 @@ ParticleEmitterData::ParticleEmitterData()
    
    alignParticles = false;
    alignDirection = Point3F(0.0f, 1.0f, 0.0f);
+   
+   ejectionInvert = false;
+   fade_color    = false;
+   fade_alpha    = false;
+   fade_size     = false;
+   parts_per_eject = 1;
+   use_emitter_xfm = false;
+   
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+   pool_datablock = 0;
+   pool_index = 0;
+   pool_depth_fade = false;
+   pool_radial_fade = false;
+   do_pool_id_convert = false;
+#endif
 }
 
 
@@ -297,6 +317,23 @@ void ParticleEmitterData::initPersistFields()
 
    endGroup( "ParticleEmitterData" );
 
+   addGroup("AFX");
+   addField("ejectionInvert",       TypeBool,    Offset(ejectionInvert,     ParticleEmitterData));
+   addField("fadeColor",            TypeBool,    Offset(fade_color,         ParticleEmitterData));
+   addField("fadeAlpha",            TypeBool,    Offset(fade_alpha,         ParticleEmitterData));
+   addField("fadeSize",             TypeBool,    Offset(fade_size,          ParticleEmitterData));
+   // useEmitterTransform currently does not work in TGEA or T3D
+   addField("useEmitterTransform",  TypeBool,    Offset(use_emitter_xfm,    ParticleEmitterData));
+   endGroup("AFX");
+
+#if defined(AFX_CAP_PARTICLE_POOLS)
+   addGroup("AFX Pooled Particles");
+   addField("poolData", TYPEID<afxParticlePoolData>(), Offset(pool_datablock, ParticleEmitterData));
+   addField("poolIndex",            TypeS32,                      Offset(pool_index,        ParticleEmitterData));
+   addField("poolDepthFade",        TypeBool,                     Offset(pool_depth_fade,   ParticleEmitterData));
+   addField("poolRadialFade",       TypeBool,                     Offset(pool_radial_fade,  ParticleEmitterData));
+   endGroup("AFX Pooled Particles");
+#endif
    // disallow some field substitutions
    disableFieldSubstitutions("particles");
    onlyKeepClearSubstitutions("poolData"); // subs resolving to "~~", or "~0" are OK
@@ -365,6 +402,22 @@ void ParticleEmitterData::packData(BitStream* stream)
    stream->writeFlag(renderReflection);
    stream->writeFlag(glow);
    stream->writeInt( blendStyle, 4 );
+
+   stream->writeFlag(ejectionInvert);
+   stream->writeFlag(fade_color);
+   stream->writeFlag(fade_alpha);
+   stream->writeFlag(fade_size);
+   stream->writeFlag(use_emitter_xfm);
+
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+   if (stream->writeFlag(pool_datablock))
+   {
+     stream->writeRangedU32(packed ? SimObjectId((uintptr_t)pool_datablock) : pool_datablock->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast);
+     stream->write(pool_index);
+     stream->writeFlag(pool_depth_fade);
+     stream->writeFlag(pool_radial_fade);
+   }
+#endif 
 }
 
 //-----------------------------------------------------------------------------
@@ -428,6 +481,22 @@ void ParticleEmitterData::unpackData(BitStream* stream)
    renderReflection = stream->readFlag();
    glow = stream->readFlag();
    blendStyle = stream->readInt( 4 );
+   ejectionInvert = stream->readFlag();
+   fade_color = stream->readFlag();
+   fade_alpha = stream->readFlag();
+   fade_size = stream->readFlag();
+   use_emitter_xfm = stream->readFlag();
+   
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+   if (stream->readFlag())
+   {
+      pool_datablock = (afxParticlePoolData*)stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+      stream->read(&pool_index);
+      pool_depth_fade = stream->readFlag();
+      pool_radial_fade = stream->readFlag();
+      do_pool_id_convert = true;
+   }
+#endif 
 }
 
 //-----------------------------------------------------------------------------
@@ -607,6 +676,22 @@ bool ParticleEmitterData::preload(bool server, String &errorStr)
 
    if (!server)
    {
+#if defined(AFX_CAP_PARTICLE_POOLS)
+      if (do_pool_id_convert)
+      {
+        SimObjectId db_id = (SimObjectId)(uintptr_t)pool_datablock;
+        if (db_id != 0)
+        {
+          // try to convert id to pointer
+          if (!Sim::findObject(db_id, pool_datablock))
+          {
+            Con::errorf("ParticleEmitterData::reload() -- bad datablockId: 0x%x (poolData)", db_id);
+          }
+        }
+        do_pool_id_convert = false;
+      }
+#endif
+
      // load emitter texture if specified
      if (textureName && textureName[0])
      {
@@ -676,6 +761,8 @@ void ParticleEmitterData::allocPrimBuffer( S32 overrideSize )
 
    partListInitSize = maxPartLife / (ejectionPeriodMS - periodVarianceMS);
    partListInitSize += 8; // add 8 as "fudge factor" to make sure it doesn't realloc if it goes over by 1
+   if (parts_per_eject > 1)
+     partListInitSize *= parts_per_eject;
 
    // if override size is specified, then the emitter overran its buffer and needs a larger allocation
    if( overrideSize != -1 )
@@ -871,7 +958,16 @@ ParticleEmitter::ParticleEmitter()
 
    // ParticleEmitter should be allocated on the client only.
    mNetFlags.set( IsGhost );
+   fade_amt = 1.0f;
+   forced_bbox = false;
+   db_temp_clone = false;
+   pos_pe.set(0,0,0);
+   sort_priority = 0;
    mDataBlock = 0;
+   
+#if defined(AFX_CAP_PARTICLE_POOLS)
+   pool = 0;
+#endif 
 }
 
 //-----------------------------------------------------------------------------
@@ -920,6 +1016,11 @@ bool ParticleEmitter::onAdd()
    mObjBox.maxExtents = Point3F(radius, radius, radius);
    resetWorldBox();
 
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+   if (pool)
+     pool->addParticleEmitter(this);
+#endif
+
    return true;
 }
 
@@ -929,6 +1030,14 @@ bool ParticleEmitter::onAdd()
 //-----------------------------------------------------------------------------
 void ParticleEmitter::onRemove()
 {
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+  if (pool)
+  {
+    pool->removeParticleEmitter(this);
+    pool = 0;
+  }
+#endif
+
    removeFromScene();
    Parent::onRemove();
 }
@@ -1015,6 +1124,11 @@ LinearColorF ParticleEmitter::getCollectiveColor()
 //-----------------------------------------------------------------------------
 void ParticleEmitter::prepRenderImage(SceneRenderState* state)
 {
+#if defined(AFX_CAP_PARTICLE_POOLS)
+   if (pool)
+     return;
+#endif 
+
    if( state->isReflectPass() && !getDataBlock()->renderReflection )
       return;
 
@@ -1043,6 +1157,7 @@ void ParticleEmitter::prepRenderImage(SceneRenderState* state)
    ri->translucentSort = true;
    ri->type = RenderPassManager::RIT_Particle;
    ri->sortDistSq = getRenderWorldBox().getSqDistanceToPoint( camPos );
+   ri->defaultKey = (-sort_priority*100);
 
    // Draw the system offscreen unless the highResOnly flag is set on the datablock
    ri->systemState = ( getDataBlock()->highResOnly ? PSS_AwaitingHighResDraw : PSS_AwaitingOffscreenDraw );
@@ -1146,6 +1261,7 @@ void ParticleEmitter::emitParticles(const Point3F& point,
       return;
    }
 
+   pos_pe = point;
    Point3F realStart;
    if( useLastPosition && mHasLastPosition )
       realStart = mLastPosition;
@@ -1213,7 +1329,8 @@ void ParticleEmitter::emitParticles(const Point3F& start,
          // Create particle at the correct position
          Point3F pos;
          pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds));
-         addParticle(pos, axis, velocity, axisx);
+         addParticle(pos, axis, velocity, axisx, numMilliseconds-currTime);
+
          particlesAdded = true;
          mNextParticleTime = 0;
       }
@@ -1243,7 +1360,7 @@ void ParticleEmitter::emitParticles(const Point3F& start,
       // Create particle at the correct position
       Point3F pos;
       pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds));
-      addParticle(pos, axis, velocity, axisx);
+      addParticle(pos, axis, velocity, axisx, numMilliseconds-currTime);
       particlesAdded = true;
 
       //   This override-advance code is restored in order to correctly adjust
@@ -1268,17 +1385,27 @@ void ParticleEmitter::emitParticles(const Point3F& start,
          {
             if (advanceMS != 0)
             {
-              F32 t = F32(advanceMS) / 1000.0;
+               F32 t = F32(advanceMS) / 1000.0;
+
+               Point3F a = last_part->acc;
+               a -= last_part->vel * last_part->dataBlock->dragCoefficient;
+               a -= mWindVelocity * last_part->dataBlock->windCoefficient;
+               //a += Point3F(0.0f, 0.0f, -9.81f) * last_part->dataBlock->gravityCoefficient;
+               a.z += -9.81f*last_part->dataBlock->gravityCoefficient; // as long as gravity is a constant, this is faster
+
+               last_part->vel += a * t;
+               //last_part->pos += last_part->vel * t;
+               last_part->pos_local += last_part->vel * t;
 
-              Point3F a = last_part->acc;
-              a -= last_part->vel * last_part->dataBlock->dragCoefficient;
-              a -= mWindVelocity * last_part->dataBlock->windCoefficient;
-              a += Point3F(0.0f, 0.0f, -9.81f) * last_part->dataBlock->gravityCoefficient;
+               // AFX -- allow subclasses to adjust the particle params here
+               sub_particleUpdate(last_part);
 
-              last_part->vel += a * t;
-              last_part->pos += last_part->vel * t;
+               if (last_part->dataBlock->constrain_pos)
+                  last_part->pos = last_part->pos_local + this->pos_pe;
+               else
+                  last_part->pos = last_part->pos_local;
 
-              updateKeyData( last_part );
+               updateKeyData( last_part );
             }
          }
       }
@@ -1350,7 +1477,7 @@ void ParticleEmitter::emitParticles(const Point3F& rCenter,
       axis.normalize();
       pos += rCenter;
 
-      addParticle(pos, axis, velocity, axisz);
+      addParticle(pos, axis, velocity, axisz, 0);
    }
 
    // Set world bounding box
@@ -1373,6 +1500,8 @@ void ParticleEmitter::emitParticles(const Point3F& rCenter,
 //-----------------------------------------------------------------------------
 void ParticleEmitter::updateBBox()
 {
+   if (forced_bbox)
+     return;
    Point3F minPt(1e10,   1e10,  1e10);
    Point3F maxPt(-1e10, -1e10, -1e10);
 
@@ -1393,15 +1522,18 @@ void ParticleEmitter::updateBBox()
    boxScale.y = getMax(boxScale.y, 1.0f);
    boxScale.z = getMax(boxScale.z, 1.0f);
    mBBObjToWorld.scale(boxScale);
+
+#if defined(AFX_CAP_PARTICLE_POOLS)
+   if (pool)
+     pool->updatePoolBBox(this);
+#endif
 }
 
 //-----------------------------------------------------------------------------
 // addParticle
 //-----------------------------------------------------------------------------
-void ParticleEmitter::addParticle(const Point3F& pos,
-                                  const Point3F& axis,
-                                  const Point3F& vel,
-                                  const Point3F& axisx)
+void ParticleEmitter::addParticle(const Point3F& pos, const Point3F& axis, const Point3F& vel,
+                                  const Point3F& axisx, const U32 age_offset)
 {
    n_parts++;
    if (n_parts > n_part_capacity || n_parts > mDataBlock->partListInitSize)
@@ -1423,6 +1555,16 @@ void ParticleEmitter::addParticle(const Point3F& pos,
    pNew->next = part_list_head.next;
    part_list_head.next = pNew;
 
+   // for earlier access to constrain_pos, the ParticleData datablock is chosen here instead
+   // of later in the method.
+   U32 dBlockIndex = gRandGen.randI() % mDataBlock->particleDataBlocks.size();
+   ParticleData* part_db = mDataBlock->particleDataBlocks[dBlockIndex];
+   // set start position to world or local space
+   Point3F pos_start; 
+   if (part_db->constrain_pos)
+     pos_start.set(0,0,0);
+   else
+     pos_start = pos;
    Point3F ejectionAxis = axis;
    F32 theta = (mDataBlock->thetaMax - mDataBlock->thetaMin) * gRandGen.randF() +
                mDataBlock->thetaMin;
@@ -1444,14 +1586,17 @@ void ParticleEmitter::addParticle(const Point3F& pos,
    F32 initialVel = mDataBlock->ejectionVelocity;
    initialVel    += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance;
 
-   pNew->pos = pos + (ejectionAxis * (mDataBlock->ejectionOffset + mDataBlock->ejectionOffsetVariance* gRandGen.randF()) );
-   pNew->vel = ejectionAxis * initialVel;
-   pNew->orientDir = ejectionAxis;
+   pNew->pos = pos_start + (ejectionAxis * (mDataBlock->ejectionOffset + mDataBlock->ejectionOffsetVariance* gRandGen.randF()) );
+   pNew->pos_local = pNew->pos;
+   pNew->vel = mDataBlock->ejectionInvert ? ejectionAxis * -initialVel : ejectionAxis * initialVel;
+   if (mDataBlock->orientParticles)
+     pNew->orientDir = ejectionAxis;
+   else
+     // note -- for non-oriented particles, we use orientDir.x to store the billboard start angle.
+     pNew->orientDir.x = mDegToRad(part_db->start_angle + part_db->angle_variance*2.0f*gRandGen.randF() - part_db->angle_variance);
    pNew->acc.set(0, 0, 0);
-   pNew->currentAge = 0;
-
-   // Choose a new particle datablack randomly from the list
-   U32 dBlockIndex = gRandGen.randI() % mDataBlock->particleDataBlocks.size();
+   pNew->currentAge = age_offset;
+   pNew->t_last = 0.0f;
    mDataBlock->particleDataBlocks[dBlockIndex]->initializeParticle(pNew, vel);
    updateKeyData( pNew );
 
@@ -1533,8 +1678,10 @@ void ParticleEmitter::updateKeyData( Particle *part )
 	if( part->totalLifetime < 1 )
 		part->totalLifetime = 1;
 
-   F32 t = F32(part->currentAge) / F32(part->totalLifetime);
-   AssertFatal(t <= 1.0f, "Out out bounds filter function for particle.");
+   if (part->currentAge > part->totalLifetime)
+      part->currentAge = part->totalLifetime;
+   F32 t = (F32)part->currentAge / (F32)part->totalLifetime;
+  
 
    for( U32 i = 1; i < ParticleData::PDC_NUM_KEYS; i++ )
    {
@@ -1566,7 +1713,25 @@ void ParticleEmitter::updateKeyData( Particle *part )
          {
             part->size = (part->dataBlock->sizes[i-1] * (1.0 - firstPart)) +
                          (part->dataBlock->sizes[i]   * firstPart);
+            part->size *= part->dataBlock->sizeBias;
          }
+		 
+         if (mDataBlock->fade_color) 
+         {
+           if (mDataBlock->fade_alpha) 
+             part->color *= fade_amt; 
+           else 
+           {
+             part->color.red *= fade_amt; 
+             part->color.green *= fade_amt; 
+             part->color.blue *= fade_amt; 
+           }
+         }
+         else if (mDataBlock->fade_alpha) 
+           part->color.alpha *= fade_amt;
+
+         if (mDataBlock->fade_size)
+           part->size *= fade_amt;
          break;
 
       }
@@ -1578,19 +1743,25 @@ void ParticleEmitter::updateKeyData( Particle *part )
 //-----------------------------------------------------------------------------
 void ParticleEmitter::update( U32 ms )
 {
-   // TODO: Prefetch
+   F32 t = F32(ms)/1000.0f; // AFX -- moved outside loop, no need to recalculate this for every particle
 
    for (Particle* part = part_list_head.next; part != NULL; part = part->next)
    {
-      F32 t = F32(ms) / 1000.0;
-
       Point3F a = part->acc;
-      a -= part->vel        * part->dataBlock->dragCoefficient;
+      a -= part->vel * part->dataBlock->dragCoefficient;
       a -= mWindVelocity * part->dataBlock->windCoefficient;
-      a += Point3F(0.0f, 0.0f, -9.81f) * part->dataBlock->gravityCoefficient;
+      a.z += -9.81f*part->dataBlock->gravityCoefficient; // AFX -- as long as gravity is a constant, this is faster
 
       part->vel += a * t;
-      part->pos += part->vel * t;
+      part->pos_local += part->vel * t; 
+
+      // AFX -- allow subclasses to adjust the particle params here
+      sub_particleUpdate(part);
+
+      if (part->dataBlock->constrain_pos)
+        part->pos = part->pos_local + this->pos_pe;
+      else
+        part->pos = part->pos_local;
 
       updateKeyData( part );
    }
@@ -2153,3 +2324,43 @@ DefineEngineMethod(ParticleEmitterData, reload, void,(),,
 {
    object->reload();
 }
+void ParticleEmitter::emitParticlesExt(const MatrixF& xfm, const Point3F& point, 
+                                       const Point3F& velocity, const U32 numMilliseconds)
+{
+   if (mDataBlock->use_emitter_xfm)
+   {
+      Point3F zero_point(0.0f, 0.0f, 0.0f);
+      this->pos_pe = zero_point;
+      this->setTransform(xfm);
+      Point3F axis(0.0,0.0,1.0);    
+      xfm.mulV(axis);
+      emitParticles(zero_point, true, axis, velocity, numMilliseconds);
+   }
+   else
+   {
+      this->pos_pe = point;
+      Point3F axis(0.0,0.0,1.0);    
+      xfm.mulV(axis);
+      emitParticles(point, true, axis, velocity, numMilliseconds);
+   }
+}  
+
+void ParticleEmitter::setForcedObjBox(Box3F& box) 
+{
+  mObjBox = box;
+  forced_bbox = true;
+#if defined(AFX_CAP_PARTICLE_POOLS)
+  if (pool)
+    pool->updatePoolBBox(this);
+#endif
+}
+
+void ParticleEmitter::setSortPriority(S8 priority) 
+{
+  sort_priority = (priority == 0) ? 1 : priority;
+#if defined(AFX_CAP_PARTICLE_POOLS)
+  if (pool)
+    pool->setSortPriority(sort_priority);
+#endif
+}
+

+ 56 - 1
Engine/source/T3D/fx/particleEmitter.h

@@ -24,6 +24,7 @@
 // Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
 // Copyright (C) 2015 Faust Logic, Inc.
 //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
 #ifndef _H_PARTICLE_EMITTER
 #define _H_PARTICLE_EMITTER
 
@@ -46,6 +47,12 @@
 class RenderPassManager;
 class ParticleData;
 
+#define AFX_CAP_PARTICLE_POOLS
+#if defined(AFX_CAP_PARTICLE_POOLS)
+class afxParticlePoolData;
+class afxParticlePool;
+#endif
+
 //*****************************************************************************
 // Particle Emitter Data
 //*****************************************************************************
@@ -117,6 +124,21 @@ class ParticleEmitterData : public GameBaseData
    bool glow;                                ///< Renders this emitter into the glow buffer.
 
    bool reload();
+public:
+   bool         fade_color;
+   bool         fade_size;
+   bool         fade_alpha;
+   bool         ejectionInvert;
+   U8           parts_per_eject;
+   bool         use_emitter_xfm;
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+public:
+   afxParticlePoolData* pool_datablock;
+   U32          pool_index;
+   bool         pool_depth_fade;
+   bool         pool_radial_fade;
+   bool         do_pool_id_convert;
+#endif
 public:
    /*C*/ ParticleEmitterData(const ParticleEmitterData&, bool = false);
    /*D*/ ~ParticleEmitterData();
@@ -130,6 +152,9 @@ public:
 class ParticleEmitter : public GameBase
 {
    typedef GameBase Parent;
+#if defined(AFX_CAP_PARTICLE_POOLS) 
+   friend class afxParticlePool;
+#endif 
 
   public:
 
@@ -199,7 +224,7 @@ class ParticleEmitter : public GameBase
    /// @param   axis
    /// @param   vel   Initial velocity
    /// @param   axisx
-   void addParticle(const Point3F &pos, const Point3F &axis, const Point3F &vel, const Point3F &axisx);
+   void addParticle(const Point3F &pos, const Point3F &axis, const Point3F &vel, const Point3F &axisx, const U32 age_offset);
 
 
    inline void setupBillboard( Particle *part,
@@ -236,7 +261,12 @@ class ParticleEmitter : public GameBase
    // PEngine interface
   private:
 
+   // AFX subclasses to ParticleEmitter require access to some members and methods of
+   // ParticleEmitter which are normally declared with private scope. In this section,
+   // protected and private scope statements have been inserted inline with the original
+   // code to expose the necessary members and methods.
    void update( U32 ms );
+protected:
    inline void updateKeyData( Particle *part );
  
 
@@ -248,25 +278,30 @@ class ParticleEmitter : public GameBase
 
    ParticleEmitterData* mDataBlock;
 
+protected: 
    U32       mInternalClock;
 
    U32       mNextParticleTime;
 
    Point3F   mLastPosition;
    bool      mHasLastPosition;
+private:   
    MatrixF   mBBObjToWorld;
 
    bool      mDeleteWhenEmpty;
    bool      mDeleteOnTick;
 
+protected: 
    S32       mLifetimeMS;
    S32       mElapsedTimeMS;
 
+private:  
    F32       sizes[ ParticleData::PDC_NUM_KEYS ];
    LinearColorF    colors[ ParticleData::PDC_NUM_KEYS ];
 
    GFXVertexBufferHandle<ParticleVertexType> mVertBuff;
 
+protected:
    //   These members are for implementing a link-list of the active emitter 
    //   particles. Member part_store contains blocks of particles that can be
    //   chained in a link-list. Usually the first part_store block is large
@@ -277,8 +312,28 @@ class ParticleEmitter : public GameBase
    Particle   part_list_head;
    S32        n_part_capacity;
    S32        n_parts;
+private:    
    S32       mCurBuffSize;
 
+  protected:
+   F32 fade_amt;
+   bool forced_bbox;
+   bool db_temp_clone;
+   Point3F pos_pe;
+   S8 sort_priority;
+   virtual void sub_particleUpdate(Particle*) { }
+  public:
+   virtual void emitParticlesExt(const MatrixF& xfm, const Point3F& point, const Point3F& velocity, const U32 numMilliseconds);
+   void setFadeAmount(F32 amt) { fade_amt = amt; }  
+   void setForcedObjBox(Box3F& box);
+   void setSortPriority(S8 priority);
+#if defined(AFX_CAP_PARTICLE_POOLS)
+  protected:
+   afxParticlePool* pool;
+  public:
+   void clearPool() { pool = 0; }
+   void setPool(afxParticlePool* p) { pool = p; }
+#endif
 };
 
 #endif // _H_PARTICLE_EMITTER