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;
102 makeChild(effectProp, "inherits-from")->setStringValue("Effects/surface-lights");
104 SGPropertyNode* params = makeChild(effectProp, "parameters");
105 params->getNode("size",true)->setValue(size);
106 params->getNode("attenuation",true)->getNode("x", true)->setValue(attenuation.x());
107 params->getNode("attenuation",true)->getNode("y", true)->setValue(attenuation.y());
108 params->getNode("attenuation",true)->getNode("z", true)->setValue(attenuation.z());
109 params->getNode("min-size",true)->setValue(minSize);
110 params->getNode("max-size",true)->setValue(maxSize);
111 params->getNode("cull-face",true)->setValue(directional ? "back" : "off");
113 effect = makeEffect(effectProp, true, options);
115 if (eitr == effectMap.end())
116 effectMap.insert(std::make_pair(pointParams, effect));
118 eitr->second = effect; // update existing, but empty observer
119 return effect.release();
124 SGLightFactory::getLightDrawable(const SGLightBin::Light& light)
126 osg::Vec3Array* vertices = new osg::Vec3Array;
127 osg::Vec4Array* colors = new osg::Vec4Array;
129 vertices->push_back(toOsg(light.position));
130 colors->push_back(toOsg(light.color));
132 osg::Geometry* geometry = new osg::Geometry;
133 geometry->setDataVariance(osg::Object::STATIC);
134 geometry->setVertexArray(vertices);
135 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
136 geometry->setColorArray(colors);
137 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
139 // Enlarge the bounding box to avoid such light nodes being victim to
140 // small feature culling.
141 geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
143 osg::DrawArrays* drawArrays;
144 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
145 0, vertices->size());
146 geometry->addPrimitiveSet(drawArrays);
151 SGLightFactory::getLightDrawable(const SGDirectionalLightBin::Light& light)
153 osg::Vec3Array* vertices = new osg::Vec3Array;
154 osg::Vec4Array* colors = new osg::Vec4Array;
155 osg::Vec3Array* normals = new osg::Vec3Array;
157 vertices->push_back(toOsg(light.position));
158 colors->push_back(toOsg(light.color));
159 normals->push_back(toOsg(normalize(light.normal)));
161 osg::Geometry* geometry = new osg::Geometry;
162 geometry->setDataVariance(osg::Object::STATIC);
163 geometry->setVertexArray(vertices);
164 geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX);
165 geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX);
166 // Enlarge the bounding box to avoid such light nodes being victim to
167 // small feature culling.
168 geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
170 osg::DrawArrays* drawArrays;
171 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
172 0, vertices->size());
173 geometry->addPrimitiveSet(drawArrays);
179 ref_ptr<StateSet> simpleLightSS;
182 SGLightFactory::getLights(const SGLightBin& lights, unsigned inc, float alphaOff)
184 if (lights.getNumLights() <= 0)
187 osg::Vec3Array* vertices = new osg::Vec3Array;
188 osg::Vec4Array* colors = new osg::Vec4Array;
190 for (unsigned i = 0; i < lights.getNumLights(); i += inc) {
191 vertices->push_back(toOsg(lights.getLight(i).position));
192 SGVec4f color = lights.getLight(i).color;
193 color[3] = SGMiscf::max(0, SGMiscf::min(1, color[3] + alphaOff));
194 colors->push_back(toOsg(color));
197 osg::Geometry* geometry = new osg::Geometry;
198 geometry->setDataVariance(osg::Object::STATIC);
199 geometry->setVertexArray(vertices);
200 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
201 geometry->setColorArray(colors);
202 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
204 osg::DrawArrays* drawArrays;
205 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
206 0, vertices->size());
207 geometry->addPrimitiveSet(drawArrays);
210 ScopedLock<Mutex> lock(lightMutex);
211 if (!simpleLightSS.valid()) {
212 StateAttributeFactory *attrFact = StateAttributeFactory::instance();
213 simpleLightSS = new StateSet;
214 simpleLightSS->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
215 simpleLightSS->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
216 simpleLightSS->setAttributeAndModes(attrFact->getStandardBlendFunc());
217 simpleLightSS->setAttributeAndModes(attrFact->getStandardAlphaFunc());
220 geometry->setStateSet(simpleLightSS.get());
226 SGLightFactory::getLights(const SGDirectionalLightBin& lights)
228 if (lights.getNumLights() <= 0)
231 osg::Vec3Array* vertices = new osg::Vec3Array;
232 osg::Vec4Array* colors = new osg::Vec4Array;
233 osg::Vec3Array* normals = new osg::Vec3Array;
235 for (unsigned i = 0; i < lights.getNumLights(); ++i) {
236 vertices->push_back(toOsg(lights.getLight(i).position));
237 colors->push_back(toOsg(lights.getLight(i).color));
238 normals->push_back(toOsg(normalize(lights.getLight(i).normal)));
241 osg::Geometry* geometry = new osg::Geometry;
242 geometry->setDataVariance(osg::Object::STATIC);
243 geometry->setVertexArray(vertices);
244 geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX);
245 geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX);
247 osg::DrawArrays* drawArrays;
248 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
249 0, vertices->size());
250 geometry->addPrimitiveSet(drawArrays);
254 static SGVasiDrawable*
255 buildVasi(const SGDirectionalLightBin& lights, const SGVec3f& up,
256 const SGVec4f& red, const SGVec4f& white)
258 unsigned count = lights.getNumLights();
260 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
262 // PAPI configuration
264 drawable->addLight(lights.getLight(0).position,
265 lights.getLight(0).normal, up, 3.5);
267 drawable->addLight(lights.getLight(1).position,
268 lights.getLight(1).normal, up, 3.167);
270 drawable->addLight(lights.getLight(2).position,
271 lights.getLight(2).normal, up, 2.833);
273 drawable->addLight(lights.getLight(3).position,
274 lights.getLight(3).normal, up, 2.5);
277 } else if (count == 6) {
278 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
280 // probably vasi, first 3 are downwind bar (2.5 deg)
281 for (unsigned i = 0; i < 3; ++i)
282 drawable->addLight(lights.getLight(i).position,
283 lights.getLight(i).normal, up, 2.5);
284 // last 3 are upwind bar (3.0 deg)
285 for (unsigned i = 3; i < 6; ++i)
286 drawable->addLight(lights.getLight(i).position,
287 lights.getLight(i).normal, up, 3.0);
290 } else if (count == 12) {
291 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
293 // probably vasi, first 6 are downwind bar (2.5 deg)
294 for (unsigned i = 0; i < 6; ++i)
295 drawable->addLight(lights.getLight(i).position,
296 lights.getLight(i).normal, up, 2.5);
297 // last 6 are upwind bar (3.0 deg)
298 for (unsigned i = 6; i < 12; ++i)
299 drawable->addLight(lights.getLight(i).position,
300 lights.getLight(i).normal, up, 3.0);
305 SG_LOG(SG_TERRAIN, SG_ALERT,
306 "unknown vasi/papi configuration, count = " << count);
312 SGLightFactory::getVasi(const SGVec3f& up, const SGDirectionalLightBin& lights,
313 const SGVec4f& red, const SGVec4f& white)
315 SGVasiDrawable* drawable = buildVasi(lights, up, red, white);
319 osg::StateSet* stateSet = drawable->getOrCreateStateSet();
320 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
321 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
323 osg::BlendFunc* blendFunc = new osg::BlendFunc;
324 stateSet->setAttribute(blendFunc);
325 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
327 osg::AlphaFunc* alphaFunc;
328 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
329 stateSet->setAttribute(alphaFunc);
330 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
336 SGLightFactory::getSequenced(const SGDirectionalLightBin& lights, const SGReaderWriterOptions* options)
338 if (lights.getNumLights() <= 0)
341 // generate a repeatable random seed
342 sg_srandom(unsigned(lights.getLight(0).position[0]));
343 float flashTime = 2e-2 + 5e-3*sg_random();
344 osg::Sequence* sequence = new osg::Sequence;
345 sequence->setDefaultTime(flashTime);
346 Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.0001, 0.00000001),
347 6.0f, 10.0f, true, options);
348 for (int i = lights.getNumLights() - 1; 0 <= i; --i) {
349 EffectGeode* egeode = new EffectGeode;
350 egeode->setEffect(effect);
351 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
352 sequence->addChild(egeode, flashTime);
354 sequence->addChild(new osg::Group, 1 + 1e-1*sg_random());
355 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
356 sequence->setDuration(1.0f, -1);
357 sequence->setMode(osg::Sequence::START);
358 sequence->setSync(true);
363 SGLightFactory::getOdal(const SGLightBin& lights, const SGReaderWriterOptions* options)
365 if (lights.getNumLights() < 2)
368 // generate a repeatable random seed
369 sg_srandom(unsigned(lights.getLight(0).position[0]));
370 float flashTime = 2e-2 + 5e-3*sg_random();
371 osg::Sequence* sequence = new osg::Sequence;
372 sequence->setDefaultTime(flashTime);
373 Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.0001, 0.00000001),
374 6.0, 10.0, false, options);
376 for (int i = lights.getNumLights(); i > 1; --i) {
377 EffectGeode* egeode = new EffectGeode;
378 egeode->setEffect(effect);
379 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
380 sequence->addChild(egeode, flashTime);
383 osg::Group* group = new osg::Group;
384 for (unsigned i = 0; i < 2; ++i) {
385 EffectGeode* egeode = new EffectGeode;
386 egeode->setEffect(effect);
387 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
388 group->addChild(egeode);
390 sequence->addChild(group, flashTime);
392 // add an extra empty group for a break
393 sequence->addChild(new osg::Group, 2 + 1e-1*sg_random());
394 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
395 sequence->setDuration(1.0f, -1);
396 sequence->setMode(osg::Sequence::START);
397 sequence->setSync(true);
402 // Blinking hold short line lights
404 SGLightFactory::getHoldShort(const SGDirectionalLightBin& lights, const SGReaderWriterOptions* options)
406 if (lights.getNumLights() < 2)
409 sg_srandom(unsigned(lights.getLight(0).position[0]));
410 float flashTime = 1 + 0.1 * sg_random();
411 osg::Sequence* sequence = new osg::Sequence;
413 // start with lights off
414 sequence->addChild(new osg::Group, flashTime);
415 // ...and increase the lights in steps
416 for (int i = 2; i < 7; i+=2) {
417 Effect* effect = getLightEffect(i, osg::Vec3(1, 0.001, 0.000002),
418 0.0f, i, true, options);
419 EffectGeode* egeode = new EffectGeode;
420 for (unsigned int j = 0; j < lights.getNumLights(); ++j) {
421 egeode->addDrawable(getLightDrawable(lights.getLight(j)));
422 egeode->setEffect(effect);
424 sequence->addChild(egeode, (i==6) ? flashTime : 0.1);
427 sequence->setInterval(osg::Sequence::SWING, 0, -1);
428 sequence->setDuration(1.0f, -1);
429 sequence->setMode(osg::Sequence::START);
434 // Alternating runway guard lights ("wig-wag")
436 SGLightFactory::getGuard(const SGDirectionalLightBin& lights, const SGReaderWriterOptions* options)
438 if (lights.getNumLights() < 2)
441 // generate a repeatable random seed
442 sg_srandom(unsigned(lights.getLight(0).position[0]));
443 float flashTime = 1.0f + 1*sg_random();
444 osg::Sequence* sequence = new osg::Sequence;
445 sequence->setDefaultTime(flashTime);
446 Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.001, 0.000002),
447 0.0f, 8.0f, true, options);
448 for (unsigned int i = 0; i < lights.getNumLights(); ++i) {
449 EffectGeode* egeode = new EffectGeode;
450 egeode->setEffect(effect);
451 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
452 sequence->addChild(egeode, flashTime);
454 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
455 sequence->setDuration(1.0f, -1);
456 sequence->setMode(osg::Sequence::START);
457 sequence->setSync(true);