]> git.mxchange.org Git - simgear.git/blob - simgear/scene/model/particles.cxx
2dd2a0a46f5d8214b8259dc3b7890788db320dc1
[simgear.git] / simgear / scene / model / particles.cxx
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
4 //
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.
9 //
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.
14 //
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.
18 //
19
20 #ifdef HAVE_CONFIG_H
21 #  include <simgear_config.h>
22 #endif
23
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
30 #include <osgParticle/SmokeTrailEffect>
31 #include <osgParticle/FireEffect>
32 #include <osgParticle/ConnectedParticleSystem>
33 #include <osgParticle/MultiSegmentPlacer>
34 #include <osgParticle/SectorPlacer>
35 #include <osgParticle/ConstantRateCounter>
36 #include <osgParticle/ParticleSystemUpdater>
37 #include <osgParticle/ParticleSystem>
38 #include <osgParticle/FluidProgram>
39
40 #include <osg/Geode>
41 #include <osg/Group>
42 #include <osg/MatrixTransform>
43 #include <osg/Node>
44
45 #include "particles.hxx"
46
47 namespace simgear
48 {
49 void GlobalParticleCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
50 {
51     enabled = !enabledNode || enabledNode->getBoolValue();
52     if (!enabled)
53         return;
54     SGQuatd q
55         = SGQuatd::fromLonLatDeg(modelRoot->getFloatValue("/position/longitude-deg",0),
56                                  modelRoot->getFloatValue("/position/latitude-deg",0));
57     osg::Matrix om(q.osg());
58     osg::Vec3 v(0,0,9.81);
59     gravity = om.preMult(v);
60     const osg::Vec3& zUpWind = Particles::getWindVector();
61     osg::Vec3 w(zUpWind.y(), zUpWind.x(), - zUpWind.z());
62     wind = om.preMult(w);
63
64     //SG_LOG(SG_GENERAL, SG_ALERT, "wind vector:"<<w[0]<<","<<w[1]<<","<<w[2]<<"\n");
65 }
66
67
68 //static members
69 osg::Vec3 GlobalParticleCallback::gravity;
70 osg::Vec3 GlobalParticleCallback::wind;
71 bool GlobalParticleCallback::enabled = true;
72 SGConstPropertyNode_ptr GlobalParticleCallback::enabledNode = 0;
73
74 osg::ref_ptr<osg::Group> Particles::commonRoot;
75 osg::ref_ptr<osgParticle::ParticleSystemUpdater> Particles::psu = new osgParticle::ParticleSystemUpdater;
76 osg::ref_ptr<osg::Geode> Particles::commonGeode = new osg::Geode;;
77 osg::Vec3 Particles::_wind;
78
79 Particles::Particles() : 
80     useGravity(false),
81     useWind(false)
82 {
83 }
84
85 template <typename Object>
86 class PointerGuard{
87 public:
88     PointerGuard() : _ptr(0) {}
89     Object* get() { return _ptr; }
90     Object* operator () ()
91     {
92         if (!_ptr)
93             _ptr = new Object;
94         return _ptr;
95     }
96 private:
97     Object* _ptr;
98 };
99
100 osg::Group* Particles::getCommonRoot()
101 {
102     if(!commonRoot.valid())
103     {
104         SG_LOG(SG_GENERAL, SG_DEBUG, "Particle common root called!\n");
105         commonRoot = new osg::Group;
106         commonRoot.get()->setName("common particle system root");
107         commonGeode.get()->setName("common particle system geode");
108         commonRoot.get()->addChild(commonGeode.get());
109         commonRoot.get()->addChild(psu.get());
110     }
111     return commonRoot.get();
112 }
113
114 // Enable this once particle fix is in OSG.
115 // #define OSG_PARTICLE_FIX 1
116 void transformParticles(osgParticle::ParticleSystem* particleSys,
117                         const osg::Matrix& mat)
118 {
119     const int numParticles = particleSys->numParticles();
120     if (particleSys->areAllParticlesDead())
121         return;
122     for (int i = 0; i < numParticles; ++i) {
123         osgParticle::Particle* P = particleSys->getParticle(i);
124         if (!P->isAlive())
125             continue;
126         P->transformPositionVelocity(mat);
127     }
128 }
129
130 osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
131                                           SGPropertyNode* modelRoot,
132                                           const osgDB::ReaderWriter::Options*
133                                           options)
134 {
135     SG_LOG(SG_GENERAL, SG_DEBUG, "Setting up a particle system!\n");
136
137     osgParticle::ParticleSystem *particleSys;
138
139     //create a generic particle system
140     std::string type = configNode->getStringValue("type", "normal");
141     if (type == "normal")
142         particleSys = new osgParticle::ParticleSystem;
143     else
144         particleSys = new osgParticle::ConnectedParticleSystem;
145     //may not be used depending on the configuration
146     PointerGuard<Particles> callback;
147
148     getPSU()->addParticleSystem(particleSys); 
149     getPSU()->setUpdateCallback(new GlobalParticleCallback(modelRoot));
150     //contains counter, placer and shooter by default
151     osgParticle::ModularEmitter* emitter = new osgParticle::ModularEmitter;
152
153     emitter->setParticleSystem(particleSys);
154
155     // Set up the alignment node ("stolen" from animation.cxx)
156     // XXX Order of rotations is probably not correct.
157     osg::MatrixTransform *align = new osg::MatrixTransform;
158     osg::Matrix res_matrix;
159     res_matrix.makeRotate(
160         configNode->getFloatValue("offsets/pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS,
161         osg::Vec3(0, 1, 0),
162         configNode->getFloatValue("offsets/roll-deg", 0.0)*SG_DEGREES_TO_RADIANS,
163         osg::Vec3(1, 0, 0),
164         configNode->getFloatValue("offsets/heading-deg", 0.0)*SG_DEGREES_TO_RADIANS,
165         osg::Vec3(0, 0, 1));
166
167     osg::Matrix tmat;
168     tmat.makeTranslate(configNode->getFloatValue("offsets/x-m", 0.0),
169                        configNode->getFloatValue("offsets/y-m", 0.0),
170                        configNode->getFloatValue("offsets/z-m", 0.0));
171     align->setMatrix(res_matrix * tmat);
172
173     align->setName("particle align");
174
175     //if (dynamic_cast<CustomModularEmitter*>(emitter)==0) SG_LOG(SG_GENERAL, SG_ALERT, "observer error\n");
176     //align->addObserver(dynamic_cast<CustomModularEmitter*>(emitter));
177
178     align->addChild(emitter);
179
180     //this name can be used in the XML animation as if it was a submodel
181     std::string name = configNode->getStringValue("name", "");
182     if (!name.empty())
183         align->setName(name);
184     std::string attach = configNode->getStringValue("attach", "world");
185     if (attach == "local") { //local means attached to the model and not the world
186         osg::Geode* g = new osg::Geode;
187         align->addChild(g);
188         g->addDrawable(particleSys);
189 #ifndef OSG_PARTICLE_FIX
190         emitter->setReferenceFrame(osgParticle::Emitter::ABSOLUTE_RF);
191 #endif
192     } else {
193 #ifdef OSG_PARTICLE_FIX
194         callback()->particleFrame = new osg::MatrixTransform();
195         osg::Geode* g = new osg::Geode;
196         g->addDrawable(particleSys);
197         callback()->particleFrame->addChild(g);
198         getCommonRoot()->addChild(callback()->particleFrame.get());
199 #else
200         getCommonGeode()->addDrawable(particleSys);
201 #endif
202     }
203     std::string textureFile;
204     if (configNode->hasValue("texture")) {
205         //SG_LOG(SG_GENERAL, SG_ALERT,
206         //       "requested:"<<configNode->getStringValue("texture","")<<"\n");
207         textureFile= osgDB::findFileInPath(configNode->getStringValue("texture",
208                                                                       ""),
209                                            options->getDatabasePathList());
210         //SG_LOG(SG_GENERAL, SG_ALERT, "found:"<<textureFile<<"\n");
211
212         //for(unsigned i = 0; i < options->getDatabasePathList().size(); ++i)
213         //    SG_LOG(SG_GENERAL, SG_ALERT,
214         //           "opts:"<<options->getDatabasePathList()[i]<<"\n");
215     }
216
217     particleSys->setDefaultAttributes(textureFile,
218                                       configNode->getBoolValue("emissive",
219                                                                true),
220                                       configNode->getBoolValue("lighting",
221                                                                false));
222
223     std::string alignstr = configNode->getStringValue("align", "billboard");
224
225     if (alignstr == "fixed")
226         particleSys->setParticleAlignment(osgParticle::ParticleSystem::FIXED);
227
228     const SGPropertyNode* placernode = configNode->getChild("placer");
229
230     if (placernode) {
231         std::string emitterType = placernode->getStringValue("type", "point");
232
233         if (emitterType == "sector") {
234             osgParticle::SectorPlacer *splacer = new  osgParticle::SectorPlacer;
235             float minRadius, maxRadius, minPhi, maxPhi;
236
237             minRadius = placernode->getFloatValue("radius-min-m",0);
238             maxRadius = placernode->getFloatValue("radius-max-m",1);
239             minPhi = (placernode->getFloatValue("phi-min-deg",0)
240                       * SG_DEGREES_TO_RADIANS);
241             maxPhi = (placernode->getFloatValue("phi-max-deg",360.0f)
242                       * SG_DEGREES_TO_RADIANS);
243
244             splacer->setRadiusRange(minRadius, maxRadius);
245             splacer->setPhiRange(minPhi, maxPhi);
246             emitter->setPlacer(splacer);
247         } else if (emitterType == "segments") {
248             std::vector<SGPropertyNode_ptr> segments
249                 = placernode->getChildren("vertex");
250             if (segments.size()>1) {
251                 osgParticle::MultiSegmentPlacer *msplacer
252                     = new osgParticle::MultiSegmentPlacer();
253                 float x,y,z;
254
255                 for (unsigned i = 0; i < segments.size(); ++i) {
256                     x = segments[i]->getFloatValue("x-m",0);
257                     y = segments[i]->getFloatValue("y-m",0);
258                     z = segments[i]->getFloatValue("z-m",0);
259                     msplacer->addVertex(x, y, z);
260                 }
261                 emitter->setPlacer(msplacer);
262             } else {
263                 SG_LOG(SG_GENERAL, SG_ALERT,
264                        "Detected particle system using segment(s) with less than 2 vertices\n");
265             }
266         } //else the default placer in ModularEmitter is used (PointPlacer)
267     }
268
269     const SGPropertyNode* shnode = configNode->getChild("shooter");
270
271     if (shnode) {
272         float minTheta, maxTheta, minPhi, maxPhi, speed, spread;
273
274         minTheta = (shnode->getFloatValue("theta-min-deg",0)
275                     * SG_DEGREES_TO_RADIANS);
276         maxTheta = (shnode->getFloatValue("theta-max-deg",360.0f)
277                     * SG_DEGREES_TO_RADIANS);
278         minPhi = shnode->getFloatValue("phi-min-deg",0)* SG_DEGREES_TO_RADIANS;
279         maxPhi = (shnode->getFloatValue("phi-max-deg",360.0f)
280                   * SG_DEGREES_TO_RADIANS); 
281
282         osgParticle::RadialShooter *shooter = new osgParticle::RadialShooter;
283         emitter->setShooter(shooter);
284
285         shooter->setThetaRange(minTheta, maxTheta);
286         shooter->setPhiRange(minPhi, maxPhi);
287
288         const SGPropertyNode* speednode = shnode->getChild("speed-mps");
289
290         if (speednode) {
291             if (speednode->hasValue("value")) {
292                 speed = speednode->getFloatValue("value",0);
293                 spread = speednode->getFloatValue("spread",0);
294                 shooter->setInitialSpeedRange(speed-spread, speed+spread);
295             } else {
296                 callback()->setupShooterSpeedData(speednode, modelRoot);
297             }
298         }
299
300         const SGPropertyNode* rotspeednode = shnode->getChild("rotation-speed");
301
302         if (rotspeednode) {
303             float x1,y1,z1,x2,y2,z2;
304             x1 = rotspeednode->getFloatValue("x-min-deg-sec",0) * SG_DEGREES_TO_RADIANS;
305             y1 = rotspeednode->getFloatValue("y-min-deg-sec",0) * SG_DEGREES_TO_RADIANS;
306             z1 = rotspeednode->getFloatValue("z-min-deg-sec",0) * SG_DEGREES_TO_RADIANS;
307             x2 = rotspeednode->getFloatValue("x-max-deg-sec",0) * SG_DEGREES_TO_RADIANS;
308             y2 = rotspeednode->getFloatValue("y-max-deg-sec",0) * SG_DEGREES_TO_RADIANS;
309             z2 = rotspeednode->getFloatValue("z-max-deg-sec",0) * SG_DEGREES_TO_RADIANS;
310             shooter->setInitialRotationalSpeedRange(osg::Vec3f(x1,y1,z1), osg::Vec3f(x2,y2,z2));
311         }
312     } //else ModularEmitter uses the default RadialShooter
313
314
315     const SGPropertyNode* conditionNode = configNode->getChild("condition");
316     const SGPropertyNode* counternode = configNode->getChild("counter");
317
318     if (conditionNode || counternode) {
319         osgParticle::RandomRateCounter* counter
320             = new osgParticle::RandomRateCounter;
321         emitter->setCounter(counter);
322         float pps = 0.0f, spread = 0.0f;
323
324         if (counternode) {
325             const SGPropertyNode* ppsnode = counternode->getChild("particles-per-sec");
326             if (ppsnode) {
327                 if (ppsnode->hasValue("value")) {
328                     pps = ppsnode->getFloatValue("value",0);
329                     spread = ppsnode->getFloatValue("spread",0);
330                     counter->setRateRange(pps-spread, pps+spread);
331                 } else {
332                     callback()->setupCounterData(ppsnode, modelRoot);
333                 }
334             }
335         }
336
337         if (conditionNode) {
338             callback()->setupCounterCondition(conditionNode, modelRoot);
339             callback()->setupCounterCondition(pps, spread);
340         }
341     } //TODO: else perhaps set higher values than default? 
342
343     const SGPropertyNode* particlenode = configNode->getChild("particle");
344     if (particlenode) {
345         osgParticle::Particle &particle
346             = particleSys->getDefaultParticleTemplate();
347         float r1=0, g1=0, b1=0, a1=1, r2=0, g2=0, b2=0, a2=1;
348         const SGPropertyNode* startcolornode
349             = particlenode->getNode("start/color");
350         if (startcolornode) {
351             const SGPropertyNode* componentnode
352                 = startcolornode->getChild("red");
353             if (componentnode) {
354                 if (componentnode->hasValue("value"))
355                     r1 = componentnode->getFloatValue("value",0);
356                 else 
357                     callback()->setupColorComponent(componentnode, modelRoot,
358                                                     0, 0);
359             }
360             componentnode = startcolornode->getChild("green");
361             if (componentnode) {
362                 if (componentnode->hasValue("value"))
363                     g1 = componentnode->getFloatValue("value", 0);
364                 else
365                     callback()->setupColorComponent(componentnode, modelRoot,
366                                                     0, 1);
367             }
368             componentnode = startcolornode->getChild("blue");
369             if (componentnode) {
370                 if (componentnode->hasValue("value"))
371                     b1 = componentnode->getFloatValue("value",0);
372                 else
373                     callback()->setupColorComponent(componentnode, modelRoot,
374                                                     0, 2);
375             }
376             componentnode = startcolornode->getChild("alpha");
377             if (componentnode) {
378                 if (componentnode->hasValue("value"))
379                     a1 = componentnode->getFloatValue("value",0);
380                 else
381                     callback()->setupColorComponent(componentnode, modelRoot,
382                                                     0, 3);
383             }
384         }
385         const SGPropertyNode* endcolornode = particlenode->getNode("end/color");
386         if (endcolornode) {
387             const SGPropertyNode* componentnode = endcolornode->getChild("red");
388
389             if (componentnode) {
390                 if (componentnode->hasValue("value"))
391                     r2 = componentnode->getFloatValue("value",0);
392                 else
393                     callback()->setupColorComponent(componentnode, modelRoot,
394                                                     1, 0);
395             }
396             componentnode = endcolornode->getChild("green");
397             if (componentnode) {
398                 if (componentnode->hasValue("value"))
399                     g2 = componentnode->getFloatValue("value",0);
400                 else
401                     callback()->setupColorComponent(componentnode, modelRoot,
402                                                     1, 1);
403             }
404             componentnode = endcolornode->getChild("blue");
405             if (componentnode) {
406                 if (componentnode->hasValue("value"))
407                     b2 = componentnode->getFloatValue("value",0);
408                 else
409                     callback()->setupColorComponent(componentnode, modelRoot,
410                                                     1, 2);
411             }
412             componentnode = endcolornode->getChild("alpha");
413             if (componentnode) {
414                 if (componentnode->hasValue("value"))
415                     a2 = componentnode->getFloatValue("value",0);
416                 else
417                     callback()->setupColorComponent(componentnode, modelRoot,
418                                                     1, 3);
419             }
420         }
421         particle.setColorRange(osgParticle::rangev4(osg::Vec4(r1,g1,b1,a1),
422                                                     osg::Vec4(r2,g2,b2,a2)));
423
424         float startsize=1, endsize=0.1f;
425         const SGPropertyNode* startsizenode = particlenode->getNode("start/size");
426         if (startsizenode) {
427             if (startsizenode->hasValue("value"))
428                 startsize = startsizenode->getFloatValue("value",0);
429             else
430                 callback()->setupStartSizeData(startsizenode, modelRoot);
431         }
432         const SGPropertyNode* endsizenode = particlenode->getNode("end/size");
433         if (endsizenode) {
434             if (endsizenode->hasValue("value"))
435                 endsize = endsizenode->getFloatValue("value",0);
436             else
437                 callback()->setupEndSizeData(endsizenode, modelRoot);
438         }
439         particle.setSizeRange(osgParticle::rangef(startsize, endsize));
440         float life=5;
441         const SGPropertyNode* lifenode = particlenode->getChild("life-sec");
442         if (lifenode) {
443             if (lifenode->hasValue("value"))
444                 life =  lifenode->getFloatValue("value",0);
445             else
446                 callback()->setupLifeData(lifenode, modelRoot);
447         }
448
449         particle.setLifeTime(life);
450         if (particlenode->hasValue("radius-m"))
451             particle.setRadius(particlenode->getFloatValue("radius-m",0));
452         if (particlenode->hasValue("mass-kg"))
453             particle.setMass(particlenode->getFloatValue("mass-kg",0));
454         if (callback.get()) {
455             callback.get()->setupStaticColorComponent(r1, g1, b1, a1,
456                                                       r2, g2, b2, a2);
457             callback.get()->setupStaticSizeData(startsize, endsize);
458         }
459         //particle.setColorRange(osgParticle::rangev4( osg::Vec4(r1, g1, b1, a1), osg::Vec4(r2, g2, b2, a2)));
460     }
461
462     const SGPropertyNode* programnode = configNode->getChild("program");
463     osgParticle::FluidProgram *program = new osgParticle::FluidProgram();
464
465     if (programnode) {
466         std::string fluid = programnode->getStringValue("fluid", "air");
467
468         if (fluid=="air")
469             program->setFluidToAir();
470         else
471             program->setFluidToWater();
472
473         if (programnode->getBoolValue("gravity", true)) {
474 #ifdef OSG_PARTICLE_FIX
475             program->setToGravity();
476 #else
477             if (attach == "world")
478                 callback()->setupProgramGravity(true);
479             else
480                 program->setToGravity();
481 #endif
482         } else
483             program->setAcceleration(osg::Vec3(0,0,0));
484
485         if (programnode->getBoolValue("wind", true))
486             callback()->setupProgramWind(true);
487         else
488             program->setWind(osg::Vec3(0,0,0));
489
490         align->addChild(program);
491
492         program->setParticleSystem(particleSys);
493     }
494
495     if (callback.get()) {  //this means we want property-driven changes
496         SG_LOG(SG_GENERAL, SG_DEBUG, "setting up particle system user data and callback\n");
497         //setup data and callback
498         callback.get()->setGeneralData(dynamic_cast<osgParticle::RadialShooter*>(emitter->getShooter()),
499                                        dynamic_cast<osgParticle::RandomRateCounter*>(emitter->getCounter()),
500                                        particleSys, program);
501         emitter->setUpdateCallback(callback.get());
502     }
503
504     return align;
505 }
506
507 void Particles::operator()(osg::Node* node, osg::NodeVisitor* nv)
508 {
509     //SG_LOG(SG_GENERAL, SG_ALERT, "callback!\n");
510     using namespace osg;
511     if (shooterValue)
512         shooter->setInitialSpeedRange(shooterValue->getValue(),
513                                       (shooterValue->getValue()
514                                        + shooterExtraRange));
515     if (counterValue)
516         counter->setRateRange(counterValue->getValue(),
517                               counterValue->getValue() + counterExtraRange);
518     else if (counterCond)
519         counter->setRateRange(counterStaticValue,
520                               counterStaticValue + counterStaticExtraRange);
521     if (!GlobalParticleCallback::getEnabled() || (counterCond && !counterCond->test()))
522         counter->setRateRange(0, 0);
523     bool colorchange=false;
524     for (int i = 0; i < 8; ++i) {
525         if (colorComponents[i]) {
526             staticColorComponents[i] = colorComponents[i]->getValue();
527             colorchange=true;
528         }
529     }
530     if (colorchange)
531         particleSys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4( Vec4(staticColorComponents[0], staticColorComponents[1], staticColorComponents[2], staticColorComponents[3]), Vec4(staticColorComponents[4], staticColorComponents[5], staticColorComponents[6], staticColorComponents[7])));
532     if (startSizeValue)
533         startSize = startSizeValue->getValue();
534     if (endSizeValue)
535         endSize = endSizeValue->getValue();
536     if (startSizeValue || endSizeValue)
537         particleSys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(startSize, endSize));
538     if (lifeValue)
539         particleSys->getDefaultParticleTemplate().setLifeTime(lifeValue->getValue());
540 #ifdef OSG_PARTICLE_FIX
541     if (particleFrame.valid()) {
542         MatrixList mlist = node->getWorldMatrices();
543         if (!mlist.empty()) {
544             const Matrix& particleMat = particleFrame->getMatrix();
545             Vec3d emitOrigin(mlist[0](3, 0), mlist[0](3, 1), mlist[0](3, 2));
546             Vec3d displace
547                 = emitOrigin - Vec3d(particleMat(3, 0), particleMat(3, 1),
548                                      particleMat(3, 2));
549             if (displace * displace > 10000.0 * 10000.0) {
550                 // Make new frame for particle system, coincident with
551                 // the emitter frame, but oriented with local Z.
552                 SGGeod geod = SGGeod::fromCart(SGVec3d(emitOrigin));
553                 Matrix newParticleMat = geod.makeZUpFrame();
554                 Matrix changeParticleFrame
555                     = particleMat * Matrix::inverse(newParticleMat);
556                 particleFrame->setMatrix(newParticleMat);
557                 transformParticles(particleSys.get(), changeParticleFrame);
558             }
559         }
560     }
561     if (program.valid() && useWind)
562         program->setWind(_wind);
563 #else
564     if (program.valid()) {
565         if (useGravity)
566             program->setAcceleration(GlobalParticleCallback::getGravityVector());
567         if (useWind)
568             program->setWind(GlobalParticleCallback::getWindVector());
569     }
570 #endif
571 }
572 } // namespace simgear