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;
156 SGVec4f visibleColor(light.color);
157 SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
159 SGVec3f normal = normalize(light.normal);
160 SGVec3f perp1 = perpendicular(normal);
161 SGVec3f perp2 = cross(normal, perp1);
162 SGVec3f position = light.position;
163 vertices->push_back(toOsg(position));
164 vertices->push_back(toOsg(position + perp1));
165 vertices->push_back(toOsg(position + perp2));
166 colors->push_back(toOsg(visibleColor));
167 colors->push_back(toOsg(invisibleColor));
168 colors->push_back(toOsg(invisibleColor));
170 osg::Geometry* geometry = new osg::Geometry;
171 geometry->setDataVariance(osg::Object::STATIC);
172 geometry->setVertexArray(vertices);
173 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
174 geometry->setColorArray(colors);
175 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
176 // Enlarge the bounding box to avoid such light nodes being victim to
177 // small feature culling.
178 geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
180 osg::DrawArrays* drawArrays;
181 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
182 0, vertices->size());
183 geometry->addPrimitiveSet(drawArrays);
189 ref_ptr<StateSet> simpleLightSS;
192 SGLightFactory::getLights(const SGLightBin& lights, unsigned inc, float alphaOff)
194 if (lights.getNumLights() <= 0)
197 osg::Vec3Array* vertices = new osg::Vec3Array;
198 osg::Vec4Array* colors = new osg::Vec4Array;
200 for (unsigned i = 0; i < lights.getNumLights(); i += inc) {
201 vertices->push_back(toOsg(lights.getLight(i).position));
202 SGVec4f color = lights.getLight(i).color;
203 color[3] = SGMiscf::max(0, SGMiscf::min(1, color[3] + alphaOff));
204 colors->push_back(toOsg(color));
207 osg::Geometry* geometry = new osg::Geometry;
208 geometry->setDataVariance(osg::Object::STATIC);
209 geometry->setVertexArray(vertices);
210 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
211 geometry->setColorArray(colors);
212 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
214 osg::DrawArrays* drawArrays;
215 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
216 0, vertices->size());
217 geometry->addPrimitiveSet(drawArrays);
220 ScopedLock<Mutex> lock(lightMutex);
221 if (!simpleLightSS.valid()) {
222 StateAttributeFactory *attrFact = StateAttributeFactory::instance();
223 simpleLightSS = new StateSet;
224 simpleLightSS->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
225 simpleLightSS->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
226 simpleLightSS->setAttributeAndModes(attrFact->getStandardBlendFunc());
227 simpleLightSS->setAttributeAndModes(attrFact->getStandardAlphaFunc());
230 geometry->setStateSet(simpleLightSS.get());
236 SGLightFactory::getLights(const SGDirectionalLightBin& lights)
238 if (lights.getNumLights() <= 0)
241 osg::Vec3Array* vertices = new osg::Vec3Array;
242 osg::Vec4Array* colors = new osg::Vec4Array;
244 for (unsigned i = 0; i < lights.getNumLights(); ++i) {
245 SGVec4f visibleColor(lights.getLight(i).color);
246 SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
248 SGVec3f normal = normalize(lights.getLight(i).normal);
249 SGVec3f perp1 = perpendicular(normal);
250 SGVec3f perp2 = cross(normal, perp1);
251 SGVec3f position = lights.getLight(i).position;
252 vertices->push_back(toOsg(position));
253 vertices->push_back(toOsg(position + perp1));
254 vertices->push_back(toOsg(position + perp2));
255 colors->push_back(toOsg(visibleColor));
256 colors->push_back(toOsg(invisibleColor));
257 colors->push_back(toOsg(invisibleColor));
260 osg::Geometry* geometry = new osg::Geometry;
261 geometry->setDataVariance(osg::Object::STATIC);
262 geometry->setVertexArray(vertices);
263 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
264 geometry->setColorArray(colors);
265 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
267 osg::DrawArrays* drawArrays;
268 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
269 0, vertices->size());
270 geometry->addPrimitiveSet(drawArrays);
274 static SGVasiDrawable*
275 buildVasi(const SGDirectionalLightBin& lights, const SGVec3f& up,
276 const SGVec4f& red, const SGVec4f& white)
278 unsigned count = lights.getNumLights();
280 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
282 // PAPI configuration
284 drawable->addLight(lights.getLight(0).position,
285 lights.getLight(0).normal, up, 3.5);
287 drawable->addLight(lights.getLight(1).position,
288 lights.getLight(1).normal, up, 3.167);
290 drawable->addLight(lights.getLight(2).position,
291 lights.getLight(2).normal, up, 2.833);
293 drawable->addLight(lights.getLight(3).position,
294 lights.getLight(3).normal, up, 2.5);
297 } else if (count == 6) {
298 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
300 // probably vasi, first 3 are downwind bar (2.5 deg)
301 for (unsigned i = 0; i < 3; ++i)
302 drawable->addLight(lights.getLight(i).position,
303 lights.getLight(i).normal, up, 2.5);
304 // last 3 are upwind bar (3.0 deg)
305 for (unsigned i = 3; i < 6; ++i)
306 drawable->addLight(lights.getLight(i).position,
307 lights.getLight(i).normal, up, 3.0);
310 } else if (count == 12) {
311 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
313 // probably vasi, first 6 are downwind bar (2.5 deg)
314 for (unsigned i = 0; i < 6; ++i)
315 drawable->addLight(lights.getLight(i).position,
316 lights.getLight(i).normal, up, 2.5);
317 // last 6 are upwind bar (3.0 deg)
318 for (unsigned i = 6; i < 12; ++i)
319 drawable->addLight(lights.getLight(i).position,
320 lights.getLight(i).normal, up, 3.0);
325 SG_LOG(SG_TERRAIN, SG_ALERT,
326 "unknown vasi/papi configuration, count = " << count);
332 SGLightFactory::getVasi(const SGVec3f& up, const SGDirectionalLightBin& lights,
333 const SGVec4f& red, const SGVec4f& white)
335 SGVasiDrawable* drawable = buildVasi(lights, up, red, white);
339 osg::StateSet* stateSet = drawable->getOrCreateStateSet();
340 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
341 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
343 osg::BlendFunc* blendFunc = new osg::BlendFunc;
344 stateSet->setAttribute(blendFunc);
345 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
347 osg::AlphaFunc* alphaFunc;
348 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
349 stateSet->setAttribute(alphaFunc);
350 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
356 SGLightFactory::getSequenced(const SGDirectionalLightBin& lights, const SGReaderWriterOptions* options)
358 if (lights.getNumLights() <= 0)
361 // generate a repeatable random seed
362 sg_srandom(unsigned(lights.getLight(0).position[0]));
363 float flashTime = 2e-2 + 5e-3*sg_random();
364 osg::Sequence* sequence = new osg::Sequence;
365 sequence->setDefaultTime(flashTime);
366 Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.0001, 0.00000001),
367 6.0f, 10.0f, true, options);
368 for (int i = lights.getNumLights() - 1; 0 <= i; --i) {
369 EffectGeode* egeode = new EffectGeode;
370 egeode->setEffect(effect);
371 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
372 sequence->addChild(egeode, flashTime);
374 sequence->addChild(new osg::Group, 1 + 1e-1*sg_random());
375 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
376 sequence->setDuration(1.0f, -1);
377 sequence->setMode(osg::Sequence::START);
378 sequence->setSync(true);
383 SGLightFactory::getOdal(const SGLightBin& lights, const SGReaderWriterOptions* options)
385 if (lights.getNumLights() < 2)
388 // generate a repeatable random seed
389 sg_srandom(unsigned(lights.getLight(0).position[0]));
390 float flashTime = 2e-2 + 5e-3*sg_random();
391 osg::Sequence* sequence = new osg::Sequence;
392 sequence->setDefaultTime(flashTime);
393 Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.0001, 0.00000001),
394 6.0, 10.0, false, options);
396 for (int i = lights.getNumLights(); i > 1; --i) {
397 EffectGeode* egeode = new EffectGeode;
398 egeode->setEffect(effect);
399 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
400 sequence->addChild(egeode, flashTime);
403 osg::Group* group = new osg::Group;
404 for (unsigned i = 0; i < 2; ++i) {
405 EffectGeode* egeode = new EffectGeode;
406 egeode->setEffect(effect);
407 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
408 group->addChild(egeode);
410 sequence->addChild(group, flashTime);
412 // add an extra empty group for a break
413 sequence->addChild(new osg::Group, 2 + 1e-1*sg_random());
414 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
415 sequence->setDuration(1.0f, -1);
416 sequence->setMode(osg::Sequence::START);
417 sequence->setSync(true);
422 // Blinking hold short line lights
424 SGLightFactory::getHoldShort(const SGDirectionalLightBin& lights, const SGReaderWriterOptions* options)
426 if (lights.getNumLights() < 2)
429 sg_srandom(unsigned(lights.getLight(0).position[0]));
430 float flashTime = 1 + 0.1 * sg_random();
431 osg::Sequence* sequence = new osg::Sequence;
433 // start with lights off
434 sequence->addChild(new osg::Group, flashTime);
435 // ...and increase the lights in steps
436 for (int i = 2; i < 7; i+=2) {
437 Effect* effect = getLightEffect(i, osg::Vec3(1, 0.001, 0.000002),
438 0.0f, i, true, options);
439 EffectGeode* egeode = new EffectGeode;
440 for (unsigned int j = 0; j < lights.getNumLights(); ++j) {
441 egeode->addDrawable(getLightDrawable(lights.getLight(j)));
442 egeode->setEffect(effect);
444 sequence->addChild(egeode, (i==6) ? flashTime : 0.1);
447 sequence->setInterval(osg::Sequence::SWING, 0, -1);
448 sequence->setDuration(1.0f, -1);
449 sequence->setMode(osg::Sequence::START);
454 // Alternating runway guard lights ("wig-wag")
456 SGLightFactory::getGuard(const SGDirectionalLightBin& lights, const SGReaderWriterOptions* options)
458 if (lights.getNumLights() < 2)
461 // generate a repeatable random seed
462 sg_srandom(unsigned(lights.getLight(0).position[0]));
463 float flashTime = 1.0f + 1*sg_random();
464 osg::Sequence* sequence = new osg::Sequence;
465 sequence->setDefaultTime(flashTime);
466 Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.001, 0.000002),
467 0.0f, 8.0f, true, options);
468 for (unsigned int i = 0; i < lights.getNumLights(); ++i) {
469 EffectGeode* egeode = new EffectGeode;
470 egeode->setEffect(effect);
471 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
472 sequence->addChild(egeode, flashTime);
474 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
475 sequence->setDuration(1.0f, -1);
476 sequence->setMode(osg::Sequence::START);
477 sequence->setSync(true);