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