Particle Engines

Sage
Posts: 1,199
Joined: 2004.10
Post: #16
I'm coming in late here, but here's my take -- respect LongJumper. Not only is his approach simpler, but most importantly, a contiguous array of particles lends itself *much* better to fast drawing via vertex arrays.

In my particle systems ( screenshots: [Image: Legion-2005-06-30-75.png], [Image: Legion-2005-06-30-52.png], and a completely different system, weather: )[Image: Legion-2005-07-12-51.png] ) I use a dynamically allocated buffer which, when full, either retires the oldest particles and overwrites them with new ones, or grows itself, but carefully manages what it considers to be the "top" so it rarely grows more than a few times ( sort of like how std::vector grows exponentially ).

Since you get a continuous hunk of memory, updating is just a matter of bumping a pointer in a loop and drawing can be done via drawArrays so long as you set the right striding.

Now, that's not to say that people don't need to know how to write linked lists. They have their purposes too, obviously, and so do many tree algorithms and so on. But at least in my opinion, if you've got a system with more than a few hundred particles, I'd keep it in an array ( dynamically allocated ).

Now, if you find your code newwing and deleting 80 times a second, regardless of your algorithm, you've got a serious design problem.
Quote this message in a reply
Sage
Posts: 1,066
Joined: 2004.07
Post: #17
Can you (TomorrowPlusX or Long Jumper) give me a chunk of code or pseudo-code on what this might look like? Is it just an array of static objects or an array of pointers? I can see why the fully dynamic thing is inefficient but I'm just not getting exactly what you're meaning (as far as dynamically allocated buffer and such).
Quote this message in a reply
Moderator
Posts: 1,140
Joined: 2005.07
Post: #18
Just out of curiosity, how many particles on the screen would it take to have making vertex arrays be worth it? With the game engine that I'm making, I doubt I'll have any more than 100 of the same types of particles on the screen at one time.
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #19
Nick Wrote:Can you (TomorrowPlusX or Long Jumper) give me a chunk of code or pseudo-code on what this might look like? Is it just an array of static objects or an array of pointers? I can see why the fully dynamic thing is inefficient but I'm just not getting exactly what you're meaning (as far as dynamically allocated buffer and such).

Yes, of course. My code's not optimal ( both in performance and design ), and regarding akb825's question about when you should use VA -- my answer is that I've done both and I found immediate mode... faster. But that's because my VA code is shitty. I've kept it with the intent of re-addressing it in the future, which is coming up, since I'm in the middle of a 100% rewrite from the ground up.

Here's my particle system code:
ParticleSystem.h
ParticleSystem.cpp

The important stuff is simple ( and I'd like to point out, it's nowhere near as powerful as LongJumpter's particle system )

Code:
void ParticleSystem::performStep( float deltaT )
{
    vec3 wind = _windSpeed * deltaT;
    unsigned long index = _oldest;

    /*
        Zero our AABB; it will be recreated in the stepping loop
    */
    _aabb.invalidate();

    retireOldParticles();

    while ( index < _newest )
    {
        Particle *puff = &_particles[ index % _max ];
        
        puff->position += puff->velocity * deltaT;
        puff->position.z -= puff->weight * deltaT;        
        
        if ( _affectedByWind ) puff->position += wind;

        puff->velocity.z -= puff->weight * deltaT;

        _aabb += puff->position;
        
        puff->rotation += puff->rotationRate * deltaT;

        index++;        
    }
    
    _sysRot += _sysRotRate * deltaT;
}


void ParticleSystem::retireOldParticles( void )
{
    unsigned long index = _oldest;
    float now = time();
    
    while( index < _newest )
    {
        if ( now - _particles[ index % _max ].inception > _maxAge )
        {
            _oldest++;
        }
        
        index++;
    }
}

And the emitter function:
Code:
void ParticleSystem::emitSpherical( vec3 pos, int num, bool force )
{
    float now = time();
    if ( force || ((_minEmissionDelta <= 0) || (now - _lastEmissionTime > _minEmissionDelta)) )
    {
        if ( !force && ( _minDistBetweenEmission2 > EPSILON ) &&
             ( pos.distance2( _lastEmissionPoint ) < _minDistBetweenEmission2 ))
        {
            if ( _debug )
            {
                Logger::log( LogEntry::Debug, "ParticleSystem",
                    "emitSpherical()\tPoint %s not far enough away from last emission point %s",
                    toString( pos ).c_str(), toString( _lastEmissionPoint ).c_str());
            }
            return;
        }
    
        _lastEmissionPoint = pos;
        _lastEmissionTime = now;

        if ( _debug )
        {
            Logger::log( LogEntry::Debug, "ParticleSystem",
                "emitSpherical()\tEmitting at point %s with num: %d",
                toString( pos ).c_str(), num );
        }
        
        for ( int i = 0; i < num; i++ )
        {            
            Particle *particle = &_particles[ _newest % _max ];

            /*
                Random direction
            */
            vec3 dir( frandrange( -1, 1 ), frandrange( -1, 1 ), frandrange( -1, 1 ) );
            dir.normalizeFast();

            particle->inception    = now;
            particle->position = pos + (dir * frandrange( _minEmissionDist, _maxEmissionDist ));
            particle->velocity = dir * frandrange( _minVel, _maxVel );
            particle->weight = frandrange( _minWeight, _maxWeight );
            particle->fade = frandrange( _minFade, _maxFade );
            particle->lifespan = frandrange( _minAge, _maxAge );

            particle->rotation = frandrange( _minParticleRotRate, _maxParticleRotRate );
            particle->rotationRate = particle->rotation;

            _newest++;
            if ( _newest % _max == _oldest % _max ) _oldest++;    
        }
    }
    else if ( _debug )
    {
        Logger::log( LogEntry::Debug, "ParticleSystem",
            "emitSpherical()\tNot enough time has elapsed to emit a new particle" );
    }
}

Honestly, my particle system isn't the world's awesomest, but it works. My refactoring will involve looking into the VA implementation and trying to see if using mapped buffers will speed it up ( it ought to, really ). So far, as far as I can tell, using vanilla vertex arrays was just slightly slower than direct immediate mode.

Makes no sense to me Blush

One thing to note: I keep track of the particle system's AABB ( the axis-aligned bounding box ) for purpose of scene graph depth sorting and visibility determination. It's a good thing to not have to draw particle systems which are outside the field of view.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  osg::Particle how to change de texture of each particle zinbawe 1 2,955 Jul 14, 2009 12:46 AM
Last Post: DoG
  Particle groups unknown 4 3,284 Nov 4, 2005 10:27 AM
Last Post: Zekaric
  How to do (2D) Particle Systems? Taxxodium 8 5,589 Oct 21, 2002 04:16 PM
Last Post: w_reade