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