]> git.mxchange.org Git - simgear.git/blob - simgear/scene/tgdb/pt_lights.cxx
#232: resurrect the "point sprites for runway lights" switch
[simgear.git] / simgear / scene / tgdb / pt_lights.cxx
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 #include <boost/tuple/tuple_comparison.hpp>
31
32 #include <osg/Array>
33 #include <osg/Geometry>
34 #include <osg/CullFace>
35 #include <osg/Geode>
36 #include <osg/MatrixTransform>
37 #include <osg/NodeCallback>
38 #include <osg/NodeVisitor>
39 #include <osg/Texture2D>
40 #include <osg/AlphaFunc>
41 #include <osg/BlendFunc>
42 #include <osg/TexEnv>
43 #include <osg/Sequence>
44 #include <osg/PolygonMode>
45 #include <osg/Fog>
46 #include <osg/FragmentProgram>
47 #include <osg/VertexProgram>
48 #include <osg/Point>
49 #include <osg/PointSprite>
50 #include <osg/Material>
51 #include <osg/Group>
52 #include <osg/StateSet>
53
54 #include <osgUtil/CullVisitor>
55
56 #include <OpenThreads/Mutex>
57 #include <OpenThreads/ScopedLock>
58
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/StateAttributeFactory.hxx>
64
65 #include <simgear/scene/material/Effect.hxx>
66 #include <simgear/scene/material/EffectGeode.hxx>
67 #include <simgear/scene/material/Technique.hxx>
68 #include <simgear/scene/material/Pass.hxx>
69
70 #include "SGVasiDrawable.hxx"
71
72 using OpenThreads::Mutex;
73 using OpenThreads::ScopedLock;
74
75 using namespace osg;
76 using namespace simgear;
77
78 static void
79 setPointSpriteImage(unsigned char* data, unsigned log2resolution,
80                     unsigned charsPerPixel)
81 {
82   int env_tex_res = (1 << log2resolution);
83   for (int i = 0; i < env_tex_res; ++i) {
84     for (int j = 0; j < env_tex_res; ++j) {
85       int xi = 2*i + 1 - env_tex_res;
86       int yi = 2*j + 1 - env_tex_res;
87       if (xi < 0)
88         xi = -xi;
89       if (yi < 0)
90         yi = -yi;
91       
92       xi -= 1;
93       yi -= 1;
94       
95       if (xi < 0)
96         xi = 0;
97       if (yi < 0)
98         yi = 0;
99       
100       float x = 1.5*xi/(float)(env_tex_res);
101       float y = 1.5*yi/(float)(env_tex_res);
102       //       float x = 2*xi/(float)(env_tex_res);
103       //       float y = 2*yi/(float)(env_tex_res);
104       float dist = sqrt(x*x + y*y);
105       float bright = SGMiscf::clip(255*(1-dist), 0, 255);
106       for (unsigned l = 0; l < charsPerPixel; ++l)
107         data[charsPerPixel*(i*env_tex_res + j) + l] = (unsigned char)bright;
108     }
109   }
110 }
111
112 static osg::Image*
113 getPointSpriteImage(int logResolution)
114 {
115   osg::Image* image = new osg::Image;
116   
117   osg::Image::MipmapDataType mipmapOffsets;
118   unsigned off = 0;
119   for (int i = logResolution; 0 <= i; --i) {
120     unsigned res = 1 << i;
121     off += res*res;
122     mipmapOffsets.push_back(off);
123   }
124   
125   int env_tex_res = (1 << logResolution);
126   
127   unsigned char* imageData = new unsigned char[off];
128   image->setImage(env_tex_res, env_tex_res, 1,
129                   GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE, imageData,
130                   osg::Image::USE_NEW_DELETE);
131   image->setMipmapLevels(mipmapOffsets);
132   
133   for (int k = logResolution; 0 <= k; --k) {
134     setPointSpriteImage(image->getMipmapData(logResolution - k), k, 1);
135   }
136   
137   return image;
138 }
139
140 static Mutex lightMutex;
141
142 static osg::Texture2D*
143 gen_standard_light_sprite(void)
144 {
145   // Always called from when the lightMutex is already taken
146   static osg::ref_ptr<osg::Texture2D> texture;
147   if (texture.valid())
148     return texture.get();
149   
150   texture = new osg::Texture2D;
151   texture->setImage(getPointSpriteImage(6));
152   texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
153   texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
154   
155   return texture.get();
156 }
157
158 namespace
159 {
160 typedef boost::tuple<float, osg::Vec3, float, float, bool> PointParams;
161 typedef std::map<PointParams, ref_ptr<Effect> > EffectMap;
162
163 EffectMap effectMap;
164
165 ref_ptr<PolygonMode> polyMode = new PolygonMode(PolygonMode::FRONT,
166                                                 PolygonMode::POINT);
167 ref_ptr<PointSprite> pointSprite = new PointSprite;
168 }
169
170 Effect* getLightEffect(float size, const Vec3& attenuation,
171                        float minSize, float maxSize, bool directional)
172 {
173     PointParams pointParams(size, attenuation, minSize, maxSize, directional);
174     ScopedLock<Mutex> lock(lightMutex);
175     EffectMap::iterator eitr = effectMap.find(pointParams);
176     if (eitr != effectMap.end())
177         return eitr->second.get();
178     // Basic stuff; no sprite or attenuation support
179     Pass *basicPass = new Pass;
180     basicPass->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
181     basicPass->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
182     StateAttributeFactory *attrFact = StateAttributeFactory::instance();
183     basicPass->setAttributeAndModes(attrFact->getStandardBlendFunc());
184     basicPass->setAttributeAndModes(attrFact->getStandardAlphaFunc());
185     if (directional) {
186         basicPass->setAttributeAndModes(attrFact->getCullFaceBack());
187         basicPass->setAttribute(polyMode.get());
188     }
189     Pass *attenuationPass = clone(basicPass, CopyOp::SHALLOW_COPY);
190     osg::Point* point = new osg::Point;
191     point->setMinSize(minSize);
192     point->setMaxSize(maxSize);
193     point->setSize(size);
194     point->setDistanceAttenuation(attenuation);
195     attenuationPass->setAttributeAndModes(point);
196     Pass *spritePass = clone(basicPass, CopyOp::SHALLOW_COPY);
197     spritePass->setTextureAttributeAndModes(0, pointSprite.get(),
198                                             osg::StateAttribute::ON);
199     Texture2D* texture = gen_standard_light_sprite();
200     spritePass->setTextureAttribute(0, texture);
201     spritePass->setTextureMode(0, GL_TEXTURE_2D,
202                                osg::StateAttribute::ON);
203     spritePass->setTextureAttribute(0, attrFact->getStandardTexEnv());
204     Pass *combinedPass = clone(spritePass, CopyOp::SHALLOW_COPY);
205     combinedPass->setAttributeAndModes(point);
206     Effect* effect = new Effect;
207     std::vector<std::string> parameterExtensions;
208
209     if (SGSceneFeatures::instance()->getEnablePointSpriteLights())
210     {
211         std::vector<std::string> combinedExtensions;
212         combinedExtensions.push_back("GL_ARB_point_sprite");
213         combinedExtensions.push_back("GL_ARB_point_parameters");
214         Technique* combinedTniq = new Technique;
215         combinedTniq->passes.push_back(combinedPass);
216         combinedTniq->setGLExtensionsPred(2.0, combinedExtensions);
217         effect->techniques.push_back(combinedTniq);
218         std::vector<std::string> spriteExtensions;
219         spriteExtensions.push_back(combinedExtensions.front());
220         Technique* spriteTniq = new Technique;
221         spriteTniq->passes.push_back(spritePass);
222         spriteTniq->setGLExtensionsPred(2.0, spriteExtensions);
223         effect->techniques.push_back(spriteTniq);
224         parameterExtensions.push_back(combinedExtensions.back());
225     }
226
227     Technique* parameterTniq = new Technique;
228     parameterTniq->passes.push_back(attenuationPass);
229     parameterTniq->setGLExtensionsPred(1.4, parameterExtensions);
230     effect->techniques.push_back(parameterTniq);
231     Technique* basicTniq = new Technique(true);
232     basicTniq->passes.push_back(basicPass);
233     effect->techniques.push_back(basicTniq);
234     effectMap.insert(std::make_pair(pointParams, effect));
235     return effect;
236 }
237
238
239 osg::Drawable*
240 SGLightFactory::getLightDrawable(const SGLightBin::Light& light)
241 {
242   osg::Vec3Array* vertices = new osg::Vec3Array;
243   osg::Vec4Array* colors = new osg::Vec4Array;
244
245   vertices->push_back(toOsg(light.position));
246   colors->push_back(toOsg(light.color));
247   
248   osg::Geometry* geometry = new osg::Geometry;
249   geometry->setVertexArray(vertices);
250   geometry->setNormalBinding(osg::Geometry::BIND_OFF);
251   geometry->setColorArray(colors);
252   geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
253
254   // Enlarge the bounding box to avoid such light nodes being victim to
255   // small feature culling.
256   geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
257   
258   osg::DrawArrays* drawArrays;
259   drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
260                                    0, vertices->size());
261   geometry->addPrimitiveSet(drawArrays);
262   return geometry;
263 }
264
265 osg::Drawable*
266 SGLightFactory::getLightDrawable(const SGDirectionalLightBin::Light& light)
267 {
268   osg::Vec3Array* vertices = new osg::Vec3Array;
269   osg::Vec4Array* colors = new osg::Vec4Array;
270
271   SGVec4f visibleColor(light.color);
272   SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
273                          visibleColor[2], 0);
274   SGVec3f normal = normalize(light.normal);
275   SGVec3f perp1 = perpendicular(normal);
276   SGVec3f perp2 = cross(normal, perp1);
277   SGVec3f position = light.position;
278   vertices->push_back(toOsg(position));
279   vertices->push_back(toOsg(position + perp1));
280   vertices->push_back(toOsg(position + perp2));
281   colors->push_back(toOsg(visibleColor));
282   colors->push_back(toOsg(invisibleColor));
283   colors->push_back(toOsg(invisibleColor));
284   
285   osg::Geometry* geometry = new osg::Geometry;
286   geometry->setVertexArray(vertices);
287   geometry->setNormalBinding(osg::Geometry::BIND_OFF);
288   geometry->setColorArray(colors);
289   geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
290   // Enlarge the bounding box to avoid such light nodes being victim to
291   // small feature culling.
292   geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
293   
294   osg::DrawArrays* drawArrays;
295   drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
296                                    0, vertices->size());
297   geometry->addPrimitiveSet(drawArrays);
298   return geometry;
299 }
300
301 namespace
302 {
303   ref_ptr<StateSet> simpleLightSS;
304 }
305 osg::Drawable*
306 SGLightFactory::getLights(const SGLightBin& lights, unsigned inc, float alphaOff)
307 {
308   if (lights.getNumLights() <= 0)
309     return 0;
310   
311   osg::Vec3Array* vertices = new osg::Vec3Array;
312   osg::Vec4Array* colors = new osg::Vec4Array;
313
314   for (unsigned i = 0; i < lights.getNumLights(); i += inc) {
315     vertices->push_back(toOsg(lights.getLight(i).position));
316     SGVec4f color = lights.getLight(i).color;
317     color[3] = SGMiscf::max(0, SGMiscf::min(1, color[3] + alphaOff));
318     colors->push_back(toOsg(color));
319   }
320   
321   osg::Geometry* geometry = new osg::Geometry;
322   
323   geometry->setVertexArray(vertices);
324   geometry->setNormalBinding(osg::Geometry::BIND_OFF);
325   geometry->setColorArray(colors);
326   geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
327   
328   osg::DrawArrays* drawArrays;
329   drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
330                                    0, vertices->size());
331   geometry->addPrimitiveSet(drawArrays);
332
333   {
334     ScopedLock<Mutex> lock(lightMutex);
335     if (!simpleLightSS.valid()) {
336       StateAttributeFactory *attrFact = StateAttributeFactory::instance();
337       simpleLightSS = new StateSet;
338       simpleLightSS->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
339       simpleLightSS->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
340       simpleLightSS->setAttributeAndModes(attrFact->getStandardBlendFunc());
341       simpleLightSS->setAttributeAndModes(attrFact->getStandardAlphaFunc());
342     }
343   }
344   geometry->setStateSet(simpleLightSS.get());
345   return geometry;
346 }
347
348
349 osg::Drawable*
350 SGLightFactory::getLights(const SGDirectionalLightBin& lights)
351 {
352   if (lights.getNumLights() <= 0)
353     return 0;
354   
355   osg::Vec3Array* vertices = new osg::Vec3Array;
356   osg::Vec4Array* colors = new osg::Vec4Array;
357
358   for (unsigned i = 0; i < lights.getNumLights(); ++i) {
359     SGVec4f visibleColor(lights.getLight(i).color);
360     SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
361                            visibleColor[2], 0);
362     SGVec3f normal = normalize(lights.getLight(i).normal);
363     SGVec3f perp1 = perpendicular(normal);
364     SGVec3f perp2 = cross(normal, perp1);
365     SGVec3f position = lights.getLight(i).position;
366     vertices->push_back(toOsg(position));
367     vertices->push_back(toOsg(position + perp1));
368     vertices->push_back(toOsg(position + perp2));
369     colors->push_back(toOsg(visibleColor));
370     colors->push_back(toOsg(invisibleColor));
371     colors->push_back(toOsg(invisibleColor));
372   }
373   
374   osg::Geometry* geometry = new osg::Geometry;
375   
376   geometry->setVertexArray(vertices);
377   geometry->setNormalBinding(osg::Geometry::BIND_OFF);
378   geometry->setColorArray(colors);
379   geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
380   
381   osg::DrawArrays* drawArrays;
382   drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
383                                    0, vertices->size());
384   geometry->addPrimitiveSet(drawArrays);
385   return geometry;
386 }
387
388 static SGVasiDrawable*
389 buildVasi(const SGDirectionalLightBin& lights, const SGVec3f& up,
390        const SGVec4f& red, const SGVec4f& white)
391 {
392   unsigned count = lights.getNumLights();
393   if ( count == 4 ) {
394     SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
395
396     // PAPI configuration
397     // papi D
398     drawable->addLight(lights.getLight(0).position,
399                        lights.getLight(0).normal, up, 3.5);
400     // papi C
401     drawable->addLight(lights.getLight(1).position,
402                        lights.getLight(1).normal, up, 3.167);
403     // papi B
404     drawable->addLight(lights.getLight(2).position,
405                        lights.getLight(2).normal, up, 2.833);
406     // papi A
407     drawable->addLight(lights.getLight(3).position,
408                        lights.getLight(3).normal, up, 2.5);
409     return drawable;
410   }
411   else if (count == 12) {
412     SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
413     
414     // probably vasi, first 6 are downwind bar (2.5 deg)
415     for (unsigned i = 0; i < 6; ++i)
416       drawable->addLight(lights.getLight(i).position,
417                          lights.getLight(i).normal, up, 2.5);
418     // last 6 are upwind bar (3.0 deg)
419     for (unsigned i = 6; i < 12; ++i)
420       drawable->addLight(lights.getLight(i).position,
421                          lights.getLight(i).normal, up, 3.0);
422     
423     return drawable;
424   } else {
425     // fail safe
426     SG_LOG(SG_TERRAIN, SG_ALERT,
427            "unknown vasi/papi configuration, count = " << count);
428     return 0;
429   }
430 }
431
432 osg::Drawable*
433 SGLightFactory::getVasi(const SGVec3f& up, const SGDirectionalLightBin& lights,
434                         const SGVec4f& red, const SGVec4f& white)
435 {
436   SGVasiDrawable* drawable = buildVasi(lights, up, red, white);
437   if (!drawable)
438     return 0;
439
440   osg::StateSet* stateSet = drawable->getOrCreateStateSet();
441   stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
442   stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
443
444   osg::BlendFunc* blendFunc = new osg::BlendFunc;
445   stateSet->setAttribute(blendFunc);
446   stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
447   
448   osg::AlphaFunc* alphaFunc;
449   alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
450   stateSet->setAttribute(alphaFunc);
451   stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
452
453   return drawable;
454 }
455
456 osg::Node*
457 SGLightFactory::getSequenced(const SGDirectionalLightBin& lights)
458 {
459   if (lights.getNumLights() <= 0)
460     return 0;
461
462   // generate a repeatable random seed
463   sg_srandom(unsigned(lights.getLight(0).position[0]));
464   float flashTime = 2e-2 + 5e-3*sg_random();
465   osg::Sequence* sequence = new osg::Sequence;
466   sequence->setDefaultTime(flashTime);
467   Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.0001, 0.00000001),
468                                   6.0f, 10.0f, true);
469   for (int i = lights.getNumLights() - 1; 0 <= i; --i) {
470     EffectGeode* egeode = new EffectGeode;
471     egeode->setEffect(effect);
472     egeode->addDrawable(getLightDrawable(lights.getLight(i)));
473     sequence->addChild(egeode, flashTime);
474   }
475   sequence->addChild(new osg::Group, 1 + 1e-1*sg_random());
476   sequence->setInterval(osg::Sequence::LOOP, 0, -1);
477   sequence->setDuration(1.0f, -1);
478   sequence->setMode(osg::Sequence::START);
479   sequence->setSync(true);
480   return sequence;
481 }
482
483 osg::Node*
484 SGLightFactory::getOdal(const SGLightBin& lights)
485 {
486   if (lights.getNumLights() < 2)
487     return 0;
488
489   // generate a repeatable random seed
490   sg_srandom(unsigned(lights.getLight(0).position[0]));
491   float flashTime = 2e-2 + 5e-3*sg_random();
492   osg::Sequence* sequence = new osg::Sequence;
493   sequence->setDefaultTime(flashTime);
494   Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.0001, 0.00000001),
495                                   6.0, 10.0, false);
496   // centerline lights
497   for (int i = lights.getNumLights() - 1; 2 <= i; --i) {
498     EffectGeode* egeode = new EffectGeode;
499     egeode->setEffect(effect);
500     egeode->addDrawable(getLightDrawable(lights.getLight(i)));
501     sequence->addChild(egeode, flashTime);
502   }
503   // runway end lights
504   osg::Group* group = new osg::Group;
505   for (unsigned i = 0; i < 2; ++i) {
506     EffectGeode* egeode = new EffectGeode;
507     egeode->setEffect(effect);
508     egeode->addDrawable(getLightDrawable(lights.getLight(i)));
509     group->addChild(egeode);
510   }
511   sequence->addChild(group, flashTime);
512
513   // add an extra empty group for a break
514   sequence->addChild(new osg::Group, 9 + 1e-1*sg_random());
515   sequence->setInterval(osg::Sequence::LOOP, 0, -1);
516   sequence->setDuration(1.0f, -1);
517   sequence->setMode(osg::Sequence::START);
518   sequence->setSync(true);
519
520   return sequence;
521 }