diff --git a/Engine/source/T3D/fx/particleEmitter.cpp b/Engine/source/T3D/fx/particleEmitter.cpp index d59bef4e3..4005607d7 100644 --- a/Engine/source/T3D/fx/particleEmitter.cpp +++ b/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(), 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 +} + diff --git a/Engine/source/T3D/fx/particleEmitter.h b/Engine/source/T3D/fx/particleEmitter.h index f4da1ed53..0750ab864 100644 --- a/Engine/source/T3D/fx/particleEmitter.h +++ b/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 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