1 // particles.cxx - classes to manage particles
2 // started in 2008 by Tiago Gusmão, using animation.hxx as reference
3 // Copyright (C) 2008 Tiago Gusmão
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 # include <simgear_config.h>
24 #include <simgear/misc/sg_path.hxx>
25 #include <simgear/props/props.hxx>
26 #include <simgear/props/props_io.hxx>
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>
38 #include <osg/MatrixTransform>
40 #include "particles.hxx"
44 void GlobalParticleCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
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);
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);
57 //SG_LOG(SG_GENERAL, SG_ALERT, "wind vector:"<<w[0]<<","<<w[1]<<","<<w[2]<<"\n");
62 osg::Vec3 GlobalParticleCallback::gravity;
63 osg::Vec3 GlobalParticleCallback::wind;
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;;
69 template <typename Object>
72 PointerGuard() : _ptr(0) {}
73 Object* get() { return _ptr; }
74 Object* operator () ()
84 osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
85 SGPropertyNode* modelRoot,
86 const osgDB::ReaderWriter::Options*
89 SG_LOG(SG_GENERAL, SG_DEBUG, "Setting up a particle system!\n");
91 osgParticle::ParticleSystem *particleSys;
93 //create a generic particle system
94 std::string type = configNode->getStringValue("type", "normal");
96 particleSys = new osgParticle::ParticleSystem;
98 particleSys = new osgParticle::ConnectedParticleSystem;
99 //may not be used depending on the configuration
100 PointerGuard<Particles> callback;
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;
107 emitter->setParticleSystem(particleSys);
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,
116 configNode->getFloatValue("offsets/roll-deg", 0.0)*SG_DEGREES_TO_RADIANS,
118 configNode->getFloatValue("offsets/heading-deg", 0.0)*SG_DEGREES_TO_RADIANS,
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);
127 align->setName("particle align");
129 //if (dynamic_cast<CustomModularEmitter*>(emitter)==0) SG_LOG(SG_GENERAL, SG_ALERT, "observer error\n");
130 //align->addObserver(dynamic_cast<CustomModularEmitter*>(emitter));
132 align->addChild(emitter);
134 //this name can be used in the XML animation as if it was a submodel
135 std::string name = configNode->getStringValue("name", "");
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;
142 g->addDrawable(particleSys);
143 emitter->setReferenceFrame(osgParticle::Emitter::ABSOLUTE_RF);
145 getCommonGeode()->addDrawable(particleSys);
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",
153 options->getDatabasePathList());
154 SG_LOG(SG_GENERAL, SG_ALERT, "found:"<<textureFile<<"\n");
156 for(int i = 0; i < options->getDatabasePathList().size(); ++i)
157 SG_LOG(SG_GENERAL, SG_ALERT,
158 "opts:"<<options->getDatabasePathList()[i]<<"\n");
162 if (textureFile.empty())
165 particleSys->setDefaultAttributes(textureFile,
166 configNode->getBoolValue("emissive",
168 configNode->getBoolValue("lighting",
171 std::string alignstr = configNode->getStringValue("align", "billboard");
173 if (alignstr == "fixed")
174 particleSys->setParticleAlignment(osgParticle::ParticleSystem::FIXED);
176 const SGPropertyNode* placernode = configNode->getChild("placer");
179 std::string emitterType = placernode->getStringValue("type", "point");
181 if (emitterType == "sector") {
182 osgParticle::SectorPlacer *splacer = new osgParticle::SectorPlacer;
183 float minRadius, maxRadius, minPhi, maxPhi;
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);
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();
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);
209 emitter->setPlacer(msplacer);
211 SG_LOG(SG_GENERAL, SG_ALERT,
212 "Detected particle system using segment(s) with less than 2 vertices\n");
214 } //else the default placer in ModularEmitter is used (PointPlacer)
217 const SGPropertyNode* shnode = configNode->getChild("shooter");
220 float minTheta, maxTheta, minPhi, maxPhi, speed, spread;
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);
230 osgParticle::RadialShooter *shooter = new osgParticle::RadialShooter;
231 emitter->setShooter(shooter);
233 shooter->setThetaRange(minTheta, maxTheta);
234 shooter->setPhiRange(minPhi, maxPhi);
236 const SGPropertyNode* speednode = shnode->getChild("speed");
239 if (speednode->hasValue("value")) {
240 speed = speednode->getFloatValue("value",0);
241 spread = speednode->getFloatValue("spread",0);
242 shooter->setInitialSpeedRange(speed-spread, speed+spread);
244 callback()->setupShooterSpeedData(speednode, modelRoot);
248 const SGPropertyNode* rotspeednode = shnode->getChild("rotspeed");
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));
260 } //else ModularEmitter uses the default RadialShooter
262 const SGPropertyNode* counternode = configNode->getChild("counter");
265 osgParticle::RandomRateCounter* counter
266 = new osgParticle::RandomRateCounter;
267 emitter->setCounter(counter);
268 float pps = 0.0f, spread = 0.0f;
269 const SGPropertyNode* ppsnode = counternode->getChild("pps");
273 if (ppsnode->hasValue("value")) {
274 pps = ppsnode->getFloatValue("value",0);
275 spread = ppsnode->getFloatValue("spread",0);
276 counter->setRateRange(pps-spread, pps+spread);
278 callback()->setupCounterData(ppsnode, modelRoot);
281 const SGPropertyNode* conditionNode
282 = counternode->getChild("condition");
284 callback()->setupCounterCondition(conditionNode, modelRoot);
285 callback()->setupCounterCondition(pps, spread);
287 } //TODO: else perhaps set higher values than default?
289 const SGPropertyNode* particlenode = configNode->getChild("particle");
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");
300 if (componentnode->hasValue("value"))
301 r1 = componentnode->getFloatValue("value",0);
303 callback()->setupColorComponent(componentnode, modelRoot,
306 componentnode = startcolornode->getChild("green");
308 if (componentnode->hasValue("value"))
309 g1 = componentnode->getFloatValue("value", 0);
311 callback()->setupColorComponent(componentnode, modelRoot,
314 componentnode = startcolornode->getChild("blue");
316 if (componentnode->hasValue("value"))
317 b1 = componentnode->getFloatValue("value",0);
319 callback()->setupColorComponent(componentnode, modelRoot,
322 componentnode = startcolornode->getChild("alpha");
324 if (componentnode->hasValue("value"))
325 a1 = componentnode->getFloatValue("value",0);
327 callback()->setupColorComponent(componentnode, modelRoot,
331 const SGPropertyNode* endcolornode = particlenode->getChild("endcolor");
333 const SGPropertyNode* componentnode = endcolornode->getChild("red");
336 if (componentnode->hasValue("value"))
337 r2 = componentnode->getFloatValue("value",0);
339 callback()->setupColorComponent(componentnode, modelRoot,
342 componentnode = endcolornode->getChild("green");
344 if (componentnode->hasValue("value"))
345 g2 = componentnode->getFloatValue("value",0);
347 callback()->setupColorComponent(componentnode, modelRoot,
350 componentnode = endcolornode->getChild("blue");
352 if (componentnode->hasValue("value"))
353 b2 = componentnode->getFloatValue("value",0);
355 callback()->setupColorComponent(componentnode, modelRoot,
358 componentnode = endcolornode->getChild("alpha");
360 if (componentnode->hasValue("value"))
361 a2 = componentnode->getFloatValue("value",0);
363 callback()->setupColorComponent(componentnode, modelRoot,
367 particle.setColorRange(osgParticle::rangev4(osg::Vec4(r1,g1,b1,a1),
368 osg::Vec4(r2,g2,b2,a2)));
370 float startsize=1, endsize=0.1f;
371 const SGPropertyNode* startsizenode = particlenode->getChild("startsize");
373 if (startsizenode->hasValue("value"))
374 startsize = startsizenode->getFloatValue("value",0);
376 callback()->setupStartSizeData(startsizenode, modelRoot);
378 const SGPropertyNode* endsizenode = particlenode->getChild("endsize");
380 if (endsizenode->hasValue("value"))
381 endsize = endsizenode->getFloatValue("value",0);
383 callback()->setupEndSizeData(endsizenode, modelRoot);
385 particle.setSizeRange(osgParticle::rangef(startsize, endsize));
387 const SGPropertyNode* lifenode = particlenode->getChild("life-sec");
389 if (lifenode->hasValue("value"))
390 life = lifenode->getFloatValue("value",0);
392 callback()->setupLifeData(lifenode, modelRoot);
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,
403 callback.get()->setupStaticSizeData(startsize, endsize);
405 //particle.setColorRange(osgParticle::rangev4( osg::Vec4(r1, g1, b1, a1), osg::Vec4(r2, g2, b2, a2)));
408 const SGPropertyNode* programnode = configNode->getChild("program");
409 osgParticle::FluidProgram *program = new osgParticle::FluidProgram();
412 std::string fluid = programnode->getStringValue("fluid","air");
415 program->setFluidToAir();
418 program->setFluidToWater();
420 std::string grav = programnode->getStringValue("gravity","enabled");
422 if (grav=="enabled") {
424 if (attach == "world")
425 callback()->setupProgramGravity(true);
427 program->setToGravity();
429 program->setAcceleration(osg::Vec3(0,0,0));
431 std::string wind = programnode->getStringValue("wind","enabled");
433 callback()->setupProgramWind(true);
435 program->setWind(osg::Vec3(0,0,0));
437 align->addChild(program);
439 program->setParticleSystem(particleSys);
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());
455 void Particles::operator()(osg::Node* node, osg::NodeVisitor* nv)
457 //SG_LOG(SG_GENERAL, SG_ALERT, "callback!\n");
460 shooter->setInitialSpeedRange(shooterValue->getValue(),
461 (shooterValue->getValue()
462 + shooterExtraRange));
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();
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])));
481 startSize = startSizeValue->getValue();
483 endSize = endSizeValue->getValue();
484 if (startSizeValue || endSizeValue)
485 particleSys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(startSize, endSize));
487 particleSys->getDefaultParticleTemplate().setLifeTime(lifeValue->getValue());
490 program->setAcceleration(GlobalParticleCallback::getGravityVector());
492 program->setWind(GlobalParticleCallback::getWindVector());
495 } // namespace simgear