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