Game Development Community

dev|Pro Game Development Curriculum

Improved particle system for Torque 3D

by Lukas Joergensen · 05/20/2012 (7:08 pm) · 36 comments

Download it here

I wrote a pdf which is included in the download. The pdf should document the code decently so i will use this page mostly for credits and showcase!

Showoff thread!

Torque CE wiki page

(Some differences may apply)

What is this resource?

This resource extends the particleEmitter class and adds a new graphEmitter class.
The change to the regular particleEmitter class lies in the ability to change values on a per emitter basis rather than a per datablock basis. Read more about this here
The change that this resource will focus on tho is the graphEmitter class. The graphEmitter class is basing the position of the particles on 3 functions, one for each coordinate.
So if the function for x is x=1 it would be placed 1 meter from the graphEmitter along the x-axis.

Combined with the powerful muParser, it is very easy to create nice looking effects like the ones above.

To do list:
  • Better documentation and commenting of the code
  • Bugfixes & cleanup of the code
  • Possibly benchmark
Did i miss one? Let me know!

Please do confirm that the documentation is OK and nothing needs clarified. And do tell me if i need to upload some files!
And you are always welcome to ask all the questions you want!


A video showing almost all features of 2.0.0


An environmental example


Composite functions
If you want to do composite functions you can do it via the muParser syntax!
Here is an example of the spiralNode with a variable function!
datablock GraphEmitterNodeData(g_compSpiralNode : g_DefaultNode)
{
   xFunc = "t<500 ? cos(t/50)*t*0.02 : t<1000 ? cos(t/50)*t*0.1 : t<1500 ? cos(t/50)*t*0.02 : cos(t/50)*t*0.1";
   yFunc = "t<500 ? sin(t/50)*t*0.02 : t<1000 ? sin(t/50)*t*0.1 : t<1500 ? sin(t/50)*t*0.02 : sin(t/50)*t*0.1";
   zFunc = "t/20";
};

Oh almost forgot!
I got all changes to the particleEmitter files written here:
http://nakednerd.clan-net.dk/tutorials/read?tut=t3d1

Changelog

Hotfix 04/06-2012
  • Fixed a mistake which would bug out the projectiles if standAlone particle emitters was installed.
  • Documentation not updated (ood)

  • Hotfix 01/06-2012
  • Fixed a severe memory issue! If you have downloaded your copy of the resource before this date you should download it again or apply the small fix on the last page of the comments.
  • Update v1.0.2
  • Added simple particle physics
  • Raycast based particle collision
  • Attractive and repulsive particles
  • Easily customizeable settings

  • Update v1.0.2
  • Better documentation
  • Cleaner code
  • New syntax for dynamic variables ( For better compatibility with Twillex )

  • Update v1.0.1
  • Now takes the emitter rotation into account when emitting new particles
  • Fixed a possible issue where emitters wouldn't delete when you called %emitter.delete()
  • Added dynamic variables
  • Added reverse Boolean
  • Added loop Boolean
  • Fixed timescale
  • Added onBoundaryLimit callbacks

  • Credits

    Thanks to Ingo Berg for releasing this powerful tool (muParser)! [MIT license]
    Thanks to Charlie Patterson, hes Twillex engine is what inspired me to all of this, and i ensure you that if you combine the Twillex engine with this resource you will have some vastly powerful tools at your hands!
    Thanks to everyone who downloads this resource! If you create something magnificent please show it off in the comments below!
    Page«First 1 2 Next»
    #22
    05/28/2012 (1:00 am)
    Added a donation link to the post, donating is not must but it allows me to spend more time on improving the resource and documentation.

    BTW if you have any wishes for improvements
    (features as well as bug fixes) just write the suggestions here and I will look into it.
    #23
    05/29/2012 (12:46 pm)
    Lukas, you commit the resource to the repository of the community edition? Or did i do?
    #24
    05/29/2012 (12:49 pm)
    Oh you are welcome to do it! I wanted to do it today but when I had a look at it I was unsure of where to put it so I chose to procastinate it :)
    #25
    05/30/2012 (1:11 pm)
    Dropping the donation idea, have something else in mind.
    #26
    05/30/2012 (4:42 pm)
    There was a GarageGames programmer who posted a bunch of particle tricks a couple of years ago - I think you may have exceeded what he did!
    #27
    05/31/2012 (12:00 am)
    @Lukas, i just had to commit to the shared version of t3d. But i had some problems, and i have had to modify the code a bit. Make changes to how you have published, impedes the correct functioning of the old particle system. I have also better integrated with the editor.I suggest you to inherit classes of T3D, instead of copying them like you did.

    so:
    class GraphEmitterData : public ParticleEmitterData
    instead:
    class GraphEmitterData : public GameBaseData

    and call the Parent where required.

    Unfortunately I have no time to do it O_o
    #28
    06/01/2012 (6:36 am)
    @Alfio, I don't understand...
    In my code GraphEmitterData is inheriting the GameBaseData not the ParticleEmitterData.
    And the stock ParticleEmitters is working just as they are supposed to.
    #29
    06/01/2012 (12:55 pm)
    GUYS I am terribly sorry! Clearly it shows that I am a noob at this programming thing.
    I hope you have subscribed to this resource :)
    In graphEmitter.cpp
    Put this
    delete(*funcPos);
       delete(*p);
    After
    pNew->pos = pos + (*p * nodeDat->sa_ejectionOffset);
    pNew->relPos = *p * nodeDat->sa_ejectionOffset;
    And you might get rid of a lot headaches.
    Being a noob and all at c++ and programming in general I did not even give it a thought that I would have to free the memory after using it.
    This will fix a memory leak caused by this resource. I will update the resource files aswell.

    Edit:
    I wanted to make sure there wasn't anymore memory leaks so I made a stress test:
    http://www.youtube.com/watch?v=B51bHCHCBZM&feature=relmfu
    And it seemed to run just fine ( around 20-30 emitters, can't remember the exact amount )
    #30
    06/03/2012 (4:29 pm)
    Hey guys! Seems like I have made another mistake and accidentally overridden the emitParticles function which the projectile class uses!
    Are no one experiencing these errors or are you simply not telling me about them? :P
    To fix paste this in between the emitParticles functions:
    In the particleEmitter.h file:
    void emitParticles(const Point3F& start,
                          const Point3F& end,
                          const Point3F& axis,
                          const Point3F& velocity,
                          const U32      numMilliseconds);
    In the particleEmitter.cpp file:
    #31
    06/03/2012 (4:33 pm)
    void ParticleEmitter::emitParticles(const Point3F& start,
                                        const Point3F& end,
                                        const Point3F& axis,
                                        const Point3F& velocity,
                                        const U32      numMilliseconds)
    {
       if( mDead ) return;
       
       if( mDataBlock->particleDataBlocks.empty() )
          return;
    
       // lifetime over - no more particles
       if( mLifetimeMS > 0 && mElapsedTimeMS > mLifetimeMS )
       {
          return;
       }
    
       U32 currTime = 0;
       bool particlesAdded = false;
    
       Point3F axisx;
       if( mFabs(axis.z) < 0.9f )
          mCross(axis, Point3F(0, 0, 1), &axisx);
       else
          mCross(axis, Point3F(0, 1, 0), &axisx);
       axisx.normalize();
    
       if( mNextParticleTime != 0 )
       {
          // Need to handle next particle
          //
          if( mNextParticleTime > numMilliseconds )
          {
             // Defer to next update
             //  (Note that this introduces a potential spatial irregularity if the owning
             //   object is accelerating, and updating at a low frequency)
             //
             mNextParticleTime -= numMilliseconds;
             mInternalClock += numMilliseconds;
             mLastPosition = end;
             mHasLastPosition = true;
             return;
          }
          else
          {
             currTime       += mNextParticleTime;
             mInternalClock += mNextParticleTime;
             // Emit particle at curr time
    
             // Create particle at the correct position
             Point3F pos;
             pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds));
             addParticle(pos, axis, velocity, axisx);
             particlesAdded = true;
             mNextParticleTime = 0;
          }
       }
    
       while( currTime < numMilliseconds )
       {
          S32 nextTime = mDataBlock->ejectionPeriodMS;
          if( mDataBlock->periodVarianceMS != 0 )
          {
             nextTime += S32(gRandGen.randI() % (2 * mDataBlock->periodVarianceMS + 1)) -
                         S32(mDataBlock->periodVarianceMS);
          }
          AssertFatal(nextTime > 0, "Error, next particle ejection time must always be greater than 0");
    
          if( currTime + nextTime > numMilliseconds )
          {
             mNextParticleTime = (currTime + nextTime) - numMilliseconds;
             mInternalClock   += numMilliseconds - currTime;
             AssertFatal(mNextParticleTime > 0, "Error, should not have deferred this particle!");
             break;
          }
    
          currTime       += nextTime;
          mInternalClock += nextTime;
    
          // Create particle at the correct position
          Point3F pos;
          pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds));
          addParticle(pos, axis, velocity, axisx);
          particlesAdded = true;
    
          //   This override-advance code is restored in order to correctly adjust
          //   animated parameters of particles allocated within the same frame
          //   update. Note that ordering is important and this code correctly 
          //   adds particles in the same newest-to-oldest ordering of the link-list.
          //
          // NOTE: We are assuming that the just added particle is at the head of our
          //  list.  If that changes, so must this...
          U32 advanceMS = numMilliseconds - currTime;
          if (mDataBlock->overrideAdvance == false && advanceMS != 0) 
          {
             Particle* last_part = part_list_head.next;
             if (advanceMS > last_part->totalLifetime) 
             {
               part_list_head.next = last_part->next;
               n_parts--;
               last_part->next = part_freelist;
               part_freelist = last_part;
             } 
             else 
             {
                if (advanceMS != 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;
    
                  last_part->vel += a * t;
                  last_part->pos += last_part->vel * t;
    
                  updateKeyData( last_part );
                }
             }
          }
       }
    
       // DMMFIX: Lame and slow...
       if( particlesAdded == true )
          updateBBox();
    
    
       if( n_parts > 0 && getSceneManager() == NULL )
       {
          gClientSceneGraph->addObjectToScene(this);
          ClientProcessList::get()->addObject(this);
       }
    
       mLastPosition = end;
       mHasLastPosition = true;
    }
    #32
    06/04/2012 (10:50 am)
    Hey guys if you create something nice with this resource please post it in this thread:
    Improved Particle System showoff thread
    I would love to see what you have come up with!
    #33
    06/21/2012 (4:34 pm)
    This is excellent Lukas. Nice work! I still haven't really had the time to implement this yet, but as soon as I do I'll post something in the showoff thread. I have to get those jets sorted at some stage. :)
    #34
    02/27/2014 (6:29 pm)
    Id love to see how you did the afterburner??
    #35
    02/27/2014 (11:42 pm)
    @Donnie can't remember the exact code, but the key is to create an emitter that looks like an afterburner while it's static so you can make the particles "sticky" (that is, make them use object-space coordinates) so that they wont get spread out when the ship is flying around.

    I believe I did it by making a lot of short-lived particles, and interpolate the ejection settings over time (using Twillex) such that they would be ejected in a new pattern all the time.
    #36
    03/03/2014 (6:58 pm)
    Cool Lukas and thank you very much for this and the advice....
    Page«First 1 2 Next»