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 
31 #include <osg/Array>
32 #include <osg/Geometry>
33 #include <osg/CullFace>
34 #include <osg/Geode>
35 #include <osg/MatrixTransform>
36 #include <osg/NodeCallback>
37 #include <osg/NodeVisitor>
38 #include <osg/Texture2D>
39 #include <osg/AlphaFunc>
40 #include <osg/BlendFunc>
41 #include <osg/TexEnv>
42 #include <osg/Sequence>
43 #include <osg/Fog>
44 #include <osg/FragmentProgram>
45 #include <osg/VertexProgram>
46 #include <osg/Point>
47 #include <osg/Material>
48 #include <osg/Group>
49 #include <osg/StateSet>
50 
51 #include <osgUtil/CullVisitor>
52 
53 #include <OpenThreads/Mutex>
54 #include <OpenThreads/ScopedLock>
55 
56 #include <simgear/math/sg_random.h>
57 #include <simgear/debug/logstream.hxx>
58 #include <simgear/scene/util/RenderConstants.hxx>
59 #include <simgear/scene/util/SGEnlargeBoundingBox.hxx>
60 #include <simgear/scene/util/OsgMath.hxx>
61 #include <simgear/scene/util/StateAttributeFactory.hxx>
62 
63 #include <simgear/scene/material/Effect.hxx>
64 #include <simgear/scene/material/EffectGeode.hxx>
65 #include <simgear/scene/material/Technique.hxx>
66 #include <simgear/scene/material/Pass.hxx>
67 
68 #include "SGVasiDrawable.hxx"
69 
70 using OpenThreads::Mutex;
71 using OpenThreads::ScopedLock;
72 
73 using namespace osg;
74 using namespace simgear;
75 
76 static Mutex lightMutex;
77 
78 namespace
79 {
80 typedef std::tuple<float, osg::Vec3, float, float, bool> PointParams;
81 typedef std::map<PointParams, observer_ptr<Effect> > EffectMap;
82 
83 EffectMap effectMap;
84 }
85 
getLightEffect(float size,const Vec3 & attenuation,float minSize,float maxSize,bool directional,const SGReaderWriterOptions * options)86 Effect* getLightEffect(float size, const Vec3& attenuation,
87                        float minSize, float maxSize, bool directional,
88                        const SGReaderWriterOptions* options)
89 {
90     PointParams pointParams(size, attenuation, minSize, maxSize, directional);
91     ScopedLock<Mutex> lock(lightMutex);
92     ref_ptr<Effect> effect;
93     EffectMap::iterator eitr = effectMap.find(pointParams);
94     if (eitr != effectMap.end())
95     {
96         if (eitr->second.lock(effect))
97             return effect.release();
98     }
99 
100     SGPropertyNode_ptr effectProp = new SGPropertyNode;
101     if (directional) {
102     	makeChild(effectProp, "inherits-from")->setStringValue("Effects/surface-lights-directional");
103     } else {
104     	makeChild(effectProp, "inherits-from")->setStringValue("Effects/surface-lights");
105     }
106 
107     SGPropertyNode* params = makeChild(effectProp, "parameters");
108     params->getNode("size",true)->setValue(size);
109     params->getNode("attenuation",true)->getNode("x", true)->setValue(attenuation.x());
110     params->getNode("attenuation",true)->getNode("y", true)->setValue(attenuation.y());
111     params->getNode("attenuation",true)->getNode("z", true)->setValue(attenuation.z());
112     params->getNode("min-size",true)->setValue(minSize);
113     params->getNode("max-size",true)->setValue(maxSize);
114     params->getNode("cull-face",true)->setValue(directional ? "back" : "off");
115     params->getNode("light-directional",true)->setValue(directional);
116 
117     effect = makeEffect(effectProp, true, options);
118 
119     if (eitr == effectMap.end())
120         effectMap.insert(std::make_pair(pointParams, effect));
121     else
122         eitr->second = effect; // update existing, but empty observer
123     return effect.release();
124 }
125 
126 
127 osg::Drawable*
getLightDrawable(const SGLightBin::Light & light)128 SGLightFactory::getLightDrawable(const SGLightBin::Light& light)
129 {
130   osg::Vec3Array* vertices = new osg::Vec3Array;
131   osg::Vec4Array* colors = new osg::Vec4Array;
132 
133   vertices->push_back(toOsg(light.position));
134   colors->push_back(toOsg(light.color));
135 
136   osg::Geometry* geometry = new osg::Geometry;
137   geometry->setDataVariance(osg::Object::STATIC);
138   geometry->setVertexArray(vertices);
139   geometry->setNormalBinding(osg::Geometry::BIND_OFF);
140   geometry->setColorArray(colors);
141   geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
142 
143   // Enlarge the bounding box to avoid such light nodes being victim to
144   // small feature culling.
145   geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
146 
147   osg::DrawArrays* drawArrays;
148   drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
149                                    0, vertices->size());
150   geometry->addPrimitiveSet(drawArrays);
151   return geometry;
152 }
153 
154 osg::Drawable*
getLightDrawable(const SGDirectionalLightBin::Light & light,bool useTriangles)155 SGLightFactory::getLightDrawable(const SGDirectionalLightBin::Light& light, bool useTriangles)
156 {
157   osg::Vec3Array* vertices = new osg::Vec3Array;
158   osg::Vec4Array* colors = new osg::Vec4Array;
159 
160   if (useTriangles) {
161     SGVec4f visibleColor(light.color);
162     SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
163                            visibleColor[2], 0);
164     SGVec3f normal = normalize(light.normal);
165     SGVec3f perp1 = perpendicular(normal);
166     SGVec3f perp2 = cross(normal, perp1);
167     SGVec3f position = light.position;
168     vertices->push_back(toOsg(position));
169     vertices->push_back(toOsg(position + perp1));
170     vertices->push_back(toOsg(position + perp2));
171     colors->push_back(toOsg(visibleColor));
172     colors->push_back(toOsg(invisibleColor));
173     colors->push_back(toOsg(invisibleColor));
174 
175     osg::Geometry* geometry = new osg::Geometry;
176     geometry->setDataVariance(osg::Object::STATIC);
177     geometry->setVertexArray(vertices);
178     geometry->setNormalBinding(osg::Geometry::BIND_OFF);
179     geometry->setColorArray(colors);
180     geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
181 
182     // Enlarge the bounding box to avoid such light nodes being victim to
183     // small feature culling.
184     geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
185 
186     osg::DrawArrays* drawArrays;
187     drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
188                                      0, vertices->size());
189     geometry->addPrimitiveSet(drawArrays);
190     return geometry;
191   } else {
192     // Workaround for driver issue where point sprites cannot be triangles,
193     // (which we use to provide a sensible normal).  So fall back to a simple
194     // non-directional point sprite.
195     const SGLightBin::Light nonDirectionalLight = SGLightBin::Light(light.position, light.color);
196     return getLightDrawable(nonDirectionalLight);
197   }
198 }
199 
200 namespace
201 {
202   ref_ptr<StateSet> simpleLightSS;
203 }
204 osg::Drawable*
getLights(const SGLightBin & lights,unsigned inc,float alphaOff)205 SGLightFactory::getLights(const SGLightBin& lights, unsigned inc, float alphaOff)
206 {
207   if (lights.getNumLights() <= 0)
208     return 0;
209 
210   osg::Vec3Array* vertices = new osg::Vec3Array;
211   osg::Vec4Array* colors = new osg::Vec4Array;
212 
213   for (unsigned i = 0; i < lights.getNumLights(); i += inc) {
214     vertices->push_back(toOsg(lights.getLight(i).position));
215     SGVec4f color = lights.getLight(i).color;
216     color[3] = SGMiscf::max(0, SGMiscf::min(1, color[3] + alphaOff));
217     colors->push_back(toOsg(color));
218   }
219 
220   osg::Geometry* geometry = new osg::Geometry;
221   geometry->setDataVariance(osg::Object::STATIC);
222   geometry->setVertexArray(vertices);
223   geometry->setNormalBinding(osg::Geometry::BIND_OFF);
224   geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX);
225 
226   osg::DrawArrays* drawArrays;
227   drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
228                                    0, vertices->size());
229   geometry->addPrimitiveSet(drawArrays);
230 
231   {
232     ScopedLock<Mutex> lock(lightMutex);
233     if (!simpleLightSS.valid()) {
234       StateAttributeFactory *attrFact = StateAttributeFactory::instance();
235       simpleLightSS = new StateSet;
236       simpleLightSS->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
237       simpleLightSS->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
238       simpleLightSS->setAttributeAndModes(attrFact->getStandardBlendFunc());
239       simpleLightSS->setAttributeAndModes(attrFact->getStandardAlphaFunc());
240     }
241   }
242   geometry->setStateSet(simpleLightSS.get());
243   return geometry;
244 }
245 
246 
247 osg::Drawable*
getLights(const SGDirectionalLightBin & lights)248 SGLightFactory::getLights(const SGDirectionalLightBin& lights)
249 {
250   if (lights.getNumLights() <= 0)
251     return 0;
252 
253   osg::Vec3Array* vertices = new osg::Vec3Array;
254   osg::Vec4Array* colors = new osg::Vec4Array;
255 
256   for (unsigned i = 0; i < lights.getNumLights(); ++i) {
257 	  SGVec4f visibleColor(lights.getLight(i).color);
258 	  SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
259 	                         visibleColor[2], 0);
260 	  SGVec3f normal = normalize(lights.getLight(i).normal);
261 	  SGVec3f perp1 = perpendicular(normal);
262 	  SGVec3f perp2 = cross(normal, perp1);
263 	  SGVec3f position = lights.getLight(i).position;
264 	  vertices->push_back(toOsg(position));
265 	  vertices->push_back(toOsg(position + perp1));
266 	  vertices->push_back(toOsg(position + perp2));
267 	  colors->push_back(toOsg(visibleColor));
268 	  colors->push_back(toOsg(invisibleColor));
269 	  colors->push_back(toOsg(invisibleColor));
270   }
271 
272   osg::Geometry* geometry = new osg::Geometry;
273   geometry->setDataVariance(osg::Object::STATIC);
274   geometry->setVertexArray(vertices);
275   geometry->setNormalBinding(osg::Geometry::BIND_OFF);
276   geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX);
277 
278 
279   //osg::StateSet* stateSet = geometry->getOrCreateStateSet();
280   //stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
281   //stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
282 
283 
284   static SGSceneFeatures* sceneFeatures = SGSceneFeatures::instance();
285   bool useTriangles = sceneFeatures->getEnableTriangleDirectionalLights();
286 
287   osg::DrawArrays* drawArrays;
288   if (useTriangles)
289     drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
290                                      0, vertices->size());
291   else
292    drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
293                                      0, vertices->size());
294 
295   geometry->addPrimitiveSet(drawArrays);
296   return geometry;
297 }
298 
299 static SGVasiDrawable*
buildVasi(const SGDirectionalLightBin & lights,const SGVec3f & up,const SGVec4f & red,const SGVec4f & white)300 buildVasi(const SGDirectionalLightBin& lights, const SGVec3f& up,
301        const SGVec4f& red, const SGVec4f& white)
302 {
303   unsigned count = lights.getNumLights();
304   if ( count == 4 ) {
305     SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
306 
307     // PAPI configuration
308     // papi D
309     drawable->addLight(lights.getLight(0).position,
310                        lights.getLight(0).normal, up, 3.5);
311     // papi C
312     drawable->addLight(lights.getLight(1).position,
313                        lights.getLight(1).normal, up, 3.167);
314     // papi B
315     drawable->addLight(lights.getLight(2).position,
316                        lights.getLight(2).normal, up, 2.833);
317     // papi A
318     drawable->addLight(lights.getLight(3).position,
319                        lights.getLight(3).normal, up, 2.5);
320     return drawable;
321 
322   } else if (count == 6) {
323     SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
324 
325     // probably vasi, first 3 are downwind bar (2.5 deg)
326     for (unsigned i = 0; i < 3; ++i)
327       drawable->addLight(lights.getLight(i).position,
328                          lights.getLight(i).normal, up, 2.5);
329     // last 3 are upwind bar (3.0 deg)
330     for (unsigned i = 3; i < 6; ++i)
331       drawable->addLight(lights.getLight(i).position,
332                          lights.getLight(i).normal, up, 3.0);
333     return drawable;
334 
335   } else if (count == 12) {
336     SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
337 
338     // probably vasi, first 6 are downwind bar (2.5 deg)
339     for (unsigned i = 0; i < 6; ++i)
340       drawable->addLight(lights.getLight(i).position,
341                          lights.getLight(i).normal, up, 2.5);
342     // last 6 are upwind bar (3.0 deg)
343     for (unsigned i = 6; i < 12; ++i)
344       drawable->addLight(lights.getLight(i).position,
345                          lights.getLight(i).normal, up, 3.0);
346     return drawable;
347 
348   } else {
349     // fail safe
350     SG_LOG(SG_TERRAIN, SG_ALERT,
351            "unknown vasi/papi configuration, count = " << count);
352     return 0;
353   }
354 }
355 
356 osg::Drawable*
getVasi(const SGVec3f & up,const SGDirectionalLightBin & lights,const SGVec4f & red,const SGVec4f & white)357 SGLightFactory::getVasi(const SGVec3f& up, const SGDirectionalLightBin& lights,
358                         const SGVec4f& red, const SGVec4f& white)
359 {
360   SGVasiDrawable* drawable = buildVasi(lights, up, red, white);
361   if (!drawable)
362     return 0;
363 
364   osg::StateSet* stateSet = drawable->getOrCreateStateSet();
365   stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
366   stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
367 
368   osg::BlendFunc* blendFunc = new osg::BlendFunc;
369   stateSet->setAttribute(blendFunc);
370   stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
371 
372   osg::AlphaFunc* alphaFunc;
373   alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
374   stateSet->setAttribute(alphaFunc);
375   stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
376 
377   return drawable;
378 }
379 
380 osg::Node*
getSequenced(const SGDirectionalLightBin & lights,const SGReaderWriterOptions * options)381 SGLightFactory::getSequenced(const SGDirectionalLightBin& lights, const SGReaderWriterOptions* options)
382 {
383   if (lights.getNumLights() <= 0)
384     return 0;
385 
386   static SGSceneFeatures* sceneFeatures = SGSceneFeatures::instance();
387   bool useTriangles = sceneFeatures->getEnableTriangleDirectionalLights();
388 
389   // generate a repeatable random seed
390   sg_srandom(unsigned(lights.getLight(0).position[0]));
391   float flashTime = 0.065 + 0.003 * sg_random();
392   osg::Sequence* sequence = new osg::Sequence;
393   sequence->setDefaultTime(flashTime);
394   Effect* effect = getLightEffect(24.0f, osg::Vec3(1.0, 0.0001, 0.000001),
395                                   1.0f, 24.0f, true, options);
396   for (int i = lights.getNumLights() - 1; 0 <= i; --i) {
397     EffectGeode* egeode = new EffectGeode;
398     egeode->setEffect(effect);
399     egeode->addDrawable(getLightDrawable(lights.getLight(i), useTriangles));
400     sequence->addChild(egeode, flashTime);
401   }
402   sequence->addChild(new osg::Group, 1.9 + (0.1 * sg_random()) - (lights.getNumLights() * flashTime));
403   sequence->setInterval(osg::Sequence::LOOP, 0, -1);
404   sequence->setDuration(1.0f, -1);
405   sequence->setMode(osg::Sequence::START);
406   sequence->setSync(true);
407   return sequence;
408 }
409 
410 osg::Node*
getReil(const SGDirectionalLightBin & lights,const SGReaderWriterOptions * options)411 SGLightFactory::getReil(const SGDirectionalLightBin& lights, const SGReaderWriterOptions* options)
412 {
413   if (lights.getNumLights() <= 0)
414     return 0;
415 
416   static SGSceneFeatures* sceneFeatures = SGSceneFeatures::instance();
417   bool useTriangles = sceneFeatures->getEnableTriangleDirectionalLights();
418 
419   // generate a repeatable random seed
420   sg_srandom(unsigned(lights.getLight(0).position[0]));
421   float flashTime = 0.065 + 0.003 * sg_random();
422   osg::Sequence* sequence = new osg::Sequence;
423   sequence->setDefaultTime(flashTime);
424   Effect* effect = getLightEffect(24.0f, osg::Vec3(1.0, 0.0001, 0.000001),
425                                   1.0f, 24.0f, true, options);
426   EffectGeode* egeode = new EffectGeode;
427   egeode->setEffect(effect);
428 
429   for (int i = lights.getNumLights() - 1; 0 <= i; --i) {
430     egeode->addDrawable(getLightDrawable(lights.getLight(i), useTriangles));
431   }
432   sequence->addChild(egeode, flashTime);
433   sequence->addChild(new osg::Group, 1.9 + 0.1 * sg_random() - flashTime);
434   sequence->setInterval(osg::Sequence::LOOP, 0, -1);
435   sequence->setDuration(1.0f, -1);
436   sequence->setMode(osg::Sequence::START);
437   sequence->setSync(true);
438   return sequence;
439 }
440 
441 osg::Node*
getOdal(const SGLightBin & lights,const SGReaderWriterOptions * options)442 SGLightFactory::getOdal(const SGLightBin& lights, const SGReaderWriterOptions* options)
443 {
444   if (lights.getNumLights() < 2)
445     return 0;
446 
447   // generate a repeatable random seed
448   sg_srandom(unsigned(lights.getLight(0).position[0]));
449   float flashTime = 0.065 + 0.003 * sg_random();
450   osg::Sequence* sequence = new osg::Sequence;
451   sequence->setDefaultTime(flashTime);
452   Effect* effect = getLightEffect(20.0f, osg::Vec3(1.0, 0.0001, 0.000001),
453                                   1.0f, 20.0f, false, options);
454   // centerline lights
455   for (int i = lights.getNumLights() - 1; i >= 2; i--) {
456     EffectGeode* egeode = new EffectGeode;
457     egeode->setEffect(effect);
458     egeode->addDrawable(getLightDrawable(lights.getLight(i)));
459     sequence->addChild(egeode, flashTime);
460   }
461   // add extra empty group for a break
462   sequence->addChild(new osg::Group, 4 * flashTime);
463   // runway end lights
464   EffectGeode* egeode = new EffectGeode;
465   egeode->setEffect(effect);
466   for (unsigned i = 0; i < 2; ++i) {
467     egeode->addDrawable(getLightDrawable(lights.getLight(i)));
468   }
469   sequence->addChild(egeode, flashTime);
470 
471   // add an extra empty group for a break
472   sequence->addChild(new osg::Group, 1.9 + (0.1 * sg_random()) - ((lights.getNumLights() + 2) * flashTime));
473   sequence->setInterval(osg::Sequence::LOOP, 0, -1);
474   sequence->setDuration(1.0f, -1);
475   sequence->setMode(osg::Sequence::START);
476   sequence->setSync(true);
477 
478   return sequence;
479 }
480 
481 // Blinking hold short line lights
482 osg::Node*
getHoldShort(const SGDirectionalLightBin & lights,const SGReaderWriterOptions * options)483 SGLightFactory::getHoldShort(const SGDirectionalLightBin& lights, const SGReaderWriterOptions* options)
484 {
485   if (lights.getNumLights() < 2)
486     return 0;
487 
488   static SGSceneFeatures* sceneFeatures = SGSceneFeatures::instance();
489   bool useTriangles = sceneFeatures->getEnableTriangleDirectionalLights();
490 
491   sg_srandom(unsigned(lights.getLight(0).position[0]));
492   float flashTime = 0.9 + 0.2 * sg_random();
493   osg::Sequence* sequence = new osg::Sequence;
494 
495   // start with lights off
496   sequence->addChild(new osg::Group, 0.2);
497   // ...and increase the lights in steps
498   for (int i = 0; i < 5; i++) {
499       Effect* effect = getLightEffect(12.0f + i, osg::Vec3(1, 0.001, 0.0002),
500                                       1.0f, 12.0f + i, true, options);
501       EffectGeode* egeode = new EffectGeode;
502       egeode->setEffect(effect);
503       for (unsigned int j = 0; j < lights.getNumLights(); ++j) {
504           egeode->addDrawable(getLightDrawable(lights.getLight(j), useTriangles));
505       }
506       sequence->addChild(egeode, (i==4) ? flashTime : 0.1);
507   }
508   sequence->setInterval(osg::Sequence::SWING, 0, -1);
509   sequence->setDuration(1.0f, -1);
510   sequence->setMode(osg::Sequence::START);
511 
512   return sequence;
513 }
514 
515 // Alternating runway guard lights ("wig-wag")
516 osg::Node*
getGuard(const SGDirectionalLightBin & lights,const SGReaderWriterOptions * options)517 SGLightFactory::getGuard(const SGDirectionalLightBin& lights, const SGReaderWriterOptions* options)
518 {
519   if (lights.getNumLights() < 2)
520     return 0;
521 
522   static SGSceneFeatures* sceneFeatures = SGSceneFeatures::instance();
523   bool useTriangles = sceneFeatures->getEnableTriangleDirectionalLights();
524 
525   // generate a repeatable random seed
526   sg_srandom(unsigned(lights.getLight(0).position[0]));
527   float flashTime = 0.9 + 0.2 * sg_random();
528   osg::Sequence* sequence = new osg::Sequence;
529   sequence->setDefaultTime(flashTime);
530   Effect* effect = getLightEffect(16.0f, osg::Vec3(1.0, 0.001, 0.0002),
531                                   1.0f, 16.0f, true, options);
532   for (unsigned int i = 0; i < lights.getNumLights(); ++i) {
533     EffectGeode* egeode = new EffectGeode;
534     egeode->setEffect(effect);
535     egeode->addDrawable(getLightDrawable(lights.getLight(i), useTriangles));
536     sequence->addChild(egeode, flashTime);
537   }
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   return sequence;
543 }
544