/* OpenSceneGraph example, osgparticle. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include ////////////////////////////////////////////////////////////////////////////// // CUSTOM OPERATOR CLASS ////////////////////////////////////////////////////////////////////////////// // This class demonstrates Operator subclassing. This way you can create // custom operators to apply your motion effects to the particles. See docs // for more details. class VortexOperator: public osgParticle::Operator { public: VortexOperator() : osgParticle::Operator(), center_(0, 0, 0), axis_(0, 0, 1), intensity_(0.1f) {} VortexOperator(const VortexOperator ©, const osg::CopyOp ©op = osg::CopyOp::SHALLOW_COPY) : osgParticle::Operator(copy, copyop), center_(copy.center_), axis_(copy.axis_), intensity_(copy.intensity_) {} META_Object(osgParticle, VortexOperator); void setCenter(const osg::Vec3 &c) { center_ = c; } void setAxis(const osg::Vec3 &a) { axis_ = a / a.length(); } // this method is called by ModularProgram before applying // operators on the particle set via the operate() method. void beginOperate(osgParticle::Program *prg) { // we have to check whether the reference frame is RELATIVE_RF to parents // or it's absolute; in the first case, we must transform the vectors // from local to world space. if (prg->getReferenceFrame() == osgParticle::Program::RELATIVE_RF) { // transform the center point (full transformation) xf_center_ = prg->transformLocalToWorld(center_); // transform the axis vector (only rotation and scale) xf_axis_ = prg->rotateLocalToWorld(axis_); } else { xf_center_ = center_; xf_axis_ = axis_; } } // apply a vortex-like acceleration. This code is not optimized, // it's here only for demonstration purposes. void operate(osgParticle::Particle *P, double dt) { float l = xf_axis_ * (P->getPosition() - xf_center_); osg::Vec3 lc = xf_center_ + xf_axis_ * l; osg::Vec3 R = P->getPosition() - lc; osg::Vec3 v = (R ^ xf_axis_) * P->getMassInv() * intensity_; // compute new position osg::Vec3 newpos = P->getPosition() + v * dt; // update the position of the particle without modifying its // velocity vector (this is unusual, normally you should call // the Particle::setVelocity() or Particle::addVelocity() // methods). P->setPosition(newpos); } protected: virtual ~VortexOperator() {} private: osg::Vec3 center_; osg::Vec3 xf_center_; osg::Vec3 axis_; osg::Vec3 xf_axis_; float intensity_; }; ////////////////////////////////////////////////////////////////////////////// // SIMPLE PARTICLE SYSTEM CREATION ////////////////////////////////////////////////////////////////////////////// osgParticle::ParticleSystem *create_simple_particle_system(osg::Group *root) { // Ok folks, this is the first particle system we build; it will be // very simple, with no textures and no special effects, just default // values except for a couple of attributes. // First of all, we create the ParticleSystem object; it will hold // our particles and expose the interface for managing them; this object // is a Drawable, so we'll have to add it to a Geode later. osgParticle::ParticleSystem *ps = new osgParticle::ParticleSystem; // As for other Drawable classes, the aspect of graphical elements of // ParticleSystem (the particles) depends on the StateAttribute's we // give it. The ParticleSystem class has an helper function that let // us specify a set of the most common attributes: setDefaultAttributes(). // This method can accept up to three parameters; the first is a texture // name (std::string), which can be empty to disable texturing, the second // sets whether particles have to be "emissive" (additive blending) or not; // the third parameter enables or disables lighting. ps->setDefaultAttributes("", true, false); // Now that our particle system is set we have to create an emitter, that is // an object (actually a Node descendant) that generate new particles at // each frame. The best choice is to use a ModularEmitter, which allow us to // achieve a wide variety of emitting styles by composing the emitter using // three objects: a "counter", a "placer" and a "shooter". The counter must // tell the ModularEmitter how many particles it has to create for the // current frame; then, the ModularEmitter creates these particles, and for // each new particle it instructs the placer and the shooter to set its // position vector and its velocity vector, respectively. // By default, a ModularEmitter object initializes itself with a counter of // type RandomRateCounter, a placer of type PointPlacer and a shooter of // type RadialShooter (see documentation for details). We are going to leave // these default objects there, but we'll modify the counter so that it // counts faster (more particles are emitted at each frame). osgParticle::ModularEmitter *emitter = new osgParticle::ModularEmitter; // the first thing you *MUST* do after creating an emitter is to set the // destination particle system, otherwise it won't know where to create // new particles. emitter->setParticleSystem(ps); // Ok, get a pointer to the emitter's Counter object. We could also // create a new RandomRateCounter object and assign it to the emitter, // but since the default counter is already a RandomRateCounter, we // just get a pointer to it and change a value. osgParticle::RandomRateCounter *rrc = static_cast(emitter->getCounter()); // Now set the rate range to a better value. The actual rate at each frame // will be chosen randomly within that range. rrc->setRateRange(20, 30); // generate 20 to 30 particles per second // The emitter is done! Let's add it to the scene graph. The cool thing is // that any emitter node will take into account the accumulated local-to-world // matrix, so you can attach an emitter to a transform node and see it move. root->addChild(emitter); // Ok folks, we have almost finished. We don't add any particle modifier // here (see ModularProgram and Operator classes), so all we still need is // to create a Geode and add the particle system to it, so it can be // displayed. osg::Geode *geode = new osg::Geode; geode->addDrawable(ps); // add the geode to the scene graph root->addChild(geode); return ps; } ////////////////////////////////////////////////////////////////////////////// // COMPLEX PARTICLE SYSTEM CREATION ////////////////////////////////////////////////////////////////////////////// osgParticle::ParticleSystem *create_complex_particle_system(osg::Group *root) { // Are you ready for a more complex particle system? Well, read on! // Now we take one step we didn't before: create a particle template. // A particle template is simply a Particle object for which you set // the desired properties (see documentation for details). When the // particle system has to create a new particle and it's been assigned // a particle template, the new particle will inherit the template's // properties. // You can even assign different particle templates to each emitter; in // this case, the emitter's template will override the particle system's // default template. osgParticle::Particle ptemplate; ptemplate.setLifeTime(3); // 3 seconds of life // the following ranges set the envelope of the respective // graphical properties in time. ptemplate.setSizeRange(osgParticle::rangef(0.75f, 3.0f)); ptemplate.setAlphaRange(osgParticle::rangef(0.0f, 1.5f)); ptemplate.setColorRange(osgParticle::rangev4( osg::Vec4(1, 0.5f, 0.3f, 1.5f), osg::Vec4(0, 0.7f, 1.0f, 0.0f))); // these are physical properties of the particle ptemplate.setRadius(0.05f); // 5 cm wide particles ptemplate.setMass(0.05f); // 50 g heavy // As usual, let's create the ParticleSystem object and set its // default state attributes. This time we use a texture named // "smoke.rgb", you can find it in the data distribution of OSG. // We turn off the additive blending, because smoke has no self- // illumination. osgParticle::ParticleSystem *ps = new osgParticle::ParticleSystem; ps->setDefaultAttributes("Images/smoke.rgb", false, false); // assign the particle template to the system. ps->setDefaultParticleTemplate(ptemplate); // now we have to create an emitter; this will be a ModularEmitter, for which // we define a RandomRateCounter as counter, a SectorPlacer as placer, and // a RadialShooter as shooter. osgParticle::ModularEmitter *emitter = new osgParticle::ModularEmitter; emitter->setParticleSystem(ps); // setup the counter osgParticle::RandomRateCounter *counter = new osgParticle::RandomRateCounter; counter->setRateRange(60, 60); emitter->setCounter(counter); // setup the placer; it will be a circle of radius 5 (the particles will // be placed inside this circle). osgParticle::SectorPlacer *placer = new osgParticle::SectorPlacer; placer->setCenter(8, 0, 10); placer->setRadiusRange(2.5, 5); placer->setPhiRange(0, 2 * osg::PI); // 360° angle to make a circle emitter->setPlacer(placer); // now let's setup the shooter; we use a RadialShooter but we set the // initial speed to zero, because we want the particles to fall down // only under the effect of the gravity force. Since we se the speed // to zero, there is no need to setup the shooting angles. osgParticle::RadialShooter *shooter = new osgParticle::RadialShooter; shooter->setInitialSpeedRange(0, 0); emitter->setShooter(shooter); // add the emitter to the scene graph root->addChild(emitter); // WELL, we got our particle system and a nice emitter. Now we want to // simulate the effect of the earth gravity, so first of all we have to // create a Program. It is a particle processor just like the Emitter // class, but it allows to modify particle properties *after* they have // been created. // The ModularProgram class can be thought as a sequence of operators, // each one performing some actions on the particles. So, the trick is: // create the ModularProgram object, create one or more Operator objects, // add those operators to the ModularProgram, and finally add the // ModularProgram object to the scene graph. // NOTE: since the Program objects perform actions after the particles // have been emitted by one or more Emitter objects, all instances of // Program (and its descendants) should be placed *after* the instances // of Emitter objects in the scene graph. osgParticle::ModularProgram *program = new osgParticle::ModularProgram; program->setParticleSystem(ps); // create an operator that simulates the gravity acceleration. osgParticle::AccelOperator *op1 = new osgParticle::AccelOperator; op1->setToGravity(); program->addOperator(op1); // now create a custom operator, we have defined it before (see // class VortexOperator). VortexOperator *op2 = new VortexOperator; op2->setCenter(osg::Vec3(8, 0, 0)); program->addOperator(op2); // let's add a fluid operator to simulate air friction. osgParticle::FluidFrictionOperator *op3 = new osgParticle::FluidFrictionOperator; op3->setFluidToAir(); program->addOperator(op3); // add the program to the scene graph root->addChild(program); // create a Geode to contain our particle system. osg::Geode *geode = new osg::Geode; geode->addDrawable(ps); // add the geode to the scene graph. root->addChild(geode); return ps; } ////////////////////////////////////////////////////////////////////////////// // ANIMATED PARTICLE SYSTEM CREATION ////////////////////////////////////////////////////////////////////////////// osgParticle::ParticleSystem *create_animated_particle_system(osg::Group *root) { // Now we will create a particle system that uses two emitters to // display two animated particles, one showing an explosion, the other // a smoke cloud. A particle system can only use one texture, so // the animations for both particles are stored in a single bitmap. // The frames of the animation are stored in tiles. For each particle // template, the start and end tile of their animation have to be given. // The example file used here has 64 tiles, stored in eight rows with // eight images each. // First create a prototype for the explosion particle. osgParticle::Particle pexplosion; // The frames of the explosion particle are played from birth to // death of the particle. So if lifetime is one second, all 16 images // of the particle are shown in this second. pexplosion.setLifeTime(1); // some other particle properties just as in the last example. pexplosion.setSizeRange(osgParticle::rangef(0.75f, 3.0f)); pexplosion.setAlphaRange(osgParticle::rangef(0.5f, 1.0f)); pexplosion.setColorRange(osgParticle::rangev4( osg::Vec4(1, 1, 1, 1), osg::Vec4(1, 1, 1, 1))); pexplosion.setRadius(0.05f); pexplosion.setMass(0.05f); // This command sets the animation tiles to be shown for the particle. // The first two parameters define the tile layout of the texture image. // 8, 8 means the texture has eight rows of tiles with eight columns each. // 0, 15 defines the start and end tile pexplosion.setTextureTileRange(8, 8, 0, 15); // The smoke particle is just the same, only plays another tile range. osgParticle::Particle psmoke = pexplosion; psmoke.setTextureTileRange(8, 8, 32, 45); // Create a single particle system for both particle types osgParticle::ParticleSystem *ps = new osgParticle::ParticleSystem; // Assign the tiled texture ps->setDefaultAttributes("Images/fireparticle8x8.png", false, false); // Create two emitters, one for the explosions, one for the smoke balls. osgParticle::ModularEmitter *emitter1 = new osgParticle::ModularEmitter; emitter1->setParticleSystem(ps); emitter1->setParticleTemplate(pexplosion); osgParticle::ModularEmitter *emitter2 = new osgParticle::ModularEmitter; emitter2->setParticleSystem(ps); emitter2->setParticleTemplate(psmoke); // create a counter each. We could reuse the counter for both emitters, but // then we could not control the ratio of smoke balls to explosions osgParticle::RandomRateCounter *counter1 = new osgParticle::RandomRateCounter; counter1->setRateRange(10, 10); emitter1->setCounter(counter1); osgParticle::RandomRateCounter *counter2 = new osgParticle::RandomRateCounter; counter2->setRateRange(3, 4); emitter2->setCounter(counter2); // setup a single placer for both emitters. osgParticle::SectorPlacer *placer = new osgParticle::SectorPlacer; placer->setCenter(-8, 0, 0); placer->setRadiusRange(2.5, 5); placer->setPhiRange(0, 2 * osg::PI); // 360° angle to make a circle emitter1->setPlacer(placer); emitter2->setPlacer(placer); // the shooter is reused for both emitters osgParticle::RadialShooter *shooter = new osgParticle::RadialShooter; shooter->setInitialSpeedRange(0, 0); // give particles a little spin shooter->setInitialRotationalSpeedRange(osgParticle::rangev3( osg::Vec3(0, 0, -1), osg::Vec3(0, 0, 1))); emitter1->setShooter(shooter); emitter2->setShooter(shooter); // add both emitters to the scene graph root->addChild(emitter1); root->addChild(emitter2); // create a program, just as before osgParticle::ModularProgram *program = new osgParticle::ModularProgram; program->setParticleSystem(ps); // create an operator that moves the particles upwards osgParticle::AccelOperator *op1 = new osgParticle::AccelOperator; op1->setAcceleration(osg::Vec3(0, 0, 2.0f)); program->addOperator(op1); // add the program to the scene graph root->addChild(program); // create a Geode to contain our particle system. osg::Geode *geode = new osg::Geode; geode->addDrawable(ps); // add the geode to the scene graph. root->addChild(geode); return ps; } ////////////////////////////////////////////////////////////////////////////// // MAIN SCENE GRAPH BUILDING FUNCTION ////////////////////////////////////////////////////////////////////////////// void build_world(osg::Group *root) { // In this function we are going to create two particle systems; // the first one will be very simple, based mostly on default properties; // the second one will be a little bit more complex, showing how to // create custom operators. // To avoid inserting too much code in a single function, we have // splitted the work into two functions which accept a Group node as // parameter, and return a pointer to the particle system they created. osgParticle::ParticleSystem *ps1 = create_simple_particle_system(root); osgParticle::ParticleSystem *ps2 = create_complex_particle_system(root); osgParticle::ParticleSystem *ps3 = create_animated_particle_system(root); // Now that the particle systems and all other related objects have been // created, we have to add an "updater" node to the scene graph. This node // will react to cull traversal by updating the specified particles system. osgParticle::ParticleSystemUpdater *psu = new osgParticle::ParticleSystemUpdater; psu->addParticleSystem(ps1); psu->addParticleSystem(ps2); psu->addParticleSystem(ps3); // add the updater node to the scene graph root->addChild(psu); } ////////////////////////////////////////////////////////////////////////////// // main() ////////////////////////////////////////////////////////////////////////////// int main(int, char **) { // construct the viewer. osgViewer::Viewer viewer; osg::Group *root = new osg::Group; build_world(root); // add the stats handler viewer.addEventHandler(new osgViewer::StatsHandler); // add a viewport to the viewer and attach the scene graph. viewer.setSceneData(root); return viewer.run(); }