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>
44 #include <osg/PolygonMode>
46 #include <osg/FragmentProgram>
47 #include <osg/VertexProgram>
49 #include <osg/PointSprite>
50 #include <osg/Material>
52 #include <osg/StateSet>
54 #include <osgUtil/CullVisitor>
56 #include <OpenThreads/Mutex>
57 #include <OpenThreads/ScopedLock>
59 #include <simgear/math/sg_random.h>
60 #include <simgear/debug/logstream.hxx>
61 #include <simgear/scene/util/RenderConstants.hxx>
62 #include <simgear/scene/util/SGEnlargeBoundingBox.hxx>
63 #include <simgear/scene/util/OsgMath.hxx>
64 #include <simgear/scene/util/StateAttributeFactory.hxx>
66 #include <simgear/scene/material/Effect.hxx>
67 #include <simgear/scene/material/EffectGeode.hxx>
68 #include <simgear/scene/material/Technique.hxx>
69 #include <simgear/scene/material/Pass.hxx>
71 #include "SGVasiDrawable.hxx"
73 using OpenThreads::Mutex;
74 using OpenThreads::ScopedLock;
77 using namespace simgear;
80 setPointSpriteImage(unsigned char* data, unsigned log2resolution,
81 unsigned charsPerPixel)
83 int env_tex_res = (1 << log2resolution);
84 for (int i = 0; i < env_tex_res; ++i) {
85 for (int j = 0; j < env_tex_res; ++j) {
86 int xi = 2*i + 1 - env_tex_res;
87 int yi = 2*j + 1 - env_tex_res;
101 float x = 1.5*xi/(float)(env_tex_res);
102 float y = 1.5*yi/(float)(env_tex_res);
103 // float x = 2*xi/(float)(env_tex_res);
104 // float y = 2*yi/(float)(env_tex_res);
105 float dist = sqrt(x*x + y*y);
106 float bright = SGMiscf::clip(255*(1-dist), 0, 255);
107 for (unsigned l = 0; l < charsPerPixel; ++l)
108 data[charsPerPixel*(i*env_tex_res + j) + l] = (unsigned char)bright;
114 getPointSpriteImage(int logResolution)
116 osg::Image* image = new osg::Image;
118 osg::Image::MipmapDataType mipmapOffsets;
120 for (int i = logResolution; 0 <= i; --i) {
121 unsigned res = 1 << i;
123 mipmapOffsets.push_back(off);
126 int env_tex_res = (1 << logResolution);
128 unsigned char* imageData = new unsigned char[off];
129 image->setImage(env_tex_res, env_tex_res, 1,
130 GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE, imageData,
131 osg::Image::USE_NEW_DELETE);
132 image->setMipmapLevels(mipmapOffsets);
134 for (int k = logResolution; 0 <= k; --k) {
135 setPointSpriteImage(image->getMipmapData(logResolution - k), k, 1);
141 static Mutex lightMutex;
143 static osg::Texture2D*
144 gen_standard_light_sprite(void)
146 // Always called from when the lightMutex is already taken
147 static osg::ref_ptr<osg::Texture2D> texture;
149 return texture.get();
151 texture = new osg::Texture2D;
152 texture->setImage(getPointSpriteImage(6));
153 texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
154 texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
156 return texture.get();
161 typedef boost::tuple<float, osg::Vec3, float, float, bool> PointParams;
162 typedef std::map<PointParams, observer_ptr<Effect> > EffectMap;
166 ref_ptr<PolygonMode> polyMode = new PolygonMode(PolygonMode::FRONT,
168 ref_ptr<PointSprite> pointSprite = new PointSprite;
171 Effect* getLightEffect(float size, const Vec3& attenuation,
172 float minSize, float maxSize, bool directional)
174 PointParams pointParams(size, attenuation, minSize, maxSize, directional);
175 ScopedLock<Mutex> lock(lightMutex);
176 EffectMap::iterator eitr = effectMap.find(pointParams);
177 if (eitr != effectMap.end())
179 ref_ptr<Effect> effect;
180 if (eitr->second.lock(effect))
181 return effect.release();
183 // Basic stuff; no sprite or attenuation support
184 Pass *basicPass = new Pass;
185 basicPass->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
186 basicPass->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
187 StateAttributeFactory *attrFact = StateAttributeFactory::instance();
188 basicPass->setAttributeAndModes(attrFact->getStandardBlendFunc());
189 basicPass->setAttributeAndModes(attrFact->getStandardAlphaFunc());
191 basicPass->setAttributeAndModes(attrFact->getCullFaceBack());
192 basicPass->setAttribute(polyMode.get());
194 Pass *attenuationPass = clone(basicPass, CopyOp::SHALLOW_COPY);
195 osg::Point* point = new osg::Point;
196 point->setMinSize(minSize);
197 point->setMaxSize(maxSize);
198 point->setSize(size);
199 point->setDistanceAttenuation(attenuation);
200 attenuationPass->setAttributeAndModes(point);
201 Pass *spritePass = clone(basicPass, CopyOp::SHALLOW_COPY);
202 spritePass->setTextureAttributeAndModes(0, pointSprite.get(),
203 osg::StateAttribute::ON);
204 Texture2D* texture = gen_standard_light_sprite();
205 spritePass->setTextureAttribute(0, texture);
206 spritePass->setTextureMode(0, GL_TEXTURE_2D,
207 osg::StateAttribute::ON);
208 spritePass->setTextureAttribute(0, attrFact->getStandardTexEnv());
209 Pass *combinedPass = clone(spritePass, CopyOp::SHALLOW_COPY);
210 combinedPass->setAttributeAndModes(point);
211 ref_ptr<Effect> effect = new Effect;
212 std::vector<std::string> parameterExtensions;
214 if (SGSceneFeatures::instance()->getEnablePointSpriteLights())
216 std::vector<std::string> combinedExtensions;
217 combinedExtensions.push_back("GL_ARB_point_sprite");
218 combinedExtensions.push_back("GL_ARB_point_parameters");
219 Technique* combinedTniq = new Technique;
220 combinedTniq->passes.push_back(combinedPass);
221 combinedTniq->setGLExtensionsPred(2.0, combinedExtensions);
222 effect->techniques.push_back(combinedTniq);
223 std::vector<std::string> spriteExtensions;
224 spriteExtensions.push_back(combinedExtensions.front());
225 Technique* spriteTniq = new Technique;
226 spriteTniq->passes.push_back(spritePass);
227 spriteTniq->setGLExtensionsPred(2.0, spriteExtensions);
228 effect->techniques.push_back(spriteTniq);
229 parameterExtensions.push_back(combinedExtensions.back());
232 Technique* parameterTniq = new Technique;
233 parameterTniq->passes.push_back(attenuationPass);
234 parameterTniq->setGLExtensionsPred(1.4, parameterExtensions);
235 effect->techniques.push_back(parameterTniq);
236 Technique* basicTniq = new Technique(true);
237 basicTniq->passes.push_back(basicPass);
238 effect->techniques.push_back(basicTniq);
239 if (eitr == effectMap.end())
240 effectMap.insert(std::make_pair(pointParams, effect));
242 eitr->second = effect; // update existing, but empty observer
243 return effect.release();
248 SGLightFactory::getLightDrawable(const SGLightBin::Light& light)
250 osg::Vec3Array* vertices = new osg::Vec3Array;
251 osg::Vec4Array* colors = new osg::Vec4Array;
253 vertices->push_back(toOsg(light.position));
254 colors->push_back(toOsg(light.color));
256 osg::Geometry* geometry = new osg::Geometry;
257 geometry->setDataVariance(osg::Object::STATIC);
258 geometry->setVertexArray(vertices);
259 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
260 geometry->setColorArray(colors);
261 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
263 // Enlarge the bounding box to avoid such light nodes being victim to
264 // small feature culling.
265 geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
267 osg::DrawArrays* drawArrays;
268 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
269 0, vertices->size());
270 geometry->addPrimitiveSet(drawArrays);
275 SGLightFactory::getLightDrawable(const SGDirectionalLightBin::Light& light)
277 osg::Vec3Array* vertices = new osg::Vec3Array;
278 osg::Vec4Array* colors = new osg::Vec4Array;
280 SGVec4f visibleColor(light.color);
281 SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
283 SGVec3f normal = normalize(light.normal);
284 SGVec3f perp1 = perpendicular(normal);
285 SGVec3f perp2 = cross(normal, perp1);
286 SGVec3f position = light.position;
287 vertices->push_back(toOsg(position));
288 vertices->push_back(toOsg(position + perp1));
289 vertices->push_back(toOsg(position + perp2));
290 colors->push_back(toOsg(visibleColor));
291 colors->push_back(toOsg(invisibleColor));
292 colors->push_back(toOsg(invisibleColor));
294 osg::Geometry* geometry = new osg::Geometry;
295 geometry->setDataVariance(osg::Object::STATIC);
296 geometry->setVertexArray(vertices);
297 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
298 geometry->setColorArray(colors);
299 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
300 // Enlarge the bounding box to avoid such light nodes being victim to
301 // small feature culling.
302 geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
304 osg::DrawArrays* drawArrays;
305 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
306 0, vertices->size());
307 geometry->addPrimitiveSet(drawArrays);
313 ref_ptr<StateSet> simpleLightSS;
316 SGLightFactory::getLights(const SGLightBin& lights, unsigned inc, float alphaOff)
318 if (lights.getNumLights() <= 0)
321 osg::Vec3Array* vertices = new osg::Vec3Array;
322 osg::Vec4Array* colors = new osg::Vec4Array;
324 for (unsigned i = 0; i < lights.getNumLights(); i += inc) {
325 vertices->push_back(toOsg(lights.getLight(i).position));
326 SGVec4f color = lights.getLight(i).color;
327 color[3] = SGMiscf::max(0, SGMiscf::min(1, color[3] + alphaOff));
328 colors->push_back(toOsg(color));
331 osg::Geometry* geometry = new osg::Geometry;
332 geometry->setDataVariance(osg::Object::STATIC);
333 geometry->setVertexArray(vertices);
334 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
335 geometry->setColorArray(colors);
336 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
338 osg::DrawArrays* drawArrays;
339 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
340 0, vertices->size());
341 geometry->addPrimitiveSet(drawArrays);
344 ScopedLock<Mutex> lock(lightMutex);
345 if (!simpleLightSS.valid()) {
346 StateAttributeFactory *attrFact = StateAttributeFactory::instance();
347 simpleLightSS = new StateSet;
348 simpleLightSS->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
349 simpleLightSS->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
350 simpleLightSS->setAttributeAndModes(attrFact->getStandardBlendFunc());
351 simpleLightSS->setAttributeAndModes(attrFact->getStandardAlphaFunc());
354 geometry->setStateSet(simpleLightSS.get());
360 SGLightFactory::getLights(const SGDirectionalLightBin& lights)
362 if (lights.getNumLights() <= 0)
365 osg::Vec3Array* vertices = new osg::Vec3Array;
366 osg::Vec4Array* colors = new osg::Vec4Array;
368 for (unsigned i = 0; i < lights.getNumLights(); ++i) {
369 SGVec4f visibleColor(lights.getLight(i).color);
370 SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
372 SGVec3f normal = normalize(lights.getLight(i).normal);
373 SGVec3f perp1 = perpendicular(normal);
374 SGVec3f perp2 = cross(normal, perp1);
375 SGVec3f position = lights.getLight(i).position;
376 vertices->push_back(toOsg(position));
377 vertices->push_back(toOsg(position + perp1));
378 vertices->push_back(toOsg(position + perp2));
379 colors->push_back(toOsg(visibleColor));
380 colors->push_back(toOsg(invisibleColor));
381 colors->push_back(toOsg(invisibleColor));
384 osg::Geometry* geometry = new osg::Geometry;
385 geometry->setDataVariance(osg::Object::STATIC);
386 geometry->setVertexArray(vertices);
387 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
388 geometry->setColorArray(colors);
389 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
391 osg::DrawArrays* drawArrays;
392 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
393 0, vertices->size());
394 geometry->addPrimitiveSet(drawArrays);
398 static SGVasiDrawable*
399 buildVasi(const SGDirectionalLightBin& lights, const SGVec3f& up,
400 const SGVec4f& red, const SGVec4f& white)
402 unsigned count = lights.getNumLights();
404 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
406 // PAPI configuration
408 drawable->addLight(lights.getLight(0).position,
409 lights.getLight(0).normal, up, 3.5);
411 drawable->addLight(lights.getLight(1).position,
412 lights.getLight(1).normal, up, 3.167);
414 drawable->addLight(lights.getLight(2).position,
415 lights.getLight(2).normal, up, 2.833);
417 drawable->addLight(lights.getLight(3).position,
418 lights.getLight(3).normal, up, 2.5);
421 } else if (count == 6) {
422 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
424 // probably vasi, first 3 are downwind bar (2.5 deg)
425 for (unsigned i = 0; i < 3; ++i)
426 drawable->addLight(lights.getLight(i).position,
427 lights.getLight(i).normal, up, 2.5);
428 // last 3 are upwind bar (3.0 deg)
429 for (unsigned i = 3; i < 6; ++i)
430 drawable->addLight(lights.getLight(i).position,
431 lights.getLight(i).normal, up, 3.0);
434 } else if (count == 12) {
435 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
437 // probably vasi, first 6 are downwind bar (2.5 deg)
438 for (unsigned i = 0; i < 6; ++i)
439 drawable->addLight(lights.getLight(i).position,
440 lights.getLight(i).normal, up, 2.5);
441 // last 6 are upwind bar (3.0 deg)
442 for (unsigned i = 6; i < 12; ++i)
443 drawable->addLight(lights.getLight(i).position,
444 lights.getLight(i).normal, up, 3.0);
449 SG_LOG(SG_TERRAIN, SG_ALERT,
450 "unknown vasi/papi configuration, count = " << count);
456 SGLightFactory::getVasi(const SGVec3f& up, const SGDirectionalLightBin& lights,
457 const SGVec4f& red, const SGVec4f& white)
459 SGVasiDrawable* drawable = buildVasi(lights, up, red, white);
463 osg::StateSet* stateSet = drawable->getOrCreateStateSet();
464 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
465 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
467 osg::BlendFunc* blendFunc = new osg::BlendFunc;
468 stateSet->setAttribute(blendFunc);
469 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
471 osg::AlphaFunc* alphaFunc;
472 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
473 stateSet->setAttribute(alphaFunc);
474 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
480 SGLightFactory::getSequenced(const SGDirectionalLightBin& lights)
482 if (lights.getNumLights() <= 0)
485 // generate a repeatable random seed
486 sg_srandom(unsigned(lights.getLight(0).position[0]));
487 float flashTime = 2e-2 + 5e-3*sg_random();
488 osg::Sequence* sequence = new osg::Sequence;
489 sequence->setDefaultTime(flashTime);
490 Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.0001, 0.00000001),
492 for (int i = lights.getNumLights() - 1; 0 <= i; --i) {
493 EffectGeode* egeode = new EffectGeode;
494 egeode->setEffect(effect);
495 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
496 sequence->addChild(egeode, flashTime);
498 sequence->addChild(new osg::Group, 1 + 1e-1*sg_random());
499 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
500 sequence->setDuration(1.0f, -1);
501 sequence->setMode(osg::Sequence::START);
502 sequence->setSync(true);
507 SGLightFactory::getOdal(const SGLightBin& lights)
509 if (lights.getNumLights() < 2)
512 // generate a repeatable random seed
513 sg_srandom(unsigned(lights.getLight(0).position[0]));
514 float flashTime = 2e-2 + 5e-3*sg_random();
515 osg::Sequence* sequence = new osg::Sequence;
516 sequence->setDefaultTime(flashTime);
517 Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.0001, 0.00000001),
520 for (int i = lights.getNumLights(); i > 1; --i) {
521 EffectGeode* egeode = new EffectGeode;
522 egeode->setEffect(effect);
523 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
524 sequence->addChild(egeode, flashTime);
527 osg::Group* group = new osg::Group;
528 for (unsigned i = 0; i < 2; ++i) {
529 EffectGeode* egeode = new EffectGeode;
530 egeode->setEffect(effect);
531 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
532 group->addChild(egeode);
534 sequence->addChild(group, flashTime);
536 // add an extra empty group for a break
537 sequence->addChild(new osg::Group, 2 + 1e-1*sg_random());
538 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
539 sequence->setDuration(1.0f, -1);
540 sequence->setMode(osg::Sequence::START);
541 sequence->setSync(true);
546 // Blinking hold short line lights
548 SGLightFactory::getHoldShort(const SGDirectionalLightBin& lights)
550 if (lights.getNumLights() < 2)
553 sg_srandom(unsigned(lights.getLight(0).position[0]));
554 float flashTime = 1 + 0.1 * sg_random();
555 osg::Sequence* sequence = new osg::Sequence;
557 // start with lights off
558 sequence->addChild(new osg::Group, flashTime);
559 // ...and increase the lights in steps
560 for (int i = 2; i < 7; i+=2) {
561 Effect* effect = getLightEffect(i, osg::Vec3(1, 0.001, 0.000002),
563 EffectGeode* egeode = new EffectGeode;
564 for (unsigned int j = 0; j < lights.getNumLights(); ++j) {
565 egeode->addDrawable(getLightDrawable(lights.getLight(j)));
566 egeode->setEffect(effect);
568 sequence->addChild(egeode, (i==6) ? flashTime : 0.1);
571 sequence->setInterval(osg::Sequence::SWING, 0, -1);
572 sequence->setDuration(1.0f, -1);
573 sequence->setMode(osg::Sequence::START);
578 // Alternating runway guard lights ("wig-wag")
580 SGLightFactory::getGuard(const SGDirectionalLightBin& lights)
582 if (lights.getNumLights() < 2)
585 // generate a repeatable random seed
586 sg_srandom(unsigned(lights.getLight(0).position[0]));
587 float flashTime = 1.0f + 1*sg_random();
588 osg::Sequence* sequence = new osg::Sequence;
589 sequence->setDefaultTime(flashTime);
590 Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.001, 0.000002),
592 for (unsigned int i = 0; i < lights.getNumLights(); ++i) {
593 EffectGeode* egeode = new EffectGeode;
594 egeode->setEffect(effect);
595 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
596 sequence->addChild(egeode, flashTime);
598 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
599 sequence->setDuration(1.0f, -1);
600 sequence->setMode(osg::Sequence::START);
601 sequence->setSync(true);