: Documentation : Multimedia Tutorials

Introduction

Particle systems are a computer graphics technique that can be used to simulate phenomena such as fire, smoke, water, rain, dust, trails and so on. Open Scene Graph has built in support for particle systems, provided by classes in the osgParticle namespace.

This tutorial demonstrates the basic steps necessary to include one such particle system in an osgART application. We will add the particle system in the form of smoke emission from an animated model of a rocket.

OSG Viewer & Scene Setup

We will need a bunch of includes for this project, so add the following header files:

#include <osgParticle/PointPlacer>
#include <osgParticle/Particle>
#include <osgParticle/ParticleSystem>
#include <osgParticle/ParticleSystemUpdater>
#include <osgParticle/ModularEmitter>
#include <osgParticle/RandomRateCounter>
#include <osgParticle/ModularProgram>
#include <osgParticle/FluidFrictionOperator>
#include <osgParticle/AccelOperator>

(The project will also need to be linked with the osgParticle library, so don’t forget to adjust your project file).

Using the same process as in previous tutorials, we set up a scene tracking one marker.

osg::MatrixTransform* mt = scene->addTrackedTransform("single;data/patt.hiro;80;0;0");

We load a model of a rocket and apply a simple rotation to it using an osg::AnimationPathCallback, as in the Rigid Body Animation tutorial.

 /** ... Rotation animation ... **/
osg::ref_ptr<osg::MatrixTransform> MTrocketA = new osg::MatrixTransform();
mt->addChild(MTrocketA.get());
MTrocketA->addUpdateCallback(new osg::AnimationPathCallback(osg::Vec3(0.0,0.0,100.0),
                        osg::Y_AXIS, osg::inDegrees(45.0f)));

/** ... Intitial translation to orient rocket ... **/
osg::ref_ptr<osg::MatrixTransform> MTlocalrocketA =
new osg::MatrixTransform(osg::Matrixd::rotate(osg::DegreesToRadians(90.0f),
                                                      osg::Z_AXIS));
MTlocalrocketA->addChild(osgDB::readNodeFile("Tutorials-Assets/rocket.osg"));
MTrocketA->addChild(MTlocalrocketA.get());

Adding the particle system

Now we add a smoke trail to the rocket, in the form of a customized particle system. This particle system is defined in a separate class, SmokeParticleSystem, the specifics of which are detailed here. The SmokeParticleSystem constructor takes as one of its parameters, a pointer to a MatrixTransform node - here we provide the rocket’s parent transform node, to which the animation is being applied. The SmokeParticleSystem object will use this transform node to determine the point where the smoke originates according to the position of the rocket, as the rocket rotates. This will make it look as if the smoke is being emitted from the rocket. This is the same technique we would use if we wanted the same effect in an ordinary (non-AR) OSG application.

In such an application, once the smoke has been ‘emitted’ from the rocket, it really belongs in the ‘world’, not to the rocket. In an osgART application, we would also expect this same effect, except that the ‘world’ in this case is the marker space. That is, if we move the marker the smoke and rocket should also move according to the position of that marker. Therefore we add the smoke node into the scene graph as a child of the marker’s transform node.

osg::ref_ptr<SmokeParticleSystem> smoke = new SmokeParticleSystem(scene, MTrocketA.get());
mt->addChild(smoke);

Finally…

As before, we add the scene data to the Viewer, and set up the viewer loop. The code has been omitted for brevity.

SmokeParticleSystem

As mentioned previously in order to implement a customized particle system which emulates smoke, we define a class SmokeParticleSystem, as a specialised type of osg::Group node.

To create a basic particle system, there are a number of necessary steps. For a more customized particle system, there are additional optional steps which can be taken. The OSG documentation also provides some detail about particle sytems.

Here we demonstrate the basic steps, plus some of the optional extras. Our SmokeParticleSystem constructor takes two parameters. The first will be a pointer to the osgART::Scene node. The second will be the parent node of the 3D rocket model, to which the rotation animation is being applied.

class SmokeParticleSystem : public osg::Group {
    private:
        osgParticle::PointPlacer* placer;
    public:
               SmokeParticleSystem(osg::Group* scene, osg::MatrixTransform* mt)  {
                        /* Build the particle system in here ... */
               }
};

perform rigid body animation on an object within an osgART scene. Within the constructor the steps are carried out as follows.

Create an OSG ParticleSystem and add to SmokeParticleSystem (necessary)

The osgParticle::ParticleSystem class is central to the Particle library. Its purpose is to hold a set of particles and manage particle creation, update, rendering and destruction. Each instance of ParticleSystem is a separate set of particles; it provides the interface for creating particles and iterating through them. Because the ParticleSystem is an osg::Drawable, to add it to the scene we must first add it to a Geode, and add the Geode node into the scene graph.

osgParticle::ParticleSystem* dustParticleSystem = new osgParticle::ParticleSystem;
osg::Geode *geode = new osg::Geode();
geode->addDrawable(dustParticleSystem);
this->addChild(geode);

Set ParticleSystem’s state attributes (optional)

the setDefaultAttributes method allows us to set three common state attributes of an osg::ParticleSystem - texture, additive blending and lighting. In this example we are using a texture loaded from file (a null string disables texturing), and disabling both additive blending and lighting.

dustParticleSystem->setDefaultAttributes("Tutorials-Assets/dust.png", false, false);

Define the Particle template (necessary)

An osgParticle::Particle provides a template for the particles which make up the osgParticle::ParticleSystem. We need to set the properties for a single osgParticle::Particle only. Important properties we can define are size, lifetime, mass, alpha transparency, radius and color. All properties can be expressed as a single value, or a range. If a range is specified, each Particle’s properties, with the exception of color, will vary randomly within the given range. The color of each Particle will start its life with the minimum color value specified and gradually increase to the maximum color value at the end of its lifetime.

osgParticle::Particle smokeParticle;
smokeParticle.setSizeRange(osgParticle::rangef(4.0f,20.0f)); // meters
smokeParticle.setLifeTime(15); // seconds
smokeParticle.setMass(0.4f);
smokeParticle.setColorRange(osgParticle::rangev4(
osg::Vec4(1, 1.0f, 1.0f, 1.0f),
osg::Vec4(0, 0, 0, 0.3f)));
dustParticleSystem->setDefaultParticleTemplate(smokeParticle);

Add a ParticleSystemUpdater to the ParticleSystem (necessary)

osgParticle::ParticleSystemUpdater *dustSystemUpdater = new osgParticle::ParticleSystemUpdater;
dustSystemUpdater->addParticleSystem(dustParticleSystem);
this->addChild(dustSystemUpdater);

Add a ModularEmitter (optional)

An osgParticle::ModularEmitter is the class that controls the creation and emission of particles. Here we create an instance of this class, and add it to our particle system.

osgParticle::ModularEmitter *emitter = new osgParticle::ModularEmitter;
emitter->setParticleSystem(dustParticleSystem);
this->addChild(emitter);

Upon instantiation, the ModularEmitter object constructs three objects - a counter, placer and shooter. We can use the default properties of these objects, or, for a more customized particle system, we can extract them and modify their properties.

  • Customize the ModularEmitter’s Counter (optional)

A Counter allows us to control how many new particles are created each frame. There are three types of Counter - osgParticle::ConstantRateCounter, osgParticle::VariableRateCounter and osgParticle::RandomRateCounter. Here we will use the RandomRateCounter, which will generate a random number of particles, within the minimum-maximum range we specify (40-80 in this case).

osgParticle::RandomRateCounter *dustRate =
                static_cast<osgParticle::RandomRateCounter *>(emitter->getCounter());
dustRate->setRateRange(40,80);
  • Customize the ModularEmitter’s Placer (optional)

A Placer allows us to position the particles. Here we will use anosgParticle::PointPlacer, which enables us to position all particles at a single point. The other types of Placer are osgParticle::BoxPlacer, osgParticle::SectorPlacer, osgParticle::SegmentPlacer and osgParticle::MultiSegmentPlacer.

The setCenter method of the PointPlacer defines the particle placer position where the particles will be placed. In this example, we want it to look as if the smoke is being emitted from the 3D rocket model, which is undergoing a rotation animation. We therefore need to be able to continuously update the particle placer position to match that of the rocket. To do this, we define an update callback, EmitterUpdateCallback, that will call the setCenter method every update traversal, and update the particle placer position according to the rocket position. We add this new update callback to the SmokeParticleSystem’s list of callbacks.

osgParticle::PointPlacer* placer =
                static_cast<osgParticle::PointPlacer *>(emitter->getPlacer());
this->setUpdateCallback(new EmitterUpdateCallback(placer, mt));

The EmitterUpdateCallback class is as follows:

class EmitterUpdateCallback : public osg::NodeCallback {

    private:

    osgParticle::PointPlacer* placer;
    osg::MatrixTransform* trans;

    public:

    EmitterUpdateCallback(osgParticle::PointPlacer* p, osg::MatrixTransform* mt) {
         placer = p;
        trans = mt;
    }

    void operator() (osg::Node* node, osg::NodeVisitor* nv) {
        placer->setCenter(trans->getMatrix().getTrans());
        traverse(node,nv);
    }
};
  • Customize the ModularEmitter’s Shooter (optional):

An osgParticle::RadialShooter allows us to assign a direction and an initial velocity to particles, defining how they ‘shoot’ once placed.

The direction is defined by two angles: theta, which is the angle between the velocity vector and the Z axis, and phi, which is the angle between the X axis and the velocity vector projected onto the X-Y plane. Each of these angles is expressed as a range, and a particle’s direction is computed using random values within this range.

Likewise, a particle’s initial velocity will be a random value from within the specified range.

osgParticle::RadialShooter* shooter =
                static_cast<osgParticle::RadialShooter *>(emitter->getShooter());
shooter->setThetaRange(0, 3.15149/4.0f ); // radians, relative to Z axis.
shooter->setInitialSpeedRange(4,5); // meters/second
emitter->setShooter(shooter);

Add ModularProgram (optional)

Finally, the osgParticle::ModularProgram is used to perform additional operations on particles. To use the ModularProgram, we must create one or more osgParticle::Operator object/s and add them to the ModularProgram. The Operators will be applied to the particles in the order they are added.

The four types of Operator are osgParticle::AccelOperator, osgParticle::AngularAccelOperator, osgParticle::FluidFrictionOperator and osgParticle::ForceOperator. In this example, we have applied a FluidFrictionOperator and an AccelOperator to our particles.

The FluidFrictionOperator simulates the friction of a fluid, allowing us to specify the density and viscosity of the fluid. There are two convenience methods which enable quick setup of the parameters for pure water and air. We have made use of one of these - the setFluidToAir() method, and also set the wind vector. The forces will be computed using the particle’s physical radius, however we can additionally use the setOverrideRadius() method to compute the forces using some other radius.

The AccelOperator applies a constant acceleration to the particles. Here we set the acceleration vector to 0.4 times the gravity on earth.

osgParticle::ModularProgram *program = new osgParticle::ModularProgram;
program->setParticleSystem(dustParticleSystem);
scene->addChild(program);

osgParticle::FluidFrictionOperator *airFriction = new osgParticle::FluidFrictionOperator;
airFriction->setFluidToAir();
airFriction->setWind(osg::Vec3(0.25, 0, 0));
program->addOperator(airFriction);

osgParticle::AccelOperator *accelUp = new osgParticle::AccelOperator;
accelUp->setToGravity(0.4);
program->addOperator(accelUp);

|ParticleEffectsRocketScreenshot.png