enhanced-emitter -- numerous enhancements to ParticleEmitter class.

pooled-particles -- optional support for pooled particles which combines multiple emitters in a common sorting pool.
This commit is contained in:
Marc Chapman 2017-07-26 22:45:10 +01:00
parent fc449307f9
commit 43d2399aea
2 changed files with 297 additions and 31 deletions

View file

@ -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;
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->vel += a * t;
//last_part->pos += last_part->vel * t;
last_part->pos_local += last_part->vel * t;
updateKeyData( last_part );
// AFX -- allow subclasses to adjust the particle params here
sub_particleUpdate(last_part);
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 );
}
}
}
@ -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
}

View file

@ -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