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/StateAttributeFactory.hxx>
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>
70 #include "SGVasiDrawable.hxx"
72 using OpenThreads::Mutex;
73 using OpenThreads::ScopedLock;
76 using namespace simgear;
79 setPointSpriteImage(unsigned char* data, unsigned log2resolution,
80 unsigned charsPerPixel)
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;
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;
113 getPointSpriteImage(int logResolution)
115 osg::Image* image = new osg::Image;
117 osg::Image::MipmapDataType mipmapOffsets;
119 for (int i = logResolution; 0 <= i; --i) {
120 unsigned res = 1 << i;
122 mipmapOffsets.push_back(off);
125 int env_tex_res = (1 << logResolution);
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);
133 for (int k = logResolution; 0 <= k; --k) {
134 setPointSpriteImage(image->getMipmapData(logResolution - k), k, 1);
140 static Mutex lightMutex;
142 static osg::Texture2D*
143 gen_standard_light_sprite(void)
145 // Always called from when the lightMutex is already taken
146 static osg::ref_ptr<osg::Texture2D> texture;
148 return texture.get();
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);
155 return texture.get();
160 typedef boost::tuple<float, osg::Vec3, float, float, bool> PointParams;
161 typedef std::map<PointParams, ref_ptr<Effect> > EffectMap;
165 ref_ptr<PolygonMode> polyMode = new PolygonMode(PolygonMode::FRONT,
167 ref_ptr<PointSprite> pointSprite = new PointSprite;
170 Effect* getLightEffect(float size, const Vec3& attenuation,
171 float minSize, float maxSize, bool directional)
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());
186 basicPass->setAttributeAndModes(attrFact->getCullFaceBack());
187 basicPass->setAttribute(polyMode.get());
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,
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> combinedExtensions;
208 combinedExtensions.push_back("GL_ARB_point_sprite");
209 combinedExtensions.push_back("GL_ARB_point_parameters");
210 Technique* combinedTniq = new Technique;
211 combinedTniq->passes.push_back(combinedPass);
212 combinedTniq->setGLExtensionsPred(2.0, combinedExtensions);
213 effect->techniques.push_back(combinedTniq);
214 std::vector<std::string> spriteExtensions;
215 spriteExtensions.push_back(combinedExtensions.front());
216 Technique* spriteTniq = new Technique;
217 spriteTniq->passes.push_back(spritePass);
218 spriteTniq->setGLExtensionsPred(2.0, spriteExtensions);
219 effect->techniques.push_back(spriteTniq);
220 std::vector<std::string> parameterExtensions;
221 parameterExtensions.push_back(combinedExtensions.back());
222 Technique* parameterTniq = new Technique;
223 parameterTniq->passes.push_back(attenuationPass);
224 parameterTniq->setGLExtensionsPred(1.4, parameterExtensions);
225 effect->techniques.push_back(parameterTniq);
226 Technique* basicTniq = new Technique(true);
227 basicTniq->passes.push_back(basicPass);
228 effect->techniques.push_back(basicTniq);
229 effectMap.insert(std::make_pair(pointParams, effect));
235 SGLightFactory::getLightDrawable(const SGLightBin::Light& light)
237 osg::Vec3Array* vertices = new osg::Vec3Array;
238 osg::Vec4Array* colors = new osg::Vec4Array;
240 vertices->push_back(light.position.osg());
241 colors->push_back(light.color.osg());
243 osg::Geometry* geometry = new osg::Geometry;
244 geometry->setVertexArray(vertices);
245 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
246 geometry->setColorArray(colors);
247 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
249 // Enlarge the bounding box to avoid such light nodes being victim to
250 // small feature culling.
251 geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
253 osg::DrawArrays* drawArrays;
254 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
255 0, vertices->size());
256 geometry->addPrimitiveSet(drawArrays);
261 SGLightFactory::getLightDrawable(const SGDirectionalLightBin::Light& light)
263 osg::Vec3Array* vertices = new osg::Vec3Array;
264 osg::Vec4Array* colors = new osg::Vec4Array;
266 SGVec4f visibleColor(light.color);
267 SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
269 SGVec3f normal = normalize(light.normal);
270 SGVec3f perp1 = perpendicular(normal);
271 SGVec3f perp2 = cross(normal, perp1);
272 SGVec3f position = light.position;
273 vertices->push_back(position.osg());
274 vertices->push_back((position + perp1).osg());
275 vertices->push_back((position + perp2).osg());
276 colors->push_back(visibleColor.osg());
277 colors->push_back(invisibleColor.osg());
278 colors->push_back(invisibleColor.osg());
280 osg::Geometry* geometry = new osg::Geometry;
281 geometry->setVertexArray(vertices);
282 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
283 geometry->setColorArray(colors);
284 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
285 // Enlarge the bounding box to avoid such light nodes being victim to
286 // small feature culling.
287 geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
289 osg::DrawArrays* drawArrays;
290 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
291 0, vertices->size());
292 geometry->addPrimitiveSet(drawArrays);
298 ref_ptr<StateSet> simpleLightSS;
301 SGLightFactory::getLights(const SGLightBin& lights, unsigned inc, float alphaOff)
303 if (lights.getNumLights() <= 0)
306 osg::Vec3Array* vertices = new osg::Vec3Array;
307 osg::Vec4Array* colors = new osg::Vec4Array;
309 for (unsigned i = 0; i < lights.getNumLights(); i += inc) {
310 vertices->push_back(lights.getLight(i).position.osg());
311 SGVec4f color = lights.getLight(i).color;
312 color[3] = SGMiscf::max(0, SGMiscf::min(1, color[3] + alphaOff));
313 colors->push_back(color.osg());
316 osg::Geometry* geometry = new osg::Geometry;
318 geometry->setVertexArray(vertices);
319 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
320 geometry->setColorArray(colors);
321 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
323 osg::DrawArrays* drawArrays;
324 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
325 0, vertices->size());
326 geometry->addPrimitiveSet(drawArrays);
329 ScopedLock<Mutex> lock(lightMutex);
330 if (!simpleLightSS.valid()) {
331 StateAttributeFactory *attrFact = StateAttributeFactory::instance();
332 simpleLightSS = new StateSet;
333 simpleLightSS->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
334 simpleLightSS->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
335 simpleLightSS->setAttributeAndModes(attrFact->getStandardBlendFunc());
336 simpleLightSS->setAttributeAndModes(attrFact->getStandardAlphaFunc());
339 geometry->setStateSet(simpleLightSS.get());
345 SGLightFactory::getLights(const SGDirectionalLightBin& lights)
347 if (lights.getNumLights() <= 0)
350 osg::Vec3Array* vertices = new osg::Vec3Array;
351 osg::Vec4Array* colors = new osg::Vec4Array;
353 for (unsigned i = 0; i < lights.getNumLights(); ++i) {
354 SGVec4f visibleColor(lights.getLight(i).color);
355 SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
357 SGVec3f normal = normalize(lights.getLight(i).normal);
358 SGVec3f perp1 = perpendicular(normal);
359 SGVec3f perp2 = cross(normal, perp1);
360 SGVec3f position = lights.getLight(i).position;
361 vertices->push_back(position.osg());
362 vertices->push_back((position + perp1).osg());
363 vertices->push_back((position + perp2).osg());
364 colors->push_back(visibleColor.osg());
365 colors->push_back(invisibleColor.osg());
366 colors->push_back(invisibleColor.osg());
369 osg::Geometry* geometry = new osg::Geometry;
371 geometry->setVertexArray(vertices);
372 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
373 geometry->setColorArray(colors);
374 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
376 osg::DrawArrays* drawArrays;
377 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
378 0, vertices->size());
379 geometry->addPrimitiveSet(drawArrays);
383 static SGVasiDrawable*
384 buildVasi(const SGDirectionalLightBin& lights, const SGVec3f& up,
385 const SGVec4f& red, const SGVec4f& white)
387 unsigned count = lights.getNumLights();
389 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
391 // PAPI configuration
393 drawable->addLight(lights.getLight(0).position,
394 lights.getLight(0).normal, up, 3.5);
396 drawable->addLight(lights.getLight(1).position,
397 lights.getLight(1).normal, up, 3.167);
399 drawable->addLight(lights.getLight(2).position,
400 lights.getLight(2).normal, up, 2.833);
402 drawable->addLight(lights.getLight(3).position,
403 lights.getLight(3).normal, up, 2.5);
406 else if (count == 12) {
407 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
409 // probably vasi, first 6 are downwind bar (2.5 deg)
410 for (unsigned i = 0; i < 6; ++i)
411 drawable->addLight(lights.getLight(i).position,
412 lights.getLight(i).normal, up, 2.5);
413 // last 6 are upwind bar (3.0 deg)
414 for (unsigned i = 6; i < 12; ++i)
415 drawable->addLight(lights.getLight(i).position,
416 lights.getLight(i).normal, up, 3.0);
421 SG_LOG(SG_TERRAIN, SG_ALERT,
422 "unknown vasi/papi configuration, count = " << count);
428 SGLightFactory::getVasi(const SGVec3f& up, const SGDirectionalLightBin& lights,
429 const SGVec4f& red, const SGVec4f& white)
431 SGVasiDrawable* drawable = buildVasi(lights, up, red, white);
435 osg::StateSet* stateSet = drawable->getOrCreateStateSet();
436 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
437 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
439 osg::BlendFunc* blendFunc = new osg::BlendFunc;
440 stateSet->setAttribute(blendFunc);
441 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
443 osg::AlphaFunc* alphaFunc;
444 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
445 stateSet->setAttribute(alphaFunc);
446 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
452 SGLightFactory::getSequenced(const SGDirectionalLightBin& lights)
454 if (lights.getNumLights() <= 0)
457 // generate a repeatable random seed
458 sg_srandom(unsigned(lights.getLight(0).position[0]));
459 float flashTime = 2e-2 + 5e-3*sg_random();
460 osg::Sequence* sequence = new osg::Sequence;
461 sequence->setDefaultTime(flashTime);
462 Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.0001, 0.00000001),
464 for (int i = lights.getNumLights() - 1; 0 <= i; --i) {
465 EffectGeode* egeode = new EffectGeode;
466 egeode->setEffect(effect);
467 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
468 sequence->addChild(egeode, flashTime);
470 sequence->addChild(new osg::Group, 1 + 1e-1*sg_random());
471 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
472 sequence->setDuration(1.0f, -1);
473 sequence->setMode(osg::Sequence::START);
474 sequence->setSync(true);
479 SGLightFactory::getOdal(const SGLightBin& lights)
481 if (lights.getNumLights() < 2)
484 // generate a repeatable random seed
485 sg_srandom(unsigned(lights.getLight(0).position[0]));
486 float flashTime = 2e-2 + 5e-3*sg_random();
487 osg::Sequence* sequence = new osg::Sequence;
488 sequence->setDefaultTime(flashTime);
489 Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.0001, 0.00000001),
492 for (int i = lights.getNumLights() - 1; 2 <= i; --i) {
493 EffectGeode* egeode = new EffectGeode;
494 egeode->setEffect(effect);
495 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
496 sequence->addChild(egeode, flashTime);
499 osg::Group* group = new osg::Group;
500 for (unsigned i = 0; i < 2; ++i) {
501 EffectGeode* egeode = new EffectGeode;
502 egeode->setEffect(effect);
503 egeode->addDrawable(getLightDrawable(lights.getLight(i)));
504 group->addChild(egeode);
506 sequence->addChild(group, flashTime);
508 // add an extra empty group for a break
509 sequence->addChild(new osg::Group, 9 + 1e-1*sg_random());
510 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
511 sequence->setDuration(1.0f, -1);
512 sequence->setMode(osg::Sequence::START);
513 sequence->setSync(true);