1 // pt_lights.cxx -- build a 'directional' light on the fly
3 // Written by Curtis Olson, started March 2002.
5 // Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt
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.
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.
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.
24 # include <simgear_config.h>
27 #include "pt_lights.hxx"
30 #include <boost/tuple/tuple_comparison.hpp>
33 #include <osg/Geometry>
34 #include <osg/CullFace>
36 #include <osg/MatrixTransform>
37 #include <osg/NodeCallback>
38 #include <osg/NodeVisitor>
39 #include <osg/Texture2D>
40 #include <osg/AlphaFunc>
41 #include <osg/BlendFunc>
43 #include <osg/Sequence>
45 #include <osg/FragmentProgram>
46 #include <osg/VertexProgram>
48 #include <osg/Material>
50 #include <osg/StateSet>
52 #include <osgUtil/CullVisitor>
54 #include <OpenThreads/Mutex>
55 #include <OpenThreads/ScopedLock>
57 #include <simgear/math/sg_random.h>
58 #include <simgear/debug/logstream.hxx>
59 #include <simgear/scene/util/RenderConstants.hxx>
60 #include <simgear/scene/util/SGEnlargeBoundingBox.hxx>
61 #include <simgear/scene/util/OsgMath.hxx>
62 #include <simgear/scene/util/StateAttributeFactory.hxx>
64 #include <simgear/scene/material/Effect.hxx>
65 #include <simgear/scene/material/EffectGeode.hxx>
66 #include <simgear/scene/material/Technique.hxx>
67 #include <simgear/scene/material/Pass.hxx>
69 #include "SGVasiDrawable.hxx"
71 using OpenThreads::Mutex;
72 using OpenThreads::ScopedLock;
75 using namespace simgear;
77 static Mutex lightMutex;
81 typedef boost::tuple<float, osg::Vec3, float, float, bool> PointParams;
82 typedef std::map<PointParams, observer_ptr<Effect> > EffectMap;
87 Effect* getLightEffect(float size, const Vec3& attenuation,
88 float minSize, float maxSize, bool directional,
89 const SGReaderWriterOptions* options)
91 PointParams pointParams(size, attenuation, minSize, maxSize, directional);
92 ScopedLock<Mutex> lock(lightMutex);
93 ref_ptr<Effect> effect;
94 EffectMap::iterator eitr = effectMap.find(pointParams);
95 if (eitr != effectMap.end())
97 if (eitr->second.lock(effect))
98 return effect.release();
101 SGPropertyNode_ptr effectProp = new SGPropertyNode;
103 makeChild(effectProp, "inherits-from")->setStringValue("Effects/surface-lights-directional");
105 makeChild(effectProp, "inherits-from")->setStringValue("Effects/surface-lights");
108 SGPropertyNode* params = makeChild(effectProp, "parameters");
109 params->getNode("size",true)->setValue(size);
110 params->getNode("attenuation",true)->getNode("x", true)->setValue(attenuation.x());
111 params->getNode("attenuation",true)->getNode("y", true)->setValue(attenuation.y());
112 params->getNode("attenuation",true)->getNode("z", true)->setValue(attenuation.z());
113 params->getNode("min-size",true)->setValue(minSize);
114 params->getNode("max-size",true)->setValue(maxSize);
115 params->getNode("cull-face",true)->setValue(directional ? "back" : "off");
116 params->getNode("light-directional",true)->setValue(directional);
118 effect = makeEffect(effectProp, true, options);
120 if (eitr == effectMap.end())
121 effectMap.insert(std::make_pair(pointParams, effect));
123 eitr->second = effect; // update existing, but empty observer
124 return effect.release();
129 SGLightFactory::getLightDrawable(const SGLightBin::Light& light)
131 osg::Vec3Array* vertices = new osg::Vec3Array;
132 osg::Vec4Array* colors = new osg::Vec4Array;
134 vertices->push_back(toOsg(light.position));
135 colors->push_back(toOsg(light.color));
137 osg::Geometry* geometry = new osg::Geometry;
138 geometry->setDataVariance(osg::Object::STATIC);
139 geometry->setVertexArray(vertices);
140 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
141 geometry->setColorArray(colors);
142 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
144 // Enlarge the bounding box to avoid such light nodes being victim to
145 // small feature culling.
146 geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
148 osg::DrawArrays* drawArrays;
149 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
150 0, vertices->size());
151 geometry->addPrimitiveSet(drawArrays);
156 SGLightFactory::getLightDrawable(const SGDirectionalLightBin::Light& light)
158 osg::Vec3Array* vertices = new osg::Vec3Array;
159 osg::Vec4Array* colors = new osg::Vec4Array;
161 SGVec4f visibleColor(light.color);
162 SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
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));
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);
182 // Enlarge the bounding box to avoid such light nodes being victim to
183 // small feature culling.
184 geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
186 osg::DrawArrays* drawArrays;
187 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
188 0, vertices->size());
189 geometry->addPrimitiveSet(drawArrays);
195 ref_ptr<StateSet> simpleLightSS;
198 SGLightFactory::getLights(const SGLightBin& lights, unsigned inc, float alphaOff)
200 if (lights.getNumLights() <= 0)
203 osg::Vec3Array* vertices = new osg::Vec3Array;
204 osg::Vec4Array* colors = new osg::Vec4Array;
206 for (unsigned i = 0; i < lights.getNumLights(); i += inc) {
207 vertices->push_back(toOsg(lights.getLight(i).position));
208 SGVec4f color = lights.getLight(i).color;
209 color[3] = SGMiscf::max(0, SGMiscf::min(1, color[3] + alphaOff));
210 colors->push_back(toOsg(color));
213 osg::Geometry* geometry = new osg::Geometry;
214 geometry->setDataVariance(osg::Object::STATIC);
215 geometry->setVertexArray(vertices);
216 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
217 geometry->setColorArray(colors);
218 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
220 osg::DrawArrays* drawArrays;
221 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
222 0, vertices->size());
223 geometry->addPrimitiveSet(drawArrays);
226 ScopedLock<Mutex> lock(lightMutex);
227 if (!simpleLightSS.valid()) {
228 StateAttributeFactory *attrFact = StateAttributeFactory::instance();
229 simpleLightSS = new StateSet;
230 simpleLightSS->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
231 simpleLightSS->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
232 simpleLightSS->setAttributeAndModes(attrFact->getStandardBlendFunc());
233 simpleLightSS->setAttributeAndModes(attrFact->getStandardAlphaFunc());
236 geometry->setStateSet(simpleLightSS.get());
242 SGLightFactory::getLights(const SGDirectionalLightBin& lights)
244 if (lights.getNumLights() <= 0)
247 osg::Vec3Array* vertices = new osg::Vec3Array;
248 osg::Vec4Array* colors = new osg::Vec4Array;
250 for (unsigned i = 0; i < lights.getNumLights(); ++i) {
251 SGVec4f visibleColor(lights.getLight(i).color);
252 SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
254 SGVec3f normal = normalize(lights.getLight(i).normal);
255 SGVec3f perp1 = perpendicular(normal);
256 SGVec3f perp2 = cross(normal, perp1);
257 SGVec3f position = lights.getLight(i).position;
258 vertices->push_back(toOsg(position));
259 vertices->push_back(toOsg(position + perp1));
260 vertices->push_back(toOsg(position + perp2));
261 colors->push_back(toOsg(visibleColor));
262 colors->push_back(toOsg(invisibleColor));
263 colors->push_back(toOsg(invisibleColor));
266 osg::Geometry* geometry = new osg::Geometry;
267 geometry->setDataVariance(osg::Object::STATIC);
268 geometry->setVertexArray(vertices);
269 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
270 geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX);
273 //osg::StateSet* stateSet = geometry->getOrCreateStateSet();
274 //stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
275 //stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
277 osg::DrawArrays* drawArrays;
278 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
279 0, vertices->size());
280 geometry->addPrimitiveSet(drawArrays);
284 static SGVasiDrawable*
285 buildVasi(const SGDirectionalLightBin& lights, const SGVec3f& up,
286 const SGVec4f& red, const SGVec4f& white)
288 unsigned count = lights.getNumLights();
290 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
292 // PAPI configuration
294 drawable->addLight(lights.getLight(0).position,
295 lights.getLight(0).normal, up, 3.5);
297 drawable->addLight(lights.getLight(1).position,
298 lights.getLight(1).normal, up, 3.167);
300 drawable->addLight(lights.getLight(2).position,
301 lights.getLight(2).normal, up, 2.833);
303 drawable->addLight(lights.getLight(3).position,
304 lights.getLight(3).normal, up, 2.5);
307 } else if (count == 6) {
308 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
310 // probably vasi, first 3 are downwind bar (2.5 deg)
311 for (unsigned i = 0; i < 3; ++i)
312 drawable->addLight(lights.getLight(i).position,
313 lights.getLight(i).normal, up, 2.5);
314 // last 3 are upwind bar (3.0 deg)
315 for (unsigned i = 3; i < 6; ++i)
316 drawable->addLight(lights.getLight(i).position,
317 lights.getLight(i).normal, up, 3.0);
320 } else if (count == 12) {
321 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
323 // probably vasi, first 6 are downwind bar (2.5 deg)
324 for (unsigned i = 0; i < 6; ++i)
325 drawable->addLight(lights.getLight(i).position,
326 lights.getLight(i).normal, up, 2.5);
327 // last 6 are upwind bar (3.0 deg)
328 for (unsigned i = 6; i < 12; ++i)
329 drawable->addLight(lights.getLight(i).position,
330 lights.getLight(i).normal, up, 3.0);
335 SG_LOG(SG_TERRAIN, SG_ALERT,
336 "unknown vasi/papi configuration, count = " << count);
342 SGLightFactory::getVasi(const SGVec3f& up, const SGDirectionalLightBin& lights,
343 const SGVec4f& red, const SGVec4f& white)
345 SGVasiDrawable* drawable = buildVasi(lights, up, red, white);
349 osg::StateSet* stateSet = drawable->getOrCreateStateSet();
350 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
351 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
353 osg::BlendFunc* blendFunc = new osg::BlendFunc;
354 stateSet->setAttribute(blendFunc);
355 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
357 osg::AlphaFunc* alphaFunc;
358 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
359 stateSet->setAttribute(alphaFunc);
360 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
366 SGLightFactory::getSequenced(const SGDirectionalLightBin& lights, const SGReaderWriterOptions* options)
368 if (lights.getNumLights() <= 0)
371 // generate a repeatable random seed
372 sg_srandom(unsigned(lights.getLight(0).position[0]));
373 float flashTime = 2e-2 + 5e-3*sg_random();
374 osg::Sequence* sequence = new osg::Sequence;
375 sequence->setDefaultTime(flashTime);
376 Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.0001, 0.00000001),
377 6.0f, 10.0f, true, options);
378 for (int i = lights.getNumLights() - 1; 0 <= i; --i) {
379 EffectGeode* egeode = new EffectGeode;
380 egeode->setEffect(effect);
381 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
382 sequence->addChild(egeode, flashTime);
384 sequence->addChild(new osg::Group, 1 + 1e-1*sg_random());
385 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
386 sequence->setDuration(1.0f, -1);
387 sequence->setMode(osg::Sequence::START);
388 sequence->setSync(true);
393 SGLightFactory::getOdal(const SGLightBin& lights, const SGReaderWriterOptions* options)
395 if (lights.getNumLights() < 2)
398 // generate a repeatable random seed
399 sg_srandom(unsigned(lights.getLight(0).position[0]));
400 float flashTime = 2e-2 + 5e-3*sg_random();
401 osg::Sequence* sequence = new osg::Sequence;
402 sequence->setDefaultTime(flashTime);
403 Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.0001, 0.00000001),
404 6.0, 10.0, false, options);
406 for (int i = lights.getNumLights(); i > 1; --i) {
407 EffectGeode* egeode = new EffectGeode;
408 egeode->setEffect(effect);
409 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
410 sequence->addChild(egeode, flashTime);
413 osg::Group* group = new osg::Group;
414 for (unsigned i = 0; i < 2; ++i) {
415 EffectGeode* egeode = new EffectGeode;
416 egeode->setEffect(effect);
417 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
418 group->addChild(egeode);
420 sequence->addChild(group, flashTime);
422 // add an extra empty group for a break
423 sequence->addChild(new osg::Group, 2 + 1e-1*sg_random());
424 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
425 sequence->setDuration(1.0f, -1);
426 sequence->setMode(osg::Sequence::START);
427 sequence->setSync(true);
432 // Blinking hold short line lights
434 SGLightFactory::getHoldShort(const SGDirectionalLightBin& lights, const SGReaderWriterOptions* options)
436 if (lights.getNumLights() < 2)
439 sg_srandom(unsigned(lights.getLight(0).position[0]));
440 float flashTime = 1 + 0.1 * sg_random();
441 osg::Sequence* sequence = new osg::Sequence;
443 // start with lights off
444 sequence->addChild(new osg::Group, flashTime);
445 // ...and increase the lights in steps
446 for (int i = 2; i < 7; i+=2) {
447 Effect* effect = getLightEffect(i, osg::Vec3(1, 0.001, 0.000002),
448 0.0f, i, true, options);
449 EffectGeode* egeode = new EffectGeode;
450 for (unsigned int j = 0; j < lights.getNumLights(); ++j) {
451 egeode->addDrawable(getLightDrawable(lights.getLight(j)));
452 egeode->setEffect(effect);
454 sequence->addChild(egeode, (i==6) ? flashTime : 0.1);
457 sequence->setInterval(osg::Sequence::SWING, 0, -1);
458 sequence->setDuration(1.0f, -1);
459 sequence->setMode(osg::Sequence::START);
464 // Alternating runway guard lights ("wig-wag")
466 SGLightFactory::getGuard(const SGDirectionalLightBin& lights, const SGReaderWriterOptions* options)
468 if (lights.getNumLights() < 2)
471 // generate a repeatable random seed
472 sg_srandom(unsigned(lights.getLight(0).position[0]));
473 float flashTime = 1.0f + 1*sg_random();
474 osg::Sequence* sequence = new osg::Sequence;
475 sequence->setDefaultTime(flashTime);
476 Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.001, 0.000002),
477 0.0f, 8.0f, true, options);
478 for (unsigned int i = 0; i < lights.getNumLights(); ++i) {
479 EffectGeode* egeode = new EffectGeode;
480 egeode->setEffect(effect);
481 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
482 sequence->addChild(egeode, flashTime);
484 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
485 sequence->setDuration(1.0f, -1);
486 sequence->setMode(osg::Sequence::START);
487 sequence->setSync(true);