View Full Version : Particle system

 H3ro02 February 2007, 05:11 PMHallo, I while ago I started a post about doing fluid simulation. I have now started my quest. As I have limited programming experience I decided to start with something more simple, a 2d particle system. That way I can see how drawing is done and how to handle a massive amount of particles. Also, is it ok to post this kind of things here? I see that most of the questions are very advanced and not much code is posted at all, if the forum is not meant for this kind of thing I am doing, please delete this thread. Anyway, I have gotten the basic to work, but there are some buggs that I need some help with removing: * The particles are forming a square. This makes no sense to me, as the velocity should make them move a equal distance away from where they are created. Should not the form formed be a circle? To make it a bit more clear I have made a small picture: If you look at this picture you see that the particles are only living inside the red square, but should it not be inside the blue circle? http://img214.imageshack.us/img214/1761/picturewl0.jpg * How do I make a rate controle, like there is in e.g Autodesk Mayas particle system? Right now all of my particles are created at ones (made it look a bit more interesting with a random lifespan) * If something else is all wrong let me know Here is my code: Note: void particleUpdate(); is the only interesting thing, the rest is super basic openGL and other basic stuff, but I am posting the whole thing anyway. Compiled on windows XP SP 2, with DEV-C++ #ifdef WIN32 #include #endif #include #include #include #include #include #define MAX_PARTICLES 1000 //------Global variables static GLfloat EmitterPosX = 0; static GLfloat EmitterPosY = 0; //------Global variables /end //------Functions void display(void); void particleInit(int i); void particleUpdate(); void limitFrameRate(double lastTime, float frameRate); //------Functions /end // --------------------------------------------------------------- // Create the particle system // --------------------------------------------------------------- struct particle { float lifeSpan; float age; int number; bool alive; float mass; float inv_mass; //position float posX; float posY; //velocity float vX; float vY; //acceleration float aX; float aY; //forces float fX; float fY; }; //Create the particle variable. //It is created as an array, where //one particle is a member of the array. particle particle[MAX_PARTICLES]; // --------------------------------------------------------------- // Create the particle system /end // --------------------------------------------------------------- // --------------------------------------------------------------- // Update the particle system. // The function is made like this: // // * First check if its ok do make a new fram(limit the frameRate) // * Check if the particle is alive // * Update the particle // --------------------------------------------------------------- void particleUpdate() { //First we check if its ok to update the system //-------------------------------------- //Variable for holding the wanted frameRate //because of the time function we have to use this //formula: FrameRate = 1000 / wantedRate float wantedRate = 100.0; //Should be a global variable? float frameRate = 1000/wantedRate; limitFrameRate(timeGetTime(), frameRate); //Find the timestep, which is the time between //two frames/duration of one frame float timeStep = 1.0/frameRate; //Variables for controling the particles: //-------------------------------------- //Force will be how much the particle moves per frame in //units, so a high value will make the particles move //extremly fast. Force is constant for all particles, //atleast until I get the basic to work. //This part will be removed later, as it is not very good float forceX = 0; float forceY = 0; //Update the particles //-------------------------------------- //VelocityY += ForceY / mass * timestep; //PositionY += VelocityY * timestep; for (int i=0; i
Ian Jones
02 February 2007, 11:20 PM
To get a variable rate of spawning your going to need to have more MAX_PARTICLES in the array but only initialise say 75% of them and not instantly re-initialise them when they die.

I've been doing this kind of thing recently but in c++ so the process is different because I don't have to worry about a fixed number of particles. I create new ones on the heap and add them to a list then call update on the list. It's definitely harder in c to work out these kinds of problems.

I'm not sure why you are getting a square formation, understanding random distributions is a large topic... the problem with rand() is that it is simple and only useful to a limited degree.

http://local.wasp.uwa.edu.au/~pbourke/other/random/

H3ro
03 March 2007, 04:36 PM
To get a variable rate of spawning your going to need to have more MAX_PARTICLES in the array but only initialise say 75% of them and not instantly re-initialise them when they die.

Is there somewhere I can find an example or something of it? I would really like my particle system to be able to do that, but I am kind of lost in how to.

I've been doing this kind of thing recently but in c++ so the process is different because I don't have to worry about a fixed number of particles. I create new ones on the heap and add them to a list then call update on the list. It's definitely harder in c to work out these kinds of problems.

It is written in c++, but the code might not be the best, as I am still learning programing.

mtartist
03 March 2007, 06:01 PM
Hi there! Your code is in C. I suggest using C++ classes to make the system more modular. For example create a class Emitter and another Particles and manage both using a ParticleSystem class. Also to 'make' your code C++ use the C++ headers, for example #include <cmath> instead of #include <math.h>.

Also consider using std::vector so you eliminate the fixed size array problem.

Frankly I cannot make sense of your initial velocity code. My way of doing the same thing is:

// generate a float between 0 and 2pi radians
float angle = rand() / (float)RAND_MAX * (2 * 3.142);
// this will assign a speed of 0.05, you could replace it with whatever value
particle[i].vX = sin(angle) * 0.05;
particle[i].vY = cos(angle) * 0.05;

I tried this and it worked, with the particles forming a circle.

H3ro
03 March 2007, 07:18 PM
Hi there! Your code is in C. I suggest using C++ classes to make the system more modular. For example create a class Emitter and another Particles and manage both using a ParticleSystem class. Also to 'make' your code C++ use the C++ headers, for example #include <cmath> instead of #include <math.h>.

Well, as I said I am learning c++, but as long as it compiles I am happy. But I want to do it right. Is there any problems with using c in c++? My project is set to c++ you see.

Also, I am not planning to make this a big project. I am just doing this to learn and I guess I have learned that doing stuff to advanced sucks, so I am taking a few steps back I think.

Why would it be better to do it as a class?

// generate a float between 0 and 2pi radians
float angle = rand() / (float)RAND_MAX * (2 * 3.142);
// this will assign a speed of 0.05, you could replace it with whatever value
particle[i].vX = sin(angle) * 0.05;
particle[i].vY = cos(angle) * 0.05;

I will try it out when I get home, thanks. I am really unsure about why I get the square shape (or, I have an idea, but dont know how to fix it) I hope this will solve my problem.

mtartist
03 March 2007, 08:47 PM
Mostly C code compiles in a C++ compiler, so although technically you are using C++ (cause C is a "subset" of C++) you are not taking advantage of the language features.

As for the advantage of using classes, it is a case of modularity and OOP. While the benefit of using C++ in this specific case is not so evident, you will soon realize that implementing complicated stuff in C will become more or less a mess. C++ code will enable you to create cleaner code (if done well), by splitting the problem into modules.

Even though as you said this is not a big project, it would be a good idea to use classes just to learn. I don't think it makes sense to learn how to use classes on a large project. Just start slowly and gradually increase the size of your projects.

Good Luck! :thumbsup:

H3ro
03 March 2007, 09:52 PM
mtartist: Thanks for the velocity code, it worked perfect. I think I understand why I got a square before. As I calculated position from velocity in X and Y, where they both was the same, it would be like in a conrdinate system. (1,1),(2,2)(3,3) and this will form a square. Am i right?

As for the advantage of using classes, it is a case of modularity and OOP. While the benefit of using C++ in this specific case is not so evident, you will soon realize that implementing complicated stuff in C will become more or less a mess. C++ code will enable you to create cleaner code (if done well), by splitting the problem into modules.

Think I have to try classes in a simpler project before that. I have no idea what a class is. Or i know its something like a structure which you can define and manipulate when making it or something. Reading time I guess.
I think a class will make some stuff easier, atleast I think it might remove my global variables.

I think I will try to get the rate thing to work now, as well as changing all my X and Y variables into a vector.

I guess there is a big chance of me posting here soon again, right now coding is making more questions than answars.

Ian Jones
03 March 2007, 02:35 AM
Not a problem, continue to post here as this information is useful as an insight into learning the language. I have just recently begun learning c++ myself (4 months) so am well aware of the process involved in understanding all these new concepts.

As mtartist suggests, a class based approach is much more effective for learning more and also is much more scalable and maintainable. To give you a very brief concept of a class:

You are probably already familiar with the concept of encapsulation, for example a struct which is a container which stores variables, it encapsulates them. The biggest leap forward to a c++ class is the possibility of member functions. A struct has member variables so to speak, as in you can encapsulate several variables into a structure. A class does exactly the same thing, encapsulating data in member variables but with one important addition... the ability to also encapsulate functionality on top of data storage. So basically, and in a simplistic explanation, a class is just a struct which can also have built in functions.

In a more complex sense classes have added features compared to structs. A class can have varied levels of data encapsulation (data hiding). So for example you can hide variables and even functions from code outside of the class, but you can also share specific variables and functions to any code outside the class aswell. A third level of encapsulation also allows you to hide variables and functions from code outside of the class but also share these to sub-classes, eg a mammal object may have a sub-class called dog and if it wishes can share a variable called 'age' and a function called 'walk'.

This is probably a bit of information overload atm, but what I can say is that it doesn't take long to understand all these object-oriented aspects of c++. Doing really advanced things is obviously going to be tricky, but mostly object oriented programming in c++ will make your life much easier in several important ways.

It allows for more abstract (more human like, less machine like) representations of programming problems. Examining a problem and visualising a solution is much easier when you have a concept like objects / classes rather than a more linear approach as with plain old c. An object can solve a discrete problem, an there you have it... a nicely encapsulated little bite of a programming problem solved in a modular way.

Due to this modularity it is not only easier to realise solutions to many programming problems but it is inherently clever, because now you have this neatly wrapped way of dealing with a problem that is not heavily intwined in your main function like in plain old c. This means code reusability and the advantage of being able to easily swap in or out the code of that class without disrupting the rest of your code.

A third aspect, by using objects / classes is data integrity. Because you can encapsulate variables and functions inside a class it means you can more safely control what has access to these variables and functions and what doesn't. This improves code integrity and often stability because it helps to protect unwanted changes to data.

If you like I can give you an example of a particle system I have written recently in c++. I'd be happy to explain it line by line if you wish also.

H3ro
03 March 2007, 12:44 PM
Last night I started "updating my particle system. So far I have written a vector2D class.
Classes was easyer to understand than I first thought it would be.

Here is my simple class:
//My super pimp vector class.
class vector2D
{
public:
vector2D()
{
x = 0;
y = 0;
}

vector2D(float a, float b)
{
x = a;
y = b;
}

//set vector
void vector2D::set(float a, float b)
{
x = a;
y = b;
}

//get x
float vector2D::getx()
{
return x;
}

//get y
float vector2D::gety()
{
return y;
}

{
x = a.getx() + b.getx();
y = a.gety() + b.gety();
}

private:
float x,y;

};

Ian Jones:
Thank you for taking your time and explaning it for me. I really appreciate it.

If you like I can give you an example of a particle system I have written recently in c++. I'd be happy to explain it line by line if you wish also.

I would love to see you code, and to discus it with you. That would be perfect.

Ian Jones
03 March 2007, 04:07 PM
Ok... took me a little while cos I'm not familiar with windows programming, but here's an example. All files are included. I have made no effort to make the particles do anything interesting and they also display that square effect you were talking about. Keep in mind I'm a c++ newbie, so this may not be a perfect implementation.

Ian Jones
03 March 2007, 04:11 PM
ps. Ignore the theta variable, I used a Dev-c++ template and it's not the most tidy example.

H3ro
03 March 2007, 07:05 PM
Ok... took me a little while cos I'm not familiar with windows programming, but here's an example. All files are included. I have made no effort to make the particles do anything interesting and they also display that square effect you were talking about. Keep in mind I'm a c++ newbie, so this may not be a perfect implementation.

Thank you so much for taking time to do this for me. Its looks really nice. I think this would be a good example for all beginners, so maybe you should post it somewhere else where more people can see it? All other particle examples I have seen have been much more complex and harder to understand. Thank you again.

Ian Jones
03 March 2007, 01:58 AM
Hi, glad you like the example. I'm intending on creating tutorials in the future. For now, I don't believe I have the whole picture yet... so I think creating tutorials after 4 months experience is not a great idea. For example, I have used a vector... which is very useful but not as fast as an array.

H3ro
03 March 2007, 01:45 PM
I have now had time to look at you code and to modefy my own, but I have a few questions. They questions are inside the code, as I think it is easyer to understand what I am asking if you can see the code at the same time.

Thanks for all help so far, one day I hope to return the favor.

// Create and update the particles
// Should not a lot of this be done inside of the particle class?
// Maybe have a control class over the particle class?
// Is it even possible to have a class in a class?
//-------------------------
// Initialise random seed
// Is time better than a constant?
srand((unsigned)time(NULL));

// Initialise Vector to store particles
// Why pointer?
// What is the difference between an iterator and a int?
std::vector<C_Particle *> particles; // A vector of Particle pointers called particles
std::vector<C_Particle *>::iterator iter; // An iterator is a special object used to traverse the vector

//program main loop
while (!bQuit)
{
//check for messages
if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
{
// handle or dispatch messages
if (msg.message == WM_QUIT)
{
bQuit = TRUE;
}
else
{
// What happens here?
TranslateMessage (&msg);
DispatchMessage (&msg);
}
}
else
{
// OpenGL animation code goes here
// Is theresomewhere else I can add this?
// Dont like to have it messed up inside the particle stuff
// Maybe in the render function? I tryed, but could not get it to work
glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
glClear (GL_COLOR_BUFFER_BIT);

// Add a new Particle every loop, but cap to a maximum
// Nice way of doing rate
for(int i = 0; i < 50; i++)
{
if(particles.size() < 1000)
{
//Why pointer?
C_Particle *p = new C_Particle();
particles.push_back(p);
}
}

// Update
if(particles.begin() != particles.end()) // makes sure that there is at least 1 thing in the vector
{
for(iter = particles.begin(); iter != particles.end();)
{
//What does this line do? ->, never seen it before
//Also, whay is it the iterator that is used and not the particle?
if((*iter)->isAlive() == 0)
{
// particle is dead so remove it, note that we do not increment the iterator because when we use erase(),
// the next position in the vector is now the iterator so no need to increment
// why not destruct the particle?
delete (*iter);
*iter = NULL;
particles.erase(iter);
}
else

{
// particle is alive, update() it then increment the vector
(*iter)->update();
iter++;
}
}
}

// Render
if(particles.begin() != particles.end()) // makes sure that there is at least 1 thing in the vector
{
for(iter = particles.begin(); iter != particles.end(); iter++)
{
// Why is the iterator sendt to the render and not the particle?
(*iter)->render();
}
}

// OpenGl stuff, openGL draw one frame while the other is displayed
// than they are swapped
SwapBuffers (hDC);

// Should be change into a timestep function, to control framerate?
Sleep (1);
}
}

Ian Jones
03 March 2007, 04:43 PM
Ok, I have attempted to answer all your questions. I have added comments with a *** prefix so you can clearly see my answers. You have many questions, which is fine, but I'd suggest reading more c++ tutorials to understand these concepts more completely before asking any more questions here. I say this because my explanations are only as good as my limited experience with the language.

// Create and update the particles
// Should not a lot of this be done inside of the particle class?
//***Not sure what you mean there...?
// Maybe have a control class over the particle class?
//***Yes a particle system manager would be a good way to work in the long run
// Is it even possible to have a class in a class?
//***Yes it is possible to have a class in a class, this is called 'composition', a closely related concept is called 'inheritance'... read up about both of these concepts.
//-------------------------
// Initialise random seed
// Is time better than a constant?
//***A constant would be fine here, but basing a random seed off time() means that the particle system will look different every launch.
srand((unsigned)time(NULL));

// Initialise Vector to store particles
// Why pointer?
//***Because we will be creating objects (particles) on the heap. Passing a whole object to store in the vector will create a duplicate object. A pointer is faster to use with a vector and refers to the orginally created object on the heap. Read up about 'pass by value' and 'pass by reference'.
// What is the difference between an iterator and a int?
//***Iterators are used to access members of a container class (like this vector), and can be used in a similar manner to pointers. I do not havea complete answer for you here as I don't fully understand them, again I suggest reading up about them.
std::vector<C_Particle *> particles; // A vector of Particle pointers called particles
std::vector<C_Particle *>::iterator iter; // An iterator is a special object used to traverse the vector

//program main loop
while (!bQuit)
{
//check for messages
if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
{
// handle or dispatch messages
if (msg.message == WM_QUIT)
{
bQuit = TRUE;
}
else
{
// What happens here?
//***I have absolutely no idea, this was generated by my Dev-C++ template file, and I am not familiar with Windows event message handling.
TranslateMessage (&msg);
DispatchMessage (&msg);
}
}
else
{
// OpenGL animation code goes here
// Is theresomewhere else I can add this?
//*** this section is the main loop, so I just put the particle independant OpenGL code here before any object (even non-particles) were to ever get rendered. I put the new particle generator here aswell, but there is no reason why you couldn't create a particle manager (particle system) class which handles particle spawning / creation.
// Dont like to have it messed up inside the particle stuff
// Maybe in the render function? I tryed, but could not get it to work
glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
glClear (GL_COLOR_BUFFER_BIT);

// Add a new Particle every loop, but cap to a maximum
// Nice way of doing rate
for(int i = 0; i < 50; i++)
{
if(particles.size() < 1000)
{
//Why pointer?
//***Again this relates to what the vector used to iterate over the particles expects. It expects a particle pointer, remember the line above 'std::vector<Particle *> particles'. So of course we should 'push_back(p)' a particle pointer.
C_Particle *p = new C_Particle();
particles.push_back(p);
}
}

// Update
if(particles.begin() != particles.end()) // makes sure that there is at least 1 thing in the vector
{
for(iter = particles.begin(); iter != particles.end();)
{
//What does this line do? ->, never seen it before
//***This symbol '->' is used to access a member (variable or function) of a pointer. If a variable is not a pointer you can use 'object.someFunction()' but if it is a pointer you have to use 'object->someFunction()'.
//Also, whay is it the iterator that is used and not the particle?
//***I don't have the clearest understanding of this syntax myself actually, but basically the iterator returns a pointer to an object. In this case we have an extra piece of complexity, we also need to dereference it by putting a '*' infront because carzily enough we actually have returning from the iterator a "pointer to an object pointer". The brackets surrounding '*iter' also seem to be neccessary, I don't know why but I suspect it is like typecasting?
if((*iter)->isAlive() == 0)
{
// particle is dead so remove it, note that we do not increment the iterator because when we use erase(),
// the next position in the vector is now the iterator so no need to increment
// why not destruct the particle?
//***When the particle is deleted, the destructor is actually called by default anyway, so there is no need to call the destructor explicitly.
delete (*iter);
*iter = NULL;
particles.erase(iter);
}
else

{
// particle is alive, update() it then increment the vector
(*iter)->update();
iter++;
}
}
}

// Render
if(particles.begin() != particles.end()) // makes sure that there is at least 1 thing in the vector
{
for(iter = particles.begin(); iter != particles.end(); iter++)
{
// Why is the iterator sendt to the render and not the particle?
//**Read what I wrote about this above. Basically we are actually referencing to the particle itself, because the iterator returns to us a pointer to an object pointer.
(*iter)->render();
}
}

// OpenGl stuff, openGL draw one frame while the other is displayed
// than they are swapped
SwapBuffers (hDC);

// Should be change into a timestep function, to control framerate?
//***Yes that would be a good solution, this line theoretically allows the process to sleep and allows other processes on the computer to get a go at the cpu time. It's actually more complex than that, but that's a simple explanation. Ultimately a better implementation is to not restrict the framerate and to control animaton with time elapsed rather than frame by frame as we have here. Read up about frame vs time-based animation in programming.
Sleep (1);
}
}

H3ro
03 March 2007, 10:23 PM
A big thanks to all of you who helped me :)

#include <vector.h>

// Holeds the information of one particle
class C_Particle
{
public:
C_Particle(); // Initialise the particles
~C_Particle(); // Kill the particle system

void update(); // Update the system
void render(); // Render the particle system
bool isAlive(); // Check if a particle is alive

private:
float posX;
float posY;

float velX;
float velY;

float speed;
float mass;
float inv_mass;

float lifeSpan;
float age;

bool alive;

// This is to simulate exsternal forces
// acting on the system
float forceY;
float forceX;
};

// This class is used to control the particle sytem
// Using a manager class makes the whole thing look a
// lot cleaner in the main part of the progrem
class C_ParticleManager
{
public:
// Createt particle
void create();
// Maybe have some predefined effect?
// so they can easily be created

// Update particle system
void update();

// Render the system
void render();

// Destroy particle
void destroy();

private:
std::vector<C_Particle *> particles;
std::vector<C_Particle *>::iterator iter;
};

// In the main part of the program C_ParticleManager.create
// is called to create the system
// Then C_ParticleManager.update is called to update it
// when all is done, C_ParticleManager.destroy will kill the
// system

The cpp file:
#include "C_Particle.h"
#include <gl/gl.h>
#include <math.h>
#include <stdlib.h>

//---------------------------
// A random number function:
// I like this one, as it is much simpler and cleaner than
// to use the whole thing everytime you want a random number
// between two floats.
double randNo(float min, float max)
{
double r = static_cast<double>(rand()) / static_cast<double>(RAND_MAX);
return r * ( max - min ) + min ;
}

// **********************************************************************
// * C_Particle class:
// * Used to create and control a single particle
// **********************************************************************

C_Particle::C_Particle()
{
// This class should probably take a lot of arguments
// so it would be easier to create different effects.
// ---------------------------------------------------

//Start position of the paricle
posX = 0;
posY = 0;

// Sett the speed / inital velocity of the particle
speed = 0.001;

// This does not work very well right now!
// -----------------------------------------------
// Set the initial velocity of the particle:
// This control how much the particles spread out.
// Trying to find a way to control which way the particles
// are moving.
// E.g: how to make them moving in posetive X with a spread
// of 90 degrees?
// 1 radian = 57.2957795 degree

float angle = 360;
float realAngle = ((rand() / (float)RAND_MAX ) * (angle/radian));

// The last number is the speed of the particle.
velX = (sin(realAngle) * speed);
velY = (cos(realAngle) * speed);
// -----------------------------------------------

// Here is a simpler way of doing inital velocity
// it does not look so nice (square like shape), but is easy to
// control
// Set initial velocity
//velX = randNo (-0.001, 0.001);
//velY = randNo (-0.001, 0.001);

// Set the force acting on the system
// Here for example gravity could be added
forceX = 0;
forceY = 0;

mass = 1;
inv_mass = 1 / mass; // Make it easier to create a static particle
// as if inv_mass is 0, than velocity would be 0
// Velocity = (force / inv_mass)

// The system used milliseconds (sleep(1))
lifeSpan = 500;
age = 0;

alive = 1;
}

C_Particle::~C_Particle()
{

}

void C_Particle::update()
{
//Update velocity
velX += (forceX / inv_mass);
velY += (forceY / inv_mass);

//Update position
posX += velX; // * timeStep <- need to be created and replace sleep(1) which can be found in main()
posY += velY; // * timeStep

age += 1;
}

void C_Particle::render()
{
glPushMatrix();
glTranslatef(posX, posY, 0.0);
glBegin(GL_POINTS);
glVertex3f(0.0, 0.0, 0.0);
glEnd();
glPopMatrix();
}

bool C_Particle::isAlive()
{

if (age <= lifeSpan)
return 1;
else
return 0;
}

// **********************************************************************
// * C_ParticleManager class:
// * This is the class that is used to manage a
// * particle sytem.
// **********************************************************************

void C_ParticleManager::create()
{
// Create particles and add them to the vector
for(int i = 0; i < 50; i++)
{
if(particles.size() < 1000)
{
C_Particle *p = new C_Particle();
particles.push_back(p);
}
}
}

void C_ParticleManager::update()
{
if(particles.empty() == 0)
{
for(iter = particles.begin(); iter != particles.end();)
{
if((*iter)->isAlive() == 0) // The particle is dead, remove it
{
delete (*iter);
*iter = NULL;
particles.erase(iter);
}
else
{
// particle is alive, update() it then increment the vector
(*iter)->update();
iter++;
}
}
}
}

void C_ParticleManager::render()
{
if(particles.empty() == 0)
{
for(iter = particles.begin(); iter != particles.end(); iter++)
{
(*iter)->render();
}
}
}

void C_ParticleManager::destroy()
{
// destroy the whole system
}

Let me know what you guys think

CGTalk Moderation
03 March 2007, 10:23 PM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.

1