Creating particle systems

In this tutorial we will get the very basics of the particle systems. The code in this tutorial is based on the code from tutorial 1.5 so I recommend reading it before going any further.

Particle systems are important part of modern games. All the explosions, flares, rain & snow, dust and so on are done using them. Particle systems have only one aim - to simplify complex things which particles are. In reality a cloud is built-up of billions of water particles (drops to be precise) but using particle systems you can reduce this number significantly - to just a few large particles.

In nGENE Tech there are two concepts:

  • particle emitters,
  • particle systems

A particle emitter is an object emitting only particles of one kind (eg. fire) whereas a particle system is a scene node - a collection of particle emitter objects. Thus you can achieve more complex effects using it - fire & smoke, explosions and so on.

In nGENE Tech particle systems are so called soft particles. Therefore it is required that information about depth is available for all pixels. Depth means presence of geometry so we are going to create a box surrounding whole scene and flip its winding order so we can actually see it (if culling is enabled we will see nothing):

pBox = sm->createBox(100.0f, 100.0f, 100.0f, Vector2(80.0f, 80.0f));
pBox->setPosition(0.0f, 0.0f, 0.0f);
pSurface = pBox->getSurface(L"Base");
Material* matGround = MaterialManager::getSingleton().getLibrary(L"default")->getMaterial(L"normalMap");
pSurface->setMaterial(matGround);
pSurface->flipWindingOrder();
sm->getRootNode()->addChild(L"Helper", pBox);

Nothing new here so let's move further and define particle emitter to use as smoke:

ParticleEmitter smoke;
smoke.setParticlesMaxCount(800);
smoke.setSpawnRate(24);
smoke.setPosition(Vector3(0.5f, 2.0f, 0.0f));
smoke.setVelocity(Vector3(0.075f, 0.15f, 0.075f));
smoke.setVelocitySpread(Vector3(0.075f, 0.5f, 0.075f));
smoke.setAccelerationSpread(Vector3(0.0f, 0.0f, 0.2f));
smoke.setAcceleration(Vector3(-0.2f, -0.05f, 0.0f));
smoke.setSize(0.075f);
smoke.setSizeSpread(0.025f);
smoke.setGrowth(0.45f);
smoke.setGrowthSpread(0.2f);
smoke.setLifeTime(10000.0f);
smoke.setLifeTimeSpread(0.0f);
smoke.setFadeSpeed(1.5f);
smoke.setAngularVelocity(Maths::PI / 18.0f);
smoke.setAngularVelocitySpread(0.02f);
 
Colour target = Colour::COLOUR_WHITE;
target.setAlpha(1);
smoke.setColour(target);
target.setAlpha(100);
smoke.addParticleColour(target, 0.4f);
target.setAlpha(150);
smoke.addParticleColour(target, 0.7f);
 
target = Colour::COLOUR_WHITE;
target.setRGBA(100, 100, 100);
target.setAlpha(0);
smoke.setTargetColour(target);

Wow! Quite a lot of code to define simple smoke. Fortunately in many cases the majority of attributes can be left to their default values.

First we specify that our particle emitter will emit 800 particles at most. Of course there will be moments when the number of visible particles will be less (eg. just after application start). Next we specify that as much as 24 particles/second can be emitted. The rest of the attributes names should be self-explanatory. Notice that …spread() methods are used to specify deviation from average value. For instance calling this:

smoke.setSize(0.075f);
smoke.setSizeSpread(0.025f);

results in that particle size is in range <0.075 - 0.025; 0.075 + 0.025> = <0.05; 0.1> and that final values is chosen randomly!

One more thing to notice is colour. It would be very boring if particles couldn't change their colour during animation, so in nGENE they can. To specify colour use objects of Colour class and add them to the particle emitter using following call:

smoke.addParticleColour(colour, level);

where:
  • colour - colour you want,
  • level - at which level (0.0 - 1.0) of particle life, particle should fade to this colour.

Also note that call to smoke.setColour(colour) is equivalent to smoke.addParticleColour(colour, 0.0f) and smoke.setTargetColour(colour) is equivalent to smoke.addParticleColour(colour, 1.0f);

OK, so now let's add this emitter to the scene by adding it to the particle system object:

NodeParticleSystem* pPS = sm->createParticleSystem();
pPS->setPosition(1.0f, 0.0f, 8.0f);
pPS->addParticleEmitter(smoke, L"smoke_emitter");
sm->getRootNode()->addChild(L"Smoke", pPS);
Material* matSmoke = MaterialManager::getSingleton().getLibrary(L"default")->getMaterial(L"smoke");
pSurface = pPS->getSurface(L"smoke_emitter");
pSurface->setMaterial(matSmoke);

As NodeParticleSystem is derived from NodeVisible class, above code should be self-explanatory at this level.

And that's all. Well almost. Now we have working application but it's pretty boring. So we're going to add fire-balls which are shot when user presses left mouse button. Go to MyInputListener.cpp and add this code in handleEvent(const MouseEvent& _evt) method:

// Shoot the fireball
if(_evt.type == MET_BUTTON_LEFT && _evt.nvalue & 0x80)
{
    PhysicsWorld* pWorld = Physics::getSingleton().getWorld(L"MyPhysicsWorld");
 
    wostringstream buffer;
    buffer << L"Sphere_" << m_BallsNum;
    SceneManager* sm = Engine::getSingleton().getSceneManager(0);
    PrefabSphere* pSphere = sm->createSphere(10, 10, 0.5f);
    Camera* pCam = Engine::getSingleton().getActiveCamera();
    Vector3 pos = pCam->getPositionLocal() + pCam->getForward() * 5.0f;
    pSphere->setPosition(pos);
    Surface* pSurface = pSphere->getSurface(L"Base");
    sm->getRootNode()->addChild(buffer.str(), pSphere);
 
    RigidBody* pActor = pWorld->createRigidBody(L"Sphere", Vector3(0.5, 0.0f, 0.0f), RigidBody::BT_DYNAMIC);
    pActor->attachNode(pSphere);
    pActor->setMass(0.1f);
    pActor->setShapeMaterial(0, *pWorld->getPhysicsMaterial(L"DefaultMaterial"));
    pWorld->addRigidBody(buffer.str(), pActor);
    FORCE_DESC force = {pCam->getForward(), 100.0f};
    pActor->applyForce(force);
 
    // Create particle system
    ParticleEmitter fire;
    fire.setParticlesMaxCount(2400);
    fire.setSpawnRate(120);
    fire.setPosition(Vector3(0.0f, 0.0f, 0.0f));
    fire.setVelocity(Vector3(0.6f, 0.0f, 0.0f));
    fire.setVelocitySpread(Vector3(0.6f, 0.8f, 0.3f));
    fire.setAcceleration(Vector3(0.0f, 1.2f, 0.0f));
    fire.setAngularVelocity(Maths::PI / 3.0f);
    fire.setAngularVelocitySpread(0.2f);
    fire.setSize(1.2f);
    fire.setSizeSpread(0.4f);
    fire.setGrowth(0.24f);
    fire.setGrowthSpread(0.0f);
    fire.setLifeTime(2000.0f);
    fire.setLifeTimeSpread(1000.0f);
    fire.setColour(Colour(255, 255, 255, 125));
    fire.setFadeSpeed(1.0f);
    Colour target = Colour::COLOUR_WHITE;
    target.setAlpha(0);
    fire.setTargetColour(target);
 
    wostringstream buffer2;
    buffer2 << L"FireBall_" << m_BallsNum;
    NodeParticleSystem* pPS = sm->createParticleSystem();
    pPS->setPosition(0.0f, 0.0f, 0.0f);
    pPS->addParticleEmitter(fire, L"fire_emitter");
    pSphere->addChild(buffer2.str(), pPS);
    Material* matFire = MaterialManager::getSingleton().getLibrary(L"default")->getMaterial(L"fire");
    pSurface = pPS->getSurface(L"fire_emitter");
    pSurface->setMaterial(matFire);
 
    ++m_BallsNum;
}

And here is final result:

Tut_2_1_1.jpg
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License