In this recipe, we are going to learn how we can build a basic particle system in two dimensions using the Verlet algorithm.
We will need to create two classes, a Particle
class representing a single particle, and a ParticleSystem
class to manage our particles.
Using your IDE of choice, create the following files:
Particle.h
Particle.cpp
ParticleSystem.h
ParticleSystem.cpp
We will learn how we can create a basic particle system. Perform the following steps to do so:
Particle
class in the Particle.h
file and include the necessary Cinder files:#pragma once #include "cinder/gl/gl.h" #include "cinder/Vector.h" class Particle{ };
ci::Vec2f
to store the position, previous position, and applied forces; and float
to store particle radius, mass, and drag.ci::Vec2f position, prevPosition; ci::Vec2f forces; float radius; float mass; float drag;
Particle
declaration is to add a constructor that takes the particle's initial position, radius, mass, and drag, and methods to update and draw the particle.The following is the final Particle
class declaration:
class Particle{ public: Particle( const ci::Vec2f& position, float radius, float mass, float drag ); void update(); void draw(); ci::Vec2f position, prevPosition; ci::Vec2f forces; float radius; float mass; float drag; };
Particle.cpp
file and implement the Particle
class.The first necessary step is to include the Particle.h
file, as follows:
#include "Particle.h"
forces
to zero
and prevPosition
to the initial position.Particle::Particle( const ci::Vec2f& position, float radius, float mass, float drag ){ this->position = position; this->radius = radius; this->mass = mass; this->drag = drag; prevPosition = position; forces = ci::Vec2f::zero(); }
update
method, we need to create a temporary ci::Vec2f
variable to store the particle's position before it is updated.ci::Vec2f temp = position;
drag
. We store this value in ci::Vec2f
temporarily for clarity.ci::Vec2f vel = ( position – prevPosition ) * drag;
forces
divided by mass
.position += vel + forces / mass;
update
method are to copy the previously stored position to prevPosition
and reset forces
to a zero
vector.The following is the complete update
method implementation:
void Particle::update(){ ci::Vec2f temp = position; ci::Vec2f vel = ( position - prevPosition ) * drag; position += vel + forces / mass; prevPosition = temp; forces = ci::Vec2f::zero(); }
draw
implementation, we simply draw a circle at the particle's position using its radius.void Particle::draw(){ ci::gl::drawSolidCircle( position, radius ); }
Particle
class complete, we need to begin working on the ParticleSystem
class. Move to the ParticleSystem.h
file, include the necessary files, and create the ParticleSystem
class declaration.#pragma once #include "Particle.h" #include <vector> classParticleSystem{ public: };
std::vector
variable to store the particles in this system. The following is the final class declaration:Class ParticleSystem{ public: ~ParticleSystem(); void update(); void draw(); void addParticle( Particle *particle ); void destroyParticle( Particle *particle ); std::vector<Particle*> particles; };
ParticleSystem.cpp
file, let's begin working on the implementation. The first thing we need to do is include the file with the class declaration. #include "ParticleSystem.h"
ParticleSystem::~ParticleSystem(){ for( std::vector<Particle*>::iterator it = particles.begin(); it!= particles.end(); ++it ){ delete *it; } particles.clear(); }
update
method will be used to iterate all the particles and call update
on each of them.void ParticleSystem::update(){ for( std::vector<Particle*>::iterator it = particles.begin(); it != particles.end(); ++it ){ (*it)->update(); } }
draw
method will iterate all the particles and call draw
on each of them.void ParticleSystem::draw(){ for( std::vector<Particle*>::iterator it = particles.begin(); it != particles.end(); ++it ){ (*it)->draw(); } }
addParticle
method will insert the particle on the particles
container.void ParticleSystem::addParticle( Particle *particle ){ particles.push_back( particle ); }
destroyParticle
will delete the particle and remove it from the particles' list.We'll find the particles' iterator and use it to delete and later remove the object from the container.
void ParticleSystem::destroyParticle( Particle *particle ){ std::vector<Particle*>::iterator it = std::find( particles.begin(), particles.end(), particle ); delete *it; particles.erase( it ); }
In our application's class, we need to include the ParticleSystem
header file and the necessary header to use random numbers at the top of the source file:
#include "ParticleSystem.h" #include "cinder/Rand.h"
ParticleSystem
object on our class declaration.ParticleSystem mParticleSystem;
setup
method we can create 100 particles with random positions on our window and random radius. We'll define the mass to be the same as the radius as a way to have a relation between size and mass. drag
will be set to 9.5.Add the following code snippet inside the setup method:
int numParticle = 100; for( int i=0; i<numParticle; i++ ){ float x = ci::randFloat( 0.0f, getWindowWidth() ); float y = ci::randFloat( 0.0f, getWindowHeight() ); float radius = ci::randFloat( 5.0f, 15.0f ); float mass = radius;radius; float drag = 0.95f; Particle *particle = new Particle ( Vec2f( x, y ), radius, mass, drag ); mParticleSystem.addParticle( particle ); }
update
method, we need to update the particles by calling the update
method on mParticleSystem
.void MyApp::update(){ mParticleSystem.update(); }
draw
method we need to clear the screen, set up the window's matrices, and call the draw
method on mParticleSystem
.void ParticlesApp::draw() { gl::clear( Color( 0, 0, 0 ) ); gl::setMatricesWindow( getWindowWidth(), getWindowHeight() ); mParticleSystem.draw(); }
In the next recipes we will learn how to animate the particles in organic and appealing ways.
The method described previously uses a popular and versatile Verlet integrator. One of its main characteristics is an implicit approximation of velocity. This is accomplished by calculating, on each update of the simulation, the distance traveled since the last update of the simulation. This allows for greater stability as velocity is implicit to position and there is less chance these will ever get out of sync.
The drag
member variable represents resistance to movement and should be a number between 0.0 and 1.0. A value of 0.0 represents such a great resistance that the particle will not be able to move. A value of 1.0 represents absence of resistance and will make the particle move indefinitely. We applied drag
in step 7, where we multiplied drag
by the velocity:
ci::Vec2f vel = ( position – prevPosition ) * drag;
To create a particle system in 3D it is necessary to use a 3D vector instead of a 2D one.
Since Cinder's vector 2D and 3D vector classes have a very similar class structure, we simply need to change position
, prevPosition
, and forces
to be ci::Vec3f
objects.
The constructor will also need to take a ci::Vec3f
object as an argument instead.
The following is the class declaration with these changes:
class Particle{ public: Particle( const ci::Vec3f& position, float radius, float mass, float drag ); void update(); void draw(); ci::Vec3f position, prevPosition; ci::Vec3f forces; float radius; float mass; float drag; };
The draw
method should also be changed to allow for 3D drawing; we could, for example, draw a sphere instead of a circle:
void Particle::draw(){ ci::gl::drawSphere( position, radius ); }
3.15.223.160