]> git.mxchange.org Git - simgear.git/blob - simgear/scene/tgdb/pt_lights.cxx
Fixed a crash: the singleton needs to be instantiated the first time SGCommandMgr...
[simgear.git] / simgear / scene / tgdb / pt_lights.cxx
1 // pt_lights.cxx -- build a 'directional' light on the fly
2 //
3 // Written by Curtis Olson, started March 2002.
4 //
5 // Copyright (C) 2002  Curtis L. Olson  - http://www.flightgear.org/~curt
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22
23 #ifdef HAVE_CONFIG_H
24 #  include <simgear_config.h>
25 #endif
26
27 #include "pt_lights.hxx"
28
29 #include <map>
30 #include <boost/tuple/tuple_comparison.hpp>
31
32 #include <osg/Array>
33 #include <osg/Geometry>
34 #include <osg/CullFace>
35 #include <osg/Geode>
36 #include <osg/MatrixTransform>
37 #include <osg/NodeCallback>
38 #include <osg/NodeVisitor>
39 #include <osg/Texture2D>
40 #include <osg/AlphaFunc>
41 #include <osg/BlendFunc>
42 #include <osg/TexEnv>
43 #include <osg/Sequence>
44 #include <osg/PolygonMode>
45 #include <osg/Fog>
46 #include <osg/FragmentProgram>
47 #include <osg/VertexProgram>
48 #include <osg/Point>
49 #include <osg/PointSprite>
50 #include <osg/Material>
51 #include <osg/Group>
52 #include <osg/StateSet>
53
54 #include <osgUtil/CullVisitor>
55
56 #include <OpenThreads/Mutex>
57 #include <OpenThreads/ScopedLock>
58
59 #include <simgear/math/sg_random.h>
60 #include <simgear/debug/logstream.hxx>
61 #include <simgear/scene/util/RenderConstants.hxx>
62 #include <simgear/scene/util/SGEnlargeBoundingBox.hxx>
63 #include <simgear/scene/util/OsgMath.hxx>
64 #include <simgear/scene/util/StateAttributeFactory.hxx>
65
66 #include <simgear/scene/material/Effect.hxx>
67 #include <simgear/scene/material/EffectGeode.hxx>
68 #include <simgear/scene/material/Technique.hxx>
69 #include <simgear/scene/material/Pass.hxx>
70
71 #include "SGVasiDrawable.hxx"
72
73 using OpenThreads::Mutex;
74 using OpenThreads::ScopedLock;
75
76 using namespace osg;
77 using namespace simgear;
78
79 static void
80 setPointSpriteImage(unsigned char* data, unsigned log2resolution,
81                     unsigned charsPerPixel)
82 {
83   int env_tex_res = (1 << log2resolution);
84   for (int i = 0; i < env_tex_res; ++i) {
85     for (int j = 0; j < env_tex_res; ++j) {
86       int xi = 2*i + 1 - env_tex_res;
87       int yi = 2*j + 1 - env_tex_res;
88       if (xi < 0)
89         xi = -xi;
90       if (yi < 0)
91         yi = -yi;
92       
93       xi -= 1;
94       yi -= 1;
95       
96       if (xi < 0)
97         xi = 0;
98       if (yi < 0)
99         yi = 0;
100       
101       float x = 1.5*xi/(float)(env_tex_res);
102       float y = 1.5*yi/(float)(env_tex_res);
103       //       float x = 2*xi/(float)(env_tex_res);
104       //       float y = 2*yi/(float)(env_tex_res);
105       float dist = sqrt(x*x + y*y);
106       float bright = SGMiscf::clip(255*(1-dist), 0, 255);
107       for (unsigned l = 0; l < charsPerPixel; ++l)
108         data[charsPerPixel*(i*env_tex_res + j) + l] = (unsigned char)bright;
109     }
110   }
111 }
112
113 static osg::Image*
114 getPointSpriteImage(int logResolution)
115 {
116   osg::Image* image = new osg::Image;
117   
118   osg::Image::MipmapDataType mipmapOffsets;
119   unsigned off = 0;
120   for (int i = logResolution; 0 <= i; --i) {
121     unsigned res = 1 << i;
122     off += res*res;
123     mipmapOffsets.push_back(off);
124   }
125   
126   int env_tex_res = (1 << logResolution);
127   
128   unsigned char* imageData = new unsigned char[off];
129   image->setImage(env_tex_res, env_tex_res, 1,
130                   GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE, imageData,
131                   osg::Image::USE_NEW_DELETE);
132   image->setMipmapLevels(mipmapOffsets);
133   
134   for (int k = logResolution; 0 <= k; --k) {
135     setPointSpriteImage(image->getMipmapData(logResolution - k), k, 1);
136   }
137   
138   return image;
139 }
140
141 static Mutex lightMutex;
142
143 static osg::Texture2D*
144 gen_standard_light_sprite(void)
145 {
146   // Always called from when the lightMutex is already taken
147   static osg::ref_ptr<osg::Texture2D> texture;
148   if (texture.valid())
149     return texture.get();
150   
151   texture = new osg::Texture2D;
152   texture->setImage(getPointSpriteImage(6));
153   texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
154   texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
155   
156   return texture.get();
157 }
158
159 namespace
160 {
161 typedef boost::tuple<float, osg::Vec3, float, float, bool> PointParams;
162 typedef std::map<PointParams, observer_ptr<Effect> > EffectMap;
163
164 EffectMap effectMap;
165
166 ref_ptr<PolygonMode> polyMode = new PolygonMode(PolygonMode::FRONT,
167                                                 PolygonMode::POINT);
168 ref_ptr<PointSprite> pointSprite = new PointSprite;
169 }
170
171 Effect* getLightEffect(float size, const Vec3& attenuation,
172                        float minSize, float maxSize, bool directional)
173 {
174     PointParams pointParams(size, attenuation, minSize, maxSize, directional);
175     ScopedLock<Mutex> lock(lightMutex);
176     EffectMap::iterator eitr = effectMap.find(pointParams);
177     if (eitr != effectMap.end())
178     {
179         ref_ptr<Effect> effect;
180         if (eitr->second.lock(effect))
181             return effect.release();
182     }
183     // Basic stuff; no sprite or attenuation support
184     Pass *basicPass = new Pass;
185     basicPass->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
186     basicPass->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
187     StateAttributeFactory *attrFact = StateAttributeFactory::instance();
188     basicPass->setAttributeAndModes(attrFact->getStandardBlendFunc());
189     basicPass->setAttributeAndModes(attrFact->getStandardAlphaFunc());
190     if (directional) {
191         basicPass->setAttributeAndModes(attrFact->getCullFaceBack());
192         basicPass->setAttribute(polyMode.get());
193     }
194     Pass *attenuationPass = clone(basicPass, CopyOp::SHALLOW_COPY);
195     osg::Point* point = new osg::Point;
196     point->setMinSize(minSize);
197     point->setMaxSize(maxSize);
198     point->setSize(size);
199     point->setDistanceAttenuation(attenuation);
200     attenuationPass->setAttributeAndModes(point);
201     Pass *spritePass = clone(basicPass, CopyOp::SHALLOW_COPY);
202     spritePass->setTextureAttributeAndModes(0, pointSprite.get(),
203                                             osg::StateAttribute::ON);
204     Texture2D* texture = gen_standard_light_sprite();
205     spritePass->setTextureAttribute(0, texture);
206     spritePass->setTextureMode(0, GL_TEXTURE_2D,
207                                osg::StateAttribute::ON);
208     spritePass->setTextureAttribute(0, attrFact->getStandardTexEnv());
209     Pass *combinedPass = clone(spritePass, CopyOp::SHALLOW_COPY);
210     combinedPass->setAttributeAndModes(point);
211     ref_ptr<Effect> effect = new Effect;
212     std::vector<std::string> parameterExtensions;
213
214     if (SGSceneFeatures::instance()->getEnablePointSpriteLights())
215     {
216         std::vector<std::string> combinedExtensions;
217         combinedExtensions.push_back("GL_ARB_point_sprite");
218         combinedExtensions.push_back("GL_ARB_point_parameters");
219         Technique* combinedTniq = new Technique;
220         combinedTniq->passes.push_back(combinedPass);
221         combinedTniq->setGLExtensionsPred(2.0, combinedExtensions);
222         effect->techniques.push_back(combinedTniq);
223         std::vector<std::string> spriteExtensions;
224         spriteExtensions.push_back(combinedExtensions.front());
225         Technique* spriteTniq = new Technique;
226         spriteTniq->passes.push_back(spritePass);
227         spriteTniq->setGLExtensionsPred(2.0, spriteExtensions);
228         effect->techniques.push_back(spriteTniq);
229         parameterExtensions.push_back(combinedExtensions.back());
230     }
231
232     Technique* parameterTniq = new Technique;
233     parameterTniq->passes.push_back(attenuationPass);
234     parameterTniq->setGLExtensionsPred(1.4, parameterExtensions);
235     effect->techniques.push_back(parameterTniq);
236     Technique* basicTniq = new Technique(true);
237     basicTniq->passes.push_back(basicPass);
238     effect->techniques.push_back(basicTniq);
239     if (eitr == effectMap.end())
240         effectMap.insert(std::make_pair(pointParams, effect));
241     else
242         eitr->second = effect; // update existing, but empty observer
243     return effect.release();
244 }
245
246
247 osg::Drawable*
248 SGLightFactory::getLightDrawable(const SGLightBin::Light& light)
249 {
250   osg::Vec3Array* vertices = new osg::Vec3Array;
251   osg::Vec4Array* colors = new osg::Vec4Array;
252
253   vertices->push_back(toOsg(light.position));
254   colors->push_back(toOsg(light.color));
255   
256   osg::Geometry* geometry = new osg::Geometry;
257   geometry->setDataVariance(osg::Object::STATIC);
258   geometry->setVertexArray(vertices);
259   geometry->setNormalBinding(osg::Geometry::BIND_OFF);
260   geometry->setColorArray(colors);
261   geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
262
263   // Enlarge the bounding box to avoid such light nodes being victim to
264   // small feature culling.
265   geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
266   
267   osg::DrawArrays* drawArrays;
268   drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
269                                    0, vertices->size());
270   geometry->addPrimitiveSet(drawArrays);
271   return geometry;
272 }
273
274 osg::Drawable*
275 SGLightFactory::getLightDrawable(const SGDirectionalLightBin::Light& light)
276 {
277   osg::Vec3Array* vertices = new osg::Vec3Array;
278   osg::Vec4Array* colors = new osg::Vec4Array;
279
280   SGVec4f visibleColor(light.color);
281   SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
282                          visibleColor[2], 0);
283   SGVec3f normal = normalize(light.normal);
284   SGVec3f perp1 = perpendicular(normal);
285   SGVec3f perp2 = cross(normal, perp1);
286   SGVec3f position = light.position;
287   vertices->push_back(toOsg(position));
288   vertices->push_back(toOsg(position + perp1));
289   vertices->push_back(toOsg(position + perp2));
290   colors->push_back(toOsg(visibleColor));
291   colors->push_back(toOsg(invisibleColor));
292   colors->push_back(toOsg(invisibleColor));
293   
294   osg::Geometry* geometry = new osg::Geometry;
295   geometry->setDataVariance(osg::Object::STATIC);
296   geometry->setVertexArray(vertices);
297   geometry->setNormalBinding(osg::Geometry::BIND_OFF);
298   geometry->setColorArray(colors);
299   geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
300   // Enlarge the bounding box to avoid such light nodes being victim to
301   // small feature culling.
302   geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
303   
304   osg::DrawArrays* drawArrays;
305   drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
306                                    0, vertices->size());
307   geometry->addPrimitiveSet(drawArrays);
308   return geometry;
309 }
310
311 namespace
312 {
313   ref_ptr<StateSet> simpleLightSS;
314 }
315 osg::Drawable*
316 SGLightFactory::getLights(const SGLightBin& lights, unsigned inc, float alphaOff)
317 {
318   if (lights.getNumLights() <= 0)
319     return 0;
320   
321   osg::Vec3Array* vertices = new osg::Vec3Array;
322   osg::Vec4Array* colors = new osg::Vec4Array;
323
324   for (unsigned i = 0; i < lights.getNumLights(); i += inc) {
325     vertices->push_back(toOsg(lights.getLight(i).position));
326     SGVec4f color = lights.getLight(i).color;
327     color[3] = SGMiscf::max(0, SGMiscf::min(1, color[3] + alphaOff));
328     colors->push_back(toOsg(color));
329   }
330   
331   osg::Geometry* geometry = new osg::Geometry;
332   geometry->setDataVariance(osg::Object::STATIC);
333   geometry->setVertexArray(vertices);
334   geometry->setNormalBinding(osg::Geometry::BIND_OFF);
335   geometry->setColorArray(colors);
336   geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
337   
338   osg::DrawArrays* drawArrays;
339   drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
340                                    0, vertices->size());
341   geometry->addPrimitiveSet(drawArrays);
342
343   {
344     ScopedLock<Mutex> lock(lightMutex);
345     if (!simpleLightSS.valid()) {
346       StateAttributeFactory *attrFact = StateAttributeFactory::instance();
347       simpleLightSS = new StateSet;
348       simpleLightSS->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
349       simpleLightSS->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
350       simpleLightSS->setAttributeAndModes(attrFact->getStandardBlendFunc());
351       simpleLightSS->setAttributeAndModes(attrFact->getStandardAlphaFunc());
352     }
353   }
354   geometry->setStateSet(simpleLightSS.get());
355   return geometry;
356 }
357
358
359 osg::Drawable*
360 SGLightFactory::getLights(const SGDirectionalLightBin& lights)
361 {
362   if (lights.getNumLights() <= 0)
363     return 0;
364   
365   osg::Vec3Array* vertices = new osg::Vec3Array;
366   osg::Vec4Array* colors = new osg::Vec4Array;
367
368   for (unsigned i = 0; i < lights.getNumLights(); ++i) {
369     SGVec4f visibleColor(lights.getLight(i).color);
370     SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
371                            visibleColor[2], 0);
372     SGVec3f normal = normalize(lights.getLight(i).normal);
373     SGVec3f perp1 = perpendicular(normal);
374     SGVec3f perp2 = cross(normal, perp1);
375     SGVec3f position = lights.getLight(i).position;
376     vertices->push_back(toOsg(position));
377     vertices->push_back(toOsg(position + perp1));
378     vertices->push_back(toOsg(position + perp2));
379     colors->push_back(toOsg(visibleColor));
380     colors->push_back(toOsg(invisibleColor));
381     colors->push_back(toOsg(invisibleColor));
382   }
383   
384   osg::Geometry* geometry = new osg::Geometry;
385   geometry->setDataVariance(osg::Object::STATIC);
386   geometry->setVertexArray(vertices);
387   geometry->setNormalBinding(osg::Geometry::BIND_OFF);
388   geometry->setColorArray(colors);
389   geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
390   
391   osg::DrawArrays* drawArrays;
392   drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
393                                    0, vertices->size());
394   geometry->addPrimitiveSet(drawArrays);
395   return geometry;
396 }
397
398 static SGVasiDrawable*
399 buildVasi(const SGDirectionalLightBin& lights, const SGVec3f& up,
400        const SGVec4f& red, const SGVec4f& white)
401 {
402   unsigned count = lights.getNumLights();
403   if ( count == 4 ) {
404     SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
405
406     // PAPI configuration
407     // papi D
408     drawable->addLight(lights.getLight(0).position,
409                        lights.getLight(0).normal, up, 3.5);
410     // papi C
411     drawable->addLight(lights.getLight(1).position,
412                        lights.getLight(1).normal, up, 3.167);
413     // papi B
414     drawable->addLight(lights.getLight(2).position,
415                        lights.getLight(2).normal, up, 2.833);
416     // papi A
417     drawable->addLight(lights.getLight(3).position,
418                        lights.getLight(3).normal, up, 2.5);
419     return drawable;
420
421   } else if (count == 6) {
422     SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
423
424     // probably vasi, first 3 are downwind bar (2.5 deg)
425     for (unsigned i = 0; i < 3; ++i)
426       drawable->addLight(lights.getLight(i).position,
427                          lights.getLight(i).normal, up, 2.5);
428     // last 3 are upwind bar (3.0 deg)
429     for (unsigned i = 3; i < 6; ++i)
430       drawable->addLight(lights.getLight(i).position,
431                          lights.getLight(i).normal, up, 3.0);
432     return drawable;
433
434   } else if (count == 12) {
435     SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
436     
437     // probably vasi, first 6 are downwind bar (2.5 deg)
438     for (unsigned i = 0; i < 6; ++i)
439       drawable->addLight(lights.getLight(i).position,
440                          lights.getLight(i).normal, up, 2.5);
441     // last 6 are upwind bar (3.0 deg)
442     for (unsigned i = 6; i < 12; ++i)
443       drawable->addLight(lights.getLight(i).position,
444                          lights.getLight(i).normal, up, 3.0);
445     return drawable;
446
447   } else {
448     // fail safe
449     SG_LOG(SG_TERRAIN, SG_ALERT,
450            "unknown vasi/papi configuration, count = " << count);
451     return 0;
452   }
453 }
454
455 osg::Drawable*
456 SGLightFactory::getVasi(const SGVec3f& up, const SGDirectionalLightBin& lights,
457                         const SGVec4f& red, const SGVec4f& white)
458 {
459   SGVasiDrawable* drawable = buildVasi(lights, up, red, white);
460   if (!drawable)
461     return 0;
462
463   osg::StateSet* stateSet = drawable->getOrCreateStateSet();
464   stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
465   stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
466
467   osg::BlendFunc* blendFunc = new osg::BlendFunc;
468   stateSet->setAttribute(blendFunc);
469   stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
470   
471   osg::AlphaFunc* alphaFunc;
472   alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
473   stateSet->setAttribute(alphaFunc);
474   stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
475
476   return drawable;
477 }
478
479 osg::Node*
480 SGLightFactory::getSequenced(const SGDirectionalLightBin& lights)
481 {
482   if (lights.getNumLights() <= 0)
483     return 0;
484
485   // generate a repeatable random seed
486   sg_srandom(unsigned(lights.getLight(0).position[0]));
487   float flashTime = 2e-2 + 5e-3*sg_random();
488   osg::Sequence* sequence = new osg::Sequence;
489   sequence->setDefaultTime(flashTime);
490   Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.0001, 0.00000001),
491                                   6.0f, 10.0f, true);
492   for (int i = lights.getNumLights() - 1; 0 <= i; --i) {
493     EffectGeode* egeode = new EffectGeode;
494     egeode->setEffect(effect);
495     egeode->addDrawable(getLightDrawable(lights.getLight(i)));
496     sequence->addChild(egeode, flashTime);
497   }
498   sequence->addChild(new osg::Group, 1 + 1e-1*sg_random());
499   sequence->setInterval(osg::Sequence::LOOP, 0, -1);
500   sequence->setDuration(1.0f, -1);
501   sequence->setMode(osg::Sequence::START);
502   sequence->setSync(true);
503   return sequence;
504 }
505
506 osg::Node*
507 SGLightFactory::getOdal(const SGLightBin& lights)
508 {
509   if (lights.getNumLights() < 2)
510     return 0;
511
512   // generate a repeatable random seed
513   sg_srandom(unsigned(lights.getLight(0).position[0]));
514   float flashTime = 2e-2 + 5e-3*sg_random();
515   osg::Sequence* sequence = new osg::Sequence;
516   sequence->setDefaultTime(flashTime);
517   Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.0001, 0.00000001),
518                                   6.0, 10.0, false);
519   // centerline lights
520   for (int i = lights.getNumLights(); i > 1; --i) {
521     EffectGeode* egeode = new EffectGeode;
522     egeode->setEffect(effect);
523     egeode->addDrawable(getLightDrawable(lights.getLight(i)));
524     sequence->addChild(egeode, flashTime);
525   }
526   // runway end lights
527   osg::Group* group = new osg::Group;
528   for (unsigned i = 0; i < 2; ++i) {
529     EffectGeode* egeode = new EffectGeode;
530     egeode->setEffect(effect);
531     egeode->addDrawable(getLightDrawable(lights.getLight(i)));
532     group->addChild(egeode);
533   }
534   sequence->addChild(group, flashTime);
535
536   // add an extra empty group for a break
537   sequence->addChild(new osg::Group, 2 + 1e-1*sg_random());
538   sequence->setInterval(osg::Sequence::LOOP, 0, -1);
539   sequence->setDuration(1.0f, -1);
540   sequence->setMode(osg::Sequence::START);
541   sequence->setSync(true);
542
543   return sequence;
544 }
545
546 // Blinking hold short line lights
547 osg::Node*
548 SGLightFactory::getHoldShort(const SGDirectionalLightBin& lights)
549 {
550   if (lights.getNumLights() < 2)
551     return 0;
552
553   sg_srandom(unsigned(lights.getLight(0).position[0]));
554   float flashTime = 1 + 0.1 * sg_random();
555   osg::Sequence* sequence = new osg::Sequence;
556
557   // start with lights off
558   sequence->addChild(new osg::Group, flashTime);
559   // ...and increase the lights in steps
560   for (int i = 2; i < 7; i+=2) {
561       Effect* effect = getLightEffect(i, osg::Vec3(1, 0.001, 0.000002),
562                                       0.0f, i, true);
563       EffectGeode* egeode = new EffectGeode;
564       for (unsigned int j = 0; j < lights.getNumLights(); ++j) {
565           egeode->addDrawable(getLightDrawable(lights.getLight(j)));
566           egeode->setEffect(effect);
567       }
568       sequence->addChild(egeode, (i==6) ? flashTime : 0.1);
569   }
570
571   sequence->setInterval(osg::Sequence::SWING, 0, -1);
572   sequence->setDuration(1.0f, -1);
573   sequence->setMode(osg::Sequence::START);
574
575   return sequence;
576 }
577
578 // Alternating runway guard lights ("wig-wag")
579 osg::Node*
580 SGLightFactory::getGuard(const SGDirectionalLightBin& lights)
581 {
582   if (lights.getNumLights() < 2)
583     return 0;
584
585   // generate a repeatable random seed
586   sg_srandom(unsigned(lights.getLight(0).position[0]));
587   float flashTime = 1.0f + 1*sg_random();
588   osg::Sequence* sequence = new osg::Sequence;
589   sequence->setDefaultTime(flashTime);
590   Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.001, 0.000002),
591                                   0.0f, 8.0f, true);
592   for (unsigned int i = 0; i < lights.getNumLights(); ++i) {
593     EffectGeode* egeode = new EffectGeode;
594     egeode->setEffect(effect);
595     egeode->addDrawable(getLightDrawable(lights.getLight(i)));
596     sequence->addChild(egeode, flashTime);
597   }
598   sequence->setInterval(osg::Sequence::LOOP, 0, -1);
599   sequence->setDuration(1.0f, -1);
600   sequence->setMode(osg::Sequence::START);
601   sequence->setSync(true);
602   return sequence;
603 }