Welcome, Guest! Login | Register

Particle Engine tutorial part 5 [Print this Article]
Posted by: Persuter
Date posted: Apr 04 2003
User Rating: 5 out of 5.0
Number of views: 12453
Number of comments: 15
Description: Part 5 in 5 part series
Part 4

For most purposes, we are now done. You have a way for mappers to access your particle engine, you have an example particle system to develop more systems from, and you have a good, object-oriented particle engine which we can easily extend. So this part of the tutorial is wholly unnecessary, and will basically be some ramblings and a bit of code from me to discuss how to extend your particle engine even farther and make it faster. There are several ways we can imagine to make this particle engine better.

1)We could make it faster, that is, optimize some part of it.
2)We could make it so that systems can be created by people who don't know how to program in C++.
3)In a similar, but not quite the same, vein, we could make it so that creating new systems doesn't require so much similar code.

We'll discuss optimization mostly, as making beautiful code faster is what speeds up the heart of every real programmer. user posted image

First off, the best way to optimize your systems is to not overuse particles. Using large numbers of transparent additive sprites covering a large number of pixels on your screen is a sure way to massive slowdown and lots of complaints. To this end, here are some tips:

1)Make if( part->transparency < MIN_SPRITE_TRANSPARENCY ) return false; a part of all your TestParticle functions, and then experiment with MIN_SPRITE_TRANSPARENCY. You'll find that sprites become, for all intents and purposes, more or less invisible at a transparency much higher than 0. This is, of course, only applicable to those particle systems which change the alpha of particles (smoke systems generally do, for example).
2)Don't create too many particles (duuuuh). Oftentimes two or three particles can make a convincing effect, while ten or fifteen particles can make an only slightly more convincing effect, if you don't count the framerate dropping every time you get close to it.
3)If you still seem to be getting a lot of framerate diminishment, change kRenderTransAdd to kRenderTransTexture. This will cause all the sprites to become big black squares, which will help you figure out where you're putting too many sprites. (Change it back after you reduce the particles, unless you like giant opaque black squares.)
4)Don't count on your particle system too much. For example, for purity, you should render smoke by using a lot of static gray blurry particles that fade away slowly as they ascend. But realistically, you can get a very similar effect using a very small number of animated sprites that show a much larger animated blurry blotch that grows and fades. Always look for ways that animated sprites can help you achieve effects that the particle system by itself can't.

The most annoying thing about the framerate drop is that the systems that most fill up the screen are the ones you can least afford to skimp on. For example, drawing smoke coming out of the barrel and muzzle of a gun is a popular usage of particle systems, but you'll note that these are awful close to your face, and thus really contribute to this slowdown. Unfortunately, any �shortcuts� you take on a particle system that occurs that often and is that close to your face are probably going to be noticeable. C'est la vie.

OK, so that's how to optimize in kind of a stupid, non-coding fashion. But how can we make this particle engine, which I admitted is not the fastest code in the world, faster? Well, we can start by not always using new and delete every time we want to create a new or get rid of an old particle. We'd prefer a global pool of particles that we can dip into any time we need to, so that's exactly what we're going to create. By the way, what we're about to do is a good example of the worth of object-oriented programming. I added this change about a year after I designed the first iteration of the particle system, and while, realistically, it doesn't do that much (especially since I was already doing some other memory optimization), you'll see how it is extremely easy to do because we adhered to good coding practices earlier.

So what we want is a global pool of particles. One way we could do this is by creating an array of particles and then simply passing over the pointers to various indices in the array. However, that's not really what I want to do, because it isn't scalable. If you define the array to be 5000 particles and during a hot and heavy combat scene there needs to be 5001, you're SOL. Instead, we want to be able to get particles any time we want to. Moreover, we want to be sure that it doesn't take that long (on the average) to get a particle.

So, we will start by removing all the code from ParticleSystem::TrashParticle, and simply replacing it with:

 CODE (C++) 
void ParticleSystem::TrashParticle( particle* part )
{
    GetManager()->TrashParticle( part );
}


We then change the line in ParticleSystem::AddParticle that reads �particle* newpart = new particle();� to:

 CODE (C++) 
particle* newpart = GetManager()->GetParticle();


Now, you'll notice that because we didn't let our memory management leak outside these two functions, if we now make the functions they call work correctly, this should be a plug-and-play addition, that is, we won't have to change ANYTHING.

We then go to ParticleSystemManager, and add two public functions, particle* GetParticle( void ) and void TrashParticle( particle* ). We also add two private variables, specifically, particle* m_pHeadPoolParticle and int m_iParticlesCreated.

We then go to the ParticleSystemManager constructor, and we add the following:

 CODE (C++) 
m_pHeadPoolParticle = new particle();
m_pHeadPoolParticle->nextpart = NULL;
m_iParticlesCreated = 1;


Now, we'll do TrashParticle first because it is the simplest. It simply reads:

 CODE (C++) 
void ParticleSystemManager::TrashParticle( particle* part )
{
    part->nextpart = m_pHeadPoolParticle;
    m_pHeadPoolParticle = part;
}


Nothing we haven't seen before; it's our standard way of adding a particle to a linked list. Next, we do GetParticle, which is a bit more complicated (I explain below).

 CODE (C++) 
particle* ParticleSystemManager::GetParticle( particle part )
{
    if( !m_pHeadPoolParticle )
    {
        particle* newpart;
        for( int i = 0; i < m_iParticlesCreated; i++ )
        {
            newpart = new particle();
            newpart->nextpart = m_pHeadPoolParticle;
            m_pHeadPoolParticle = newpart;
        }  
        m_iParticlesCreated *= 2;
    }

    particle* returnparticle = m_pHeadPoolParticle;
    m_pHeadPoolParticle = m_pHeadPoolParticle->nextpart;
    return returnparticle;
}


OK, so what's going on here? Basically, the idea is that we maintain a global linked list of unused particles. As particle systems need them, they request them and we return them. In the case that we don't have any more unused particles, we create as many particles as we already had in existence. So if we had 300 particles, we would create 300 more. Then, when we use up those 300, we now have 600 total particles, so we create 600 more. This is a constant-time algorithm for dynamically creating an array (that basically means that this is an intelligent way to do it).

How does this help us? Simply by reducing the amount of memory management we have to do. It is foolish to be constantly allocating and deallocating memory for particles when we could simply be reusing the same particles over and over again. Now, you may note that if there is a very large usage of particle systems at one time, followed by a long period of no usage, we will be wasting memory in unused particles. However, the actual amount of memory wasted is fairly small, it realistically can't be more than a megabyte even under the most extreme circumstances (unless you are WAAAAY overusing the engine). If you're still nervous about that, you can do some simple culling by remembering how many particles are in use, and then deallocating particles when, say, less than half of them are in use.

OK, now we're going to see if we can't keep from drawing particles that do not appear on the screen. This will actually be fairly simple, but we will do it at some loss in flexibility. Specifically, I will here assume that you are using part->origin as the actual origin of the particle, as opposed to some relative origin. That is, I'm assuming that you aren't adding the origin of the particle to the system origin to come up with the world origin for drawing. If you're shaking your head in confusion at this point, never mind. Keep on keeping on. We will modify the part in ParticleSystem::DrawSystem where we call DrawParticle very slightly to the following:

 CODE (C++) 
    // If it should we update and draw the particle
    UpdateParticle( part );
    if( ParticleIsVisible( part ) )
        DrawParticle( part );


We also add BOOL ParticleIsVisible( particle* part ) to the private class definition of ParticleSystem.

Now, we define ParticleIsVisible like so:

 CODE (C++) 
BOOL ParticleSystem::ParticleIsVisible( particle* part )
{
    vec3_t screencoord;
    return ( (gEngfuncs.pTriAPI->WorldToScreen( part->origin, screencoord )) != 1);
}


OK, so what does this mean? WorldToScreen is a function that takes the 3-D origin of something and translates it onto the screen. So, for example, if you wanted to draw a HUD square around an object in the world, you would use WorldToScreen to do it, or if you wanted to draw the names of players over them on the screen, same thing. WorldToScreen returns 1 if the object is �z clipped�, which means you can't see it. So, if WorldToScreen returns 1, we can be pretty sure that it is not possible to see the particle. Why do I say pretty sure? If you are using large particles, it is possible that the center of the particle will be off-screen but a part of it will still be on the screen to be drawn. Thus, this is probably not the sort of function you want to be using if you are using lots of big particles (which I told you earlier not to use, shame on you).

Now, this is also not the best way to do this particular function. More probably you will want to figure out the corners of the particles, and then find the vector pointing from you to the corners. Then you dot product those vectors with your own view vector to determine the angle that the vectors make with your view vector. If the angle is behind you, or even barely in front of you but far to the side for all the corners, you will want to return false. This is just to give you something to work with.

OK, so those are two good optimizations that may or may not make your particle engine run just a bit faster. The rest of the tutorial will really just be skylarking on my part. I won't provide any code, just suggest a few ways that you could make better or more easily extensible particle systems, so if you simply want something that works, you have it. If you want some extension ideas, though, read on, by all means.

First, and most obvious, one realistically uses the same DrawParticle over and over (especially if you use ParticleIsVisible, since you're assuming that part->origin always means the same thing). Thus, one should simply make a HalfLifeParticleSystem with a generic DrawParticle, and then inherit all the remaining particle systems off of it. That's just sort of obvious. However, you'll notice that you are still reusing a lot of code from the other four functions, since there are a lot of things that particle systems have in common. For example, many of your systems will use gravity. Many of your systems will want to remove particles when they touch the world, or have the particles bounce off the world, and so forth. We can imagine all of these different things as �transformations� applied to each particle. A transformation may be something as obvious as adding gravity to a particle, or it may be something that does not seem like a transformation, such as determining if the particle should be alive or not, or drawing the particle.

Then we might imagine a particle system defined as a chain of modules, each of which does something different to the particles and then passes it off to the next module (or doesn't, if it is testing whether the particle should die). Thus, a snow particle system might be defined as the following: GetManager()->AddSystem( new RandomParticleEmitter( rate, emittingbox, new GravityTransformation( gravityamount, new HorizontalFlutter( flutteramount, new WorldTouchRemoval( new HalfLifeDrawer() ) ) ) ) ); As you can see, we can make entirely new systems simply by putting together prewritten modules in different ways. Another interesting benefit of this system is that you can do temporary things to particles and have an easy way to change them back, specifically, you change the particle, call the remainder of the chain, and then change the particle back. The remainder of the chain will treat the particle as if it had whatever changes you made to it, but the changes will not be permanent. This could be used to allow the engine to draw the particle properly even when you are using the relative origin I mentioned earlier with ParticleIsVisible.

Even more cryptic, but perhaps faster because of the static linkage, one could envision chains of templated modules, rather than simple linked lists. (I'd provide an example but this site isn't happy about < and > brackets.) I'm not even entirely sure that such a system is possible, but I think it is. This would cause the compiler to specifically compile exactly what system you want, with static run-time memory allocation rather than dynamic, causing large speedups, especially if you create a lot of pretty complicated systems.

Alright, I think that's enough skylarking. If you have any ideas, suggestions, corrections, questions, or anything else, as always, please feel free to email me at persuter@planethalflife.com, and I will be happy to answer you. If you use this code in a mod, email me and let me know how it worked for you. Thanks for reading, and I hope reading this tutorial was as educational for you as writing it has been for me.

Part 4

Rate This Article
This article is currently rated: 5 out of 5.0 (5 Votes)

You have to register to rate this article.
User Comments Showing comments 1-15

Posted By: Onions on Sep 20 2003 at 21:34:13
Excellent set of tutorials, comments helped me understand everything. Great job.

Posted By: infiniteloop on Jan 05 2004 at 20:52:36
Great set of Tutorials. Extremely easy to understand (if you know C++) ;) Looking forward to any other tutorials on coding you have.....

Posted By: InternetNightmare on May 03 2004 at 19:21:35
Yeah I really liked it! Very educational... I learned some TriAPI basics too ;)

Posted By: prince_vegita on May 16 2004 at 11:00:23
Very nice tutorial your my hero :D lol

Posted By: jim_the_coder on Jun 08 2004 at 19:13:09
[edit]

I've done this tut, and the only thing I've got an issue with is the TestSystem bit...you left it unfinished and so the system is never removed. It appeared to me that particle systems survived across restarts and map changes, so the framerate got slower and slower...for now I've solved this by putting a m_iIntermission check in so that it just deletes all the particle systems when the intermission starts, but obviously this isn't optimal because players can still see wherever they currently are so snow/steam/fog suddenly disappears. Where would be the best place to either put a call to the clearsystems function or what's a good variable that the system itself can check to see if the maps restarted/changed? I found some interesting stuff in dll_state.h in the \common folder (DLL_INACTIVE, DLL_PAUSED, DLL_TRANS etc), but I don't think it's used by the DLL...Edited by jim_the_coder on Jun 08 2004, 20:50:22

Posted By: Persuter on Jun 21 2004 at 12:18:53
I think I mentioned this while talking about ClearSystems. I put a call to ClearSystems in the CHud destructor, I believe, or maybe InitHUD, I can't actually remember. :P But SOMEWHERE. That's the whole reason for having ClearSystems -- so that you can clear all the systems at the end of a map.

Posted By: PowerSoul on Aug 24 2004 at 15:25:53
There's a problem in singleplayer mods using this:
The systems keep spawning particles even while user is in console/game is paused, is there a way around it?

Posted By: PowerSoul on Aug 24 2004 at 22:22:49
ok well there's a simple fix for the "prob" i mentioned (thanks ghoul ^^), just add a check in UpdateSystem for if difftime is something else than 0.0, and only spawn new particles if it is.

Posted By: Persuter on Aug 24 2004 at 22:26:11
lol, that's a great solution. I'll put that in the tut when I get home from work.

Posted By: Deadpool on Jul 24 2009 at 12:20:08
Thanks for plagiarizing my tutorial nearly directly, and giving absolutely no credit or mention of the sources whatsoever.

I wrote the original article in 2000, and posted it on the firearmsmod.com site for a few tutorial sites and individuals at the time. I only asked for the slightest amount of credit, but you've nearly verbatim stolen and reworded the original tutorial, and directly ripped the source-code, and seemingly intentionally neglected to mention you only took somebody elses work and re-worded it, attempting to pass it off as your own. I'd have zero problems if you'd at least made a simple mention of it, even the smallest.

There's an archived message of my original posting: http://www.thewavelength.net/forums/oldthr...ads/007230.html
Although obviously the link which it was originally hosted at is dated and dead.

Good job, cowboy.

Posted By: christoph on Jul 25 2009 at 07:14:24
You got guts man, accuse Persi to rip something?

Posted By: Persuter on Jul 25 2009 at 20:31:45
That is pretty ballsy, Deadpool. I took ONE function on here from your tut, the DrawParticle function, and I SPECIFICALLY attribute it to your particle system right next to it. Indeed, I originally asked for attribution because I didn't know who had written it and you never came by, I had to find out and put it up on my own.

Other than that SINGLE function, the entirety of this tutorial is self-written. Get stuffed. It's a linked list manager, dude.Edited by Persuter on Jul 25 2009, 20:42:12

Posted By: Deadpool on Aug 02 2009 at 05:35:53
Fan-boys aside.
I guess I shouldn't have stated I no longer have a back-up of the original article to draw comparisons from.
But whatever. Half-Life is dead, I dispise Valve software and everything they stand for, and refuse to pay for another product they ever produce. You can have your glory.
But I guarentee you there were alot more similarities than a 'single function.'

And I have no clue how you could say "you have no idea who wrote it," as in the original there were multiple sources of references to get in-touch with me, of who wrote it, including other contributing authors also not listed here.

So be it. Live and let live.
And I'm already stuffed thank you, had a great dinner.

Posted By: Persuter on Aug 02 2009 at 07:14:15
You seriously need to chill out. "Fan-boy"? "Have my glory"?

Honestly, man. There's other things in life to get agitated over.

Posted By: Deadpool on Aug 02 2009 at 08:06:42
The "fan-boy" comment was in-reference to "christoph."
And I'm not sure that was really an expression of anger... "live and let live" and "so be it." (hence needing to 'chill out,' I'm quite relaxed, actually.)
And I agree with you, on that last point.
Peace.


You must register to post a comment. If you have already registered, you must login.

Latest Articles
3rd person View in Multiplayer
Half-Life 2 | Coding | Client Side Tutorials
How to enable it in HL2DM

By: cct | Nov 13 2006

Making a Camera
Half-Life 2 | Level Design
This camera is good for when you join a map, it gives you a view of the map before you join a team

By: slackiller | Mar 05 2006

Making a camera , Part 2
Half-Life 2 | Level Design
these cameras are working monitors that turn on when a button is pushed.

By: slackiller | Mar 04 2006

Storing weapons on ladder
Half-Life 2 | Coding | Snippets
like Raven Sheild or BF2

By: British_Bomber | Dec 24 2005

Implementation of a string lookup table
Half-Life 2 | Coding | Snippets
A string lookup table is a set of functions that is used to convert strings to pre-defined values

By: deathz0rz | Nov 13 2005


Latest Comments
knock knock
General | News
By: omega | Dec 22 2016
 
knock knock
General | News
By: MIFUNE | Oct 10 2015
 
New HL HUD Message System
Half-Life | Coding | Shared Tutorials
By: chbrules | Dec 31 2011
 
knock knock
General | News
By: Whistler | Nov 05 2011
 
Particle Engine tutorial part 4
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 18 2010
 
Particle Engine tutorial part 2
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 11 2010
 
Particle Engine tutorial part 3
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 11 2010
 
Game Movement Series #2: Analog Jumping and Floating
Half-Life 2 | Coding | Shared Tutorials
By: mars3554 | Oct 26 2009
 
Particle Engine tutorial part 5
Half-Life | Coding | Client Side Tutorials
By: Deadpool | Aug 02 2009
 
Particle Engine tutorial part 5
Half-Life | Coding | Client Side Tutorials
By: Persuter | Aug 02 2009
 

Site Info
297 Approved Articless
8 Pending Articles
3940 Registered Members
0 People Online (8 guests)
About - Credits - Contact Us

Wavelength version: 3.0.0.9
Valid XHTML 1.0! Valid CSS!