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