1 // particles.cxx - classes to manage particles
2 // started in 2008 by Tiago Gusmão, using animation.hxx as reference
3 // Copyright (C) 2008 Tiago Gusmão
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 # include <simgear_config.h>
24 #include <simgear/math/SGMath.hxx>
25 #include <simgear/math/SGGeod.hxx>
26 #include <simgear/misc/sg_path.hxx>
27 #include <simgear/props/props.hxx>
28 #include <simgear/props/props_io.hxx>
29 #include <simgear/structure/OSGVersion.hxx>
31 #include <osgParticle/SmokeTrailEffect>
32 #include <osgParticle/FireEffect>
33 #include <osgParticle/ConnectedParticleSystem>
34 #include <osgParticle/MultiSegmentPlacer>
35 #include <osgParticle/SectorPlacer>
36 #include <osgParticle/ConstantRateCounter>
37 #include <osgParticle/ParticleSystemUpdater>
38 #include <osgParticle/ParticleSystem>
39 #include <osgParticle/FluidProgram>
43 #include <osg/MatrixTransform>
46 #include "particles.hxx"
48 #if SG_OSG_VERSION >= 27004
49 #define OSG_PARTICLE_FIX 1
54 void GlobalParticleCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
56 enabled = !enabledNode || enabledNode->getBoolValue();
60 = SGQuatd::fromLonLatDeg(modelRoot->getFloatValue("/position/longitude-deg",0),
61 modelRoot->getFloatValue("/position/latitude-deg",0));
62 osg::Matrix om(toOsg(q));
63 osg::Vec3 v(0,0,9.81);
64 gravity = om.preMult(v);
65 // NOTE: THIS WIND COMPUTATION DOESN'T SEEM TO AFFECT PARTICLES
66 const osg::Vec3& zUpWind = Particles::getWindVector();
67 osg::Vec3 w(zUpWind.y(), zUpWind.x(), -zUpWind.z());
70 // SG_LOG(SG_GENERAL, SG_ALERT,
71 // "wind vector:" << w[0] << "," <<w[1] << "," << w[2]);
76 osg::Vec3 GlobalParticleCallback::gravity;
77 osg::Vec3 GlobalParticleCallback::wind;
78 bool GlobalParticleCallback::enabled = true;
79 SGConstPropertyNode_ptr GlobalParticleCallback::enabledNode = 0;
81 osg::ref_ptr<osg::Group> Particles::commonRoot;
82 osg::ref_ptr<osgParticle::ParticleSystemUpdater> Particles::psu = new osgParticle::ParticleSystemUpdater;
83 osg::ref_ptr<osg::Geode> Particles::commonGeode = new osg::Geode;;
84 osg::Vec3 Particles::_wind;
85 bool Particles::_frozen = false;
87 Particles::Particles() :
93 template <typename Object>
96 PointerGuard() : _ptr(0) {}
97 Object* get() { return _ptr; }
98 Object* operator () ()
108 osg::Group* Particles::getCommonRoot()
110 if(!commonRoot.valid())
112 SG_LOG(SG_GENERAL, SG_DEBUG, "Particle common root called!\n");
113 commonRoot = new osg::Group;
114 commonRoot.get()->setName("common particle system root");
115 commonGeode.get()->setName("common particle system geode");
116 commonRoot.get()->addChild(commonGeode.get());
117 commonRoot.get()->addChild(psu.get());
119 return commonRoot.get();
122 void transformParticles(osgParticle::ParticleSystem* particleSys,
123 const osg::Matrix& mat)
125 const int numParticles = particleSys->numParticles();
126 if (particleSys->areAllParticlesDead())
128 for (int i = 0; i < numParticles; ++i) {
129 osgParticle::Particle* P = particleSys->getParticle(i);
132 P->transformPositionVelocity(mat);
136 osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
137 SGPropertyNode* modelRoot,
138 const osgDB::ReaderWriter::Options*
141 SG_LOG(SG_GENERAL, SG_DEBUG, "Setting up a particle system!\n");
143 osgParticle::ParticleSystem *particleSys;
145 //create a generic particle system
146 std::string type = configNode->getStringValue("type", "normal");
147 if (type == "normal")
148 particleSys = new osgParticle::ParticleSystem;
150 particleSys = new osgParticle::ConnectedParticleSystem;
151 //may not be used depending on the configuration
152 PointerGuard<Particles> callback;
154 getPSU()->addParticleSystem(particleSys);
155 getPSU()->setUpdateCallback(new GlobalParticleCallback(modelRoot));
156 //contains counter, placer and shooter by default
157 osgParticle::ModularEmitter* emitter = new osgParticle::ModularEmitter;
159 emitter->setParticleSystem(particleSys);
161 // Set up the alignment node ("stolen" from animation.cxx)
162 // XXX Order of rotations is probably not correct.
163 osg::MatrixTransform *align = new osg::MatrixTransform;
164 osg::Matrix res_matrix;
165 res_matrix.makeRotate(
166 configNode->getFloatValue("offsets/pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS,
168 configNode->getFloatValue("offsets/roll-deg", 0.0)*SG_DEGREES_TO_RADIANS,
170 configNode->getFloatValue("offsets/heading-deg", 0.0)*SG_DEGREES_TO_RADIANS,
174 tmat.makeTranslate(configNode->getFloatValue("offsets/x-m", 0.0),
175 configNode->getFloatValue("offsets/y-m", 0.0),
176 configNode->getFloatValue("offsets/z-m", 0.0));
177 align->setMatrix(res_matrix * tmat);
179 align->setName("particle align");
181 //if (dynamic_cast<CustomModularEmitter*>(emitter)==0) SG_LOG(SG_GENERAL, SG_ALERT, "observer error\n");
182 //align->addObserver(dynamic_cast<CustomModularEmitter*>(emitter));
184 align->addChild(emitter);
186 //this name can be used in the XML animation as if it was a submodel
187 std::string name = configNode->getStringValue("name", "");
189 align->setName(name);
190 std::string attach = configNode->getStringValue("attach", "world");
191 if (attach == "local") { //local means attached to the model and not the world
192 osg::Geode* g = new osg::Geode;
194 g->addDrawable(particleSys);
195 #ifndef OSG_PARTICLE_FIX
196 emitter->setReferenceFrame(osgParticle::Emitter::ABSOLUTE_RF);
199 #ifdef OSG_PARTICLE_FIX
200 callback()->particleFrame = new osg::MatrixTransform();
201 osg::Geode* g = new osg::Geode;
202 g->addDrawable(particleSys);
203 callback()->particleFrame->addChild(g);
204 getCommonRoot()->addChild(callback()->particleFrame.get());
206 getCommonGeode()->addDrawable(particleSys);
209 std::string textureFile;
210 if (configNode->hasValue("texture")) {
211 //SG_LOG(SG_GENERAL, SG_ALERT,
212 // "requested:"<<configNode->getStringValue("texture","")<<"\n");
213 textureFile= osgDB::findFileInPath(configNode->getStringValue("texture",
215 options->getDatabasePathList());
216 //SG_LOG(SG_GENERAL, SG_ALERT, "found:"<<textureFile<<"\n");
218 //for(unsigned i = 0; i < options->getDatabasePathList().size(); ++i)
219 // SG_LOG(SG_GENERAL, SG_ALERT,
220 // "opts:"<<options->getDatabasePathList()[i]<<"\n");
223 particleSys->setDefaultAttributes(textureFile,
224 configNode->getBoolValue("emissive",
226 configNode->getBoolValue("lighting",
229 std::string alignstr = configNode->getStringValue("align", "billboard");
231 if (alignstr == "fixed")
232 particleSys->setParticleAlignment(osgParticle::ParticleSystem::FIXED);
234 const SGPropertyNode* placernode = configNode->getChild("placer");
237 std::string emitterType = placernode->getStringValue("type", "point");
239 if (emitterType == "sector") {
240 osgParticle::SectorPlacer *splacer = new osgParticle::SectorPlacer;
241 float minRadius, maxRadius, minPhi, maxPhi;
243 minRadius = placernode->getFloatValue("radius-min-m",0);
244 maxRadius = placernode->getFloatValue("radius-max-m",1);
245 minPhi = (placernode->getFloatValue("phi-min-deg",0)
246 * SG_DEGREES_TO_RADIANS);
247 maxPhi = (placernode->getFloatValue("phi-max-deg",360.0f)
248 * SG_DEGREES_TO_RADIANS);
250 splacer->setRadiusRange(minRadius, maxRadius);
251 splacer->setPhiRange(minPhi, maxPhi);
252 emitter->setPlacer(splacer);
253 } else if (emitterType == "segments") {
254 std::vector<SGPropertyNode_ptr> segments
255 = placernode->getChildren("vertex");
256 if (segments.size()>1) {
257 osgParticle::MultiSegmentPlacer *msplacer
258 = new osgParticle::MultiSegmentPlacer();
261 for (unsigned i = 0; i < segments.size(); ++i) {
262 x = segments[i]->getFloatValue("x-m",0);
263 y = segments[i]->getFloatValue("y-m",0);
264 z = segments[i]->getFloatValue("z-m",0);
265 msplacer->addVertex(x, y, z);
267 emitter->setPlacer(msplacer);
269 SG_LOG(SG_GENERAL, SG_ALERT,
270 "Detected particle system using segment(s) with less than 2 vertices\n");
272 } //else the default placer in ModularEmitter is used (PointPlacer)
275 const SGPropertyNode* shnode = configNode->getChild("shooter");
278 float minTheta, maxTheta, minPhi, maxPhi, speed, spread;
280 minTheta = (shnode->getFloatValue("theta-min-deg",0)
281 * SG_DEGREES_TO_RADIANS);
282 maxTheta = (shnode->getFloatValue("theta-max-deg",360.0f)
283 * SG_DEGREES_TO_RADIANS);
284 minPhi = shnode->getFloatValue("phi-min-deg",0)* SG_DEGREES_TO_RADIANS;
285 maxPhi = (shnode->getFloatValue("phi-max-deg",360.0f)
286 * SG_DEGREES_TO_RADIANS);
288 osgParticle::RadialShooter *shooter = new osgParticle::RadialShooter;
289 emitter->setShooter(shooter);
291 shooter->setThetaRange(minTheta, maxTheta);
292 shooter->setPhiRange(minPhi, maxPhi);
294 const SGPropertyNode* speednode = shnode->getChild("speed-mps");
297 if (speednode->hasValue("value")) {
298 speed = speednode->getFloatValue("value",0);
299 spread = speednode->getFloatValue("spread",0);
300 shooter->setInitialSpeedRange(speed-spread, speed+spread);
302 callback()->setupShooterSpeedData(speednode, modelRoot);
306 const SGPropertyNode* rotspeednode = shnode->getChild("rotation-speed");
309 float x1,y1,z1,x2,y2,z2;
310 x1 = rotspeednode->getFloatValue("x-min-deg-sec",0) * SG_DEGREES_TO_RADIANS;
311 y1 = rotspeednode->getFloatValue("y-min-deg-sec",0) * SG_DEGREES_TO_RADIANS;
312 z1 = rotspeednode->getFloatValue("z-min-deg-sec",0) * SG_DEGREES_TO_RADIANS;
313 x2 = rotspeednode->getFloatValue("x-max-deg-sec",0) * SG_DEGREES_TO_RADIANS;
314 y2 = rotspeednode->getFloatValue("y-max-deg-sec",0) * SG_DEGREES_TO_RADIANS;
315 z2 = rotspeednode->getFloatValue("z-max-deg-sec",0) * SG_DEGREES_TO_RADIANS;
316 shooter->setInitialRotationalSpeedRange(osg::Vec3f(x1,y1,z1), osg::Vec3f(x2,y2,z2));
318 } //else ModularEmitter uses the default RadialShooter
321 const SGPropertyNode* conditionNode = configNode->getChild("condition");
322 const SGPropertyNode* counternode = configNode->getChild("counter");
324 if (conditionNode || counternode) {
325 osgParticle::RandomRateCounter* counter
326 = new osgParticle::RandomRateCounter;
327 emitter->setCounter(counter);
328 float pps = 0.0f, spread = 0.0f;
331 const SGPropertyNode* ppsnode = counternode->getChild("particles-per-sec");
333 if (ppsnode->hasValue("value")) {
334 pps = ppsnode->getFloatValue("value",0);
335 spread = ppsnode->getFloatValue("spread",0);
336 counter->setRateRange(pps-spread, pps+spread);
338 callback()->setupCounterData(ppsnode, modelRoot);
344 callback()->setupCounterCondition(conditionNode, modelRoot);
345 callback()->setupCounterCondition(pps, spread);
347 } //TODO: else perhaps set higher values than default?
349 const SGPropertyNode* particlenode = configNode->getChild("particle");
351 osgParticle::Particle &particle
352 = particleSys->getDefaultParticleTemplate();
353 float r1=0, g1=0, b1=0, a1=1, r2=0, g2=0, b2=0, a2=1;
354 const SGPropertyNode* startcolornode
355 = particlenode->getNode("start/color");
356 if (startcolornode) {
357 const SGPropertyNode* componentnode
358 = startcolornode->getChild("red");
360 if (componentnode->hasValue("value"))
361 r1 = componentnode->getFloatValue("value",0);
363 callback()->setupColorComponent(componentnode, modelRoot,
366 componentnode = startcolornode->getChild("green");
368 if (componentnode->hasValue("value"))
369 g1 = componentnode->getFloatValue("value", 0);
371 callback()->setupColorComponent(componentnode, modelRoot,
374 componentnode = startcolornode->getChild("blue");
376 if (componentnode->hasValue("value"))
377 b1 = componentnode->getFloatValue("value",0);
379 callback()->setupColorComponent(componentnode, modelRoot,
382 componentnode = startcolornode->getChild("alpha");
384 if (componentnode->hasValue("value"))
385 a1 = componentnode->getFloatValue("value",0);
387 callback()->setupColorComponent(componentnode, modelRoot,
391 const SGPropertyNode* endcolornode = particlenode->getNode("end/color");
393 const SGPropertyNode* componentnode = endcolornode->getChild("red");
396 if (componentnode->hasValue("value"))
397 r2 = componentnode->getFloatValue("value",0);
399 callback()->setupColorComponent(componentnode, modelRoot,
402 componentnode = endcolornode->getChild("green");
404 if (componentnode->hasValue("value"))
405 g2 = componentnode->getFloatValue("value",0);
407 callback()->setupColorComponent(componentnode, modelRoot,
410 componentnode = endcolornode->getChild("blue");
412 if (componentnode->hasValue("value"))
413 b2 = componentnode->getFloatValue("value",0);
415 callback()->setupColorComponent(componentnode, modelRoot,
418 componentnode = endcolornode->getChild("alpha");
420 if (componentnode->hasValue("value"))
421 a2 = componentnode->getFloatValue("value",0);
423 callback()->setupColorComponent(componentnode, modelRoot,
427 particle.setColorRange(osgParticle::rangev4(osg::Vec4(r1,g1,b1,a1),
428 osg::Vec4(r2,g2,b2,a2)));
430 float startsize=1, endsize=0.1f;
431 const SGPropertyNode* startsizenode = particlenode->getNode("start/size");
433 if (startsizenode->hasValue("value"))
434 startsize = startsizenode->getFloatValue("value",0);
436 callback()->setupStartSizeData(startsizenode, modelRoot);
438 const SGPropertyNode* endsizenode = particlenode->getNode("end/size");
440 if (endsizenode->hasValue("value"))
441 endsize = endsizenode->getFloatValue("value",0);
443 callback()->setupEndSizeData(endsizenode, modelRoot);
445 particle.setSizeRange(osgParticle::rangef(startsize, endsize));
447 const SGPropertyNode* lifenode = particlenode->getChild("life-sec");
449 if (lifenode->hasValue("value"))
450 life = lifenode->getFloatValue("value",0);
452 callback()->setupLifeData(lifenode, modelRoot);
455 particle.setLifeTime(life);
456 if (particlenode->hasValue("radius-m"))
457 particle.setRadius(particlenode->getFloatValue("radius-m",0));
458 if (particlenode->hasValue("mass-kg"))
459 particle.setMass(particlenode->getFloatValue("mass-kg",0));
460 if (callback.get()) {
461 callback.get()->setupStaticColorComponent(r1, g1, b1, a1,
463 callback.get()->setupStaticSizeData(startsize, endsize);
465 //particle.setColorRange(osgParticle::rangev4( osg::Vec4(r1, g1, b1, a1), osg::Vec4(r2, g2, b2, a2)));
468 const SGPropertyNode* programnode = configNode->getChild("program");
469 osgParticle::FluidProgram *program = new osgParticle::FluidProgram();
472 std::string fluid = programnode->getStringValue("fluid", "air");
475 program->setFluidToAir();
477 program->setFluidToWater();
479 if (programnode->getBoolValue("gravity", true)) {
480 #ifdef OSG_PARTICLE_FIX
481 program->setToGravity();
483 if (attach == "world")
484 callback()->setupProgramGravity(true);
486 program->setToGravity();
489 program->setAcceleration(osg::Vec3(0,0,0));
491 if (programnode->getBoolValue("wind", true))
492 callback()->setupProgramWind(true);
494 program->setWind(osg::Vec3(0,0,0));
496 align->addChild(program);
498 program->setParticleSystem(particleSys);
501 if (callback.get()) { //this means we want property-driven changes
502 SG_LOG(SG_GENERAL, SG_DEBUG, "setting up particle system user data and callback\n");
503 //setup data and callback
504 callback.get()->setGeneralData(dynamic_cast<osgParticle::RadialShooter*>(emitter->getShooter()),
505 dynamic_cast<osgParticle::RandomRateCounter*>(emitter->getCounter()),
506 particleSys, program);
507 emitter->setUpdateCallback(callback.get());
513 void Particles::operator()(osg::Node* node, osg::NodeVisitor* nv)
515 //SG_LOG(SG_GENERAL, SG_ALERT, "callback!\n");
516 this->particleSys->setFrozen(_frozen);
520 shooter->setInitialSpeedRange(shooterValue->getValue(),
521 (shooterValue->getValue()
522 + shooterExtraRange));
524 counter->setRateRange(counterValue->getValue(),
525 counterValue->getValue() + counterExtraRange);
526 else if (counterCond)
527 counter->setRateRange(counterStaticValue,
528 counterStaticValue + counterStaticExtraRange);
529 if (!GlobalParticleCallback::getEnabled() || (counterCond && !counterCond->test()))
530 counter->setRateRange(0, 0);
531 bool colorchange=false;
532 for (int i = 0; i < 8; ++i) {
533 if (colorComponents[i]) {
534 staticColorComponents[i] = colorComponents[i]->getValue();
539 particleSys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4( Vec4(staticColorComponents[0], staticColorComponents[1], staticColorComponents[2], staticColorComponents[3]), Vec4(staticColorComponents[4], staticColorComponents[5], staticColorComponents[6], staticColorComponents[7])));
541 startSize = startSizeValue->getValue();
543 endSize = endSizeValue->getValue();
544 if (startSizeValue || endSizeValue)
545 particleSys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(startSize, endSize));
547 particleSys->getDefaultParticleTemplate().setLifeTime(lifeValue->getValue());
548 #ifdef OSG_PARTICLE_FIX
549 if (particleFrame.valid()) {
550 MatrixList mlist = node->getWorldMatrices();
551 if (!mlist.empty()) {
552 const Matrix& particleMat = particleFrame->getMatrix();
553 Vec3d emitOrigin(mlist[0](3, 0), mlist[0](3, 1), mlist[0](3, 2));
555 = emitOrigin - Vec3d(particleMat(3, 0), particleMat(3, 1),
557 if (displace * displace > 10000.0 * 10000.0) {
558 // Make new frame for particle system, coincident with
559 // the emitter frame, but oriented with local Z.
560 SGGeod geod = SGGeod::fromCart(toSG(emitOrigin));
561 Matrix newParticleMat = geod.makeZUpFrame();
562 Matrix changeParticleFrame
563 = particleMat * Matrix::inverse(newParticleMat);
564 particleFrame->setMatrix(newParticleMat);
565 transformParticles(particleSys.get(), changeParticleFrame);
569 if (program.valid() && useWind)
570 program->setWind(_wind);
572 if (program.valid()) {
574 program->setAcceleration(GlobalParticleCallback::getGravityVector());
576 program->setWind(GlobalParticleCallback::getWindVector());
580 } // namespace simgear