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 <osg/Geometry>
31 #include <osg/CullFace>
33 #include <osg/MatrixTransform>
34 #include <osg/NodeCallback>
35 #include <osg/NodeVisitor>
36 #include <osg/Texture2D>
37 #include <osg/AlphaFunc>
38 #include <osg/BlendFunc>
40 #include <osg/Sequence>
41 #include <osg/PolygonMode>
43 #include <osg/FragmentProgram>
44 #include <osg/VertexProgram>
46 #include <osg/PointSprite>
47 #include <osg/Material>
49 #include <osg/StateSet>
51 #include <osgUtil/CullVisitor>
53 #include <simgear/math/sg_random.h>
54 #include <simgear/debug/logstream.hxx>
55 #include <simgear/threads/SGThread.hxx>
56 #include <simgear/threads/SGGuard.hxx>
57 #include <simgear/scene/util/RenderConstants.hxx>
58 #include <simgear/scene/util/SGEnlargeBoundingBox.hxx>
60 #include "SGVasiDrawable.hxx"
62 using namespace simgear;
65 setPointSpriteImage(unsigned char* data, unsigned log2resolution,
66 unsigned charsPerPixel)
68 int env_tex_res = (1 << log2resolution);
69 for (int i = 0; i < env_tex_res; ++i) {
70 for (int j = 0; j < env_tex_res; ++j) {
71 int xi = 2*i + 1 - env_tex_res;
72 int yi = 2*j + 1 - env_tex_res;
86 float x = 1.5*xi/(float)(env_tex_res);
87 float y = 1.5*yi/(float)(env_tex_res);
88 // float x = 2*xi/(float)(env_tex_res);
89 // float y = 2*yi/(float)(env_tex_res);
90 float dist = sqrt(x*x + y*y);
91 float bright = SGMiscf::clip(255*(1-dist), 0, 255);
92 for (unsigned l = 0; l < charsPerPixel; ++l)
93 data[charsPerPixel*(i*env_tex_res + j) + l] = (unsigned char)bright;
99 getPointSpriteImage(int logResolution)
101 osg::Image* image = new osg::Image;
103 osg::Image::MipmapDataType mipmapOffsets;
105 for (int i = logResolution; 0 <= i; --i) {
106 unsigned res = 1 << i;
108 mipmapOffsets.push_back(off);
111 int env_tex_res = (1 << logResolution);
113 unsigned char* imageData = new unsigned char[off];
114 image->setImage(env_tex_res, env_tex_res, 1,
115 GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE, imageData,
116 osg::Image::USE_NEW_DELETE);
117 image->setMipmapLevels(mipmapOffsets);
119 for (int k = logResolution; 0 <= k; --k) {
120 setPointSpriteImage(image->getMipmapData(logResolution - k), k, 1);
126 static osg::Texture2D*
127 gen_standard_light_sprite(void)
129 // double checked locking ...
130 static osg::ref_ptr<osg::Texture2D> texture;
132 return texture.get();
134 static SGMutex mutex;
135 SGGuard<SGMutex> guard(mutex);
137 return texture.get();
139 texture = new osg::Texture2D;
140 texture->setImage(getPointSpriteImage(6));
141 texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
142 texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
144 return texture.get();
147 SGPointSpriteLightCullCallback::SGPointSpriteLightCullCallback(const osg::Vec3& da,
149 _pointSpriteStateSet(new osg::StateSet),
150 _distanceAttenuationStateSet(new osg::StateSet)
152 osg::PointSprite* pointSprite = new osg::PointSprite;
153 _pointSpriteStateSet->setTextureAttributeAndModes(0, pointSprite,
154 osg::StateAttribute::ON);
155 osg::Texture2D* texture = gen_standard_light_sprite();
156 _pointSpriteStateSet->setTextureAttribute(0, texture);
157 _pointSpriteStateSet->setTextureMode(0, GL_TEXTURE_2D,
158 osg::StateAttribute::ON);
159 osg::TexEnv* texEnv = new osg::TexEnv;
160 texEnv->setMode(osg::TexEnv::MODULATE);
161 _pointSpriteStateSet->setTextureAttribute(0, texEnv);
163 osg::Point* point = new osg::Point;
164 point->setFadeThresholdSize(1);
165 point->setMinSize(1);
166 point->setMaxSize(sz);
168 point->setDistanceAttenuation(da);
169 _distanceAttenuationStateSet->setAttributeAndModes(point);
172 // FIXME make state sets static
173 SGPointSpriteLightCullCallback::SGPointSpriteLightCullCallback(osg::Point* point) :
174 _pointSpriteStateSet(new osg::StateSet),
175 _distanceAttenuationStateSet(new osg::StateSet)
177 osg::PointSprite* pointSprite = new osg::PointSprite;
178 _pointSpriteStateSet->setTextureAttributeAndModes(0, pointSprite,
179 osg::StateAttribute::ON);
180 osg::Texture2D* texture = gen_standard_light_sprite();
181 _pointSpriteStateSet->setTextureAttribute(0, texture);
182 _pointSpriteStateSet->setTextureMode(0, GL_TEXTURE_2D,
183 osg::StateAttribute::ON);
184 osg::TexEnv* texEnv = new osg::TexEnv;
185 texEnv->setMode(osg::TexEnv::MODULATE);
186 _pointSpriteStateSet->setTextureAttribute(0, texEnv);
188 _distanceAttenuationStateSet->setAttributeAndModes(point);
192 SGPointSpriteLightCullCallback::operator()(osg::Node* node,
193 osg::NodeVisitor* nv)
195 assert(dynamic_cast<osgUtil::CullVisitor*>(nv));
196 osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(nv);
198 // Test for point sprites and point parameters availibility
199 unsigned contextId = cv->getRenderInfo().getContextID();
200 SGSceneFeatures* features = SGSceneFeatures::instance();
201 bool usePointSprite = features->getEnablePointSpriteLights(contextId);
202 bool usePointParameters = features->getEnableDistanceAttenuationLights(contextId);
205 cv->pushStateSet(_pointSpriteStateSet.get());
207 if (usePointParameters)
208 cv->pushStateSet(_distanceAttenuationStateSet.get());
212 if (usePointParameters)
220 SGLightFactory::getLight(const SGLightBin::Light& light)
222 osg::Vec3Array* vertices = new osg::Vec3Array;
223 osg::Vec4Array* colors = new osg::Vec4Array;
225 vertices->push_back(light.position.osg());
226 colors->push_back(light.color.osg());
228 osg::Geometry* geometry = new osg::Geometry;
229 geometry->setVertexArray(vertices);
230 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
231 geometry->setColorArray(colors);
232 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
234 // Enlarge the bounding box to avoid such light nodes being victim to
235 // small feature culling.
236 geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
238 osg::DrawArrays* drawArrays;
239 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
240 0, vertices->size());
241 geometry->addPrimitiveSet(drawArrays);
243 osg::StateSet* stateSet = geometry->getOrCreateStateSet();
244 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
245 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
247 osg::BlendFunc* blendFunc = new osg::BlendFunc;
248 stateSet->setAttribute(blendFunc);
249 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
251 osg::AlphaFunc* alphaFunc;
252 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
253 stateSet->setAttribute(alphaFunc);
254 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
256 osg::Geode* geode = new osg::Geode;
257 geode->addDrawable(geometry);
263 SGLightFactory::getLight(const SGDirectionalLightBin::Light& light)
265 osg::Vec3Array* vertices = new osg::Vec3Array;
266 osg::Vec4Array* colors = new osg::Vec4Array;
268 SGVec4f visibleColor(light.color);
269 SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
271 SGVec3f normal = normalize(light.normal);
272 SGVec3f perp1 = perpendicular(normal);
273 SGVec3f perp2 = cross(normal, perp1);
274 SGVec3f position = light.position;
275 vertices->push_back(position.osg());
276 vertices->push_back((position + perp1).osg());
277 vertices->push_back((position + perp2).osg());
278 colors->push_back(visibleColor.osg());
279 colors->push_back(invisibleColor.osg());
280 colors->push_back(invisibleColor.osg());
282 osg::Geometry* geometry = new osg::Geometry;
283 geometry->setVertexArray(vertices);
284 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
285 geometry->setColorArray(colors);
286 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
287 // Enlarge the bounding box to avoid such light nodes being victim to
288 // small feature culling.
289 geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
291 osg::DrawArrays* drawArrays;
292 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
293 0, vertices->size());
294 geometry->addPrimitiveSet(drawArrays);
296 osg::StateSet* stateSet = geometry->getOrCreateStateSet();
297 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
299 osg::Material* material = new osg::Material;
300 material->setColorMode(osg::Material::OFF);
301 stateSet->setAttribute(material);
303 osg::CullFace* cullFace = new osg::CullFace;
304 cullFace->setMode(osg::CullFace::BACK);
305 stateSet->setAttribute(cullFace, osg::StateAttribute::ON);
306 stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::ON);
308 osg::PolygonMode* polygonMode = new osg::PolygonMode;
309 polygonMode->setMode(osg::PolygonMode::FRONT, osg::PolygonMode::POINT);
310 stateSet->setAttribute(polygonMode);
312 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
314 osg::BlendFunc* blendFunc = new osg::BlendFunc;
315 stateSet->setAttribute(blendFunc);
316 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
318 osg::AlphaFunc* alphaFunc;
319 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
320 stateSet->setAttribute(alphaFunc);
321 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
323 osg::Geode* geode = new osg::Geode;
324 geode->addDrawable(geometry);
330 SGLightFactory::getLights(const SGLightBin& lights, unsigned inc, float alphaOff)
332 if (lights.getNumLights() <= 0)
335 osg::Vec3Array* vertices = new osg::Vec3Array;
336 osg::Vec4Array* colors = new osg::Vec4Array;
338 for (unsigned i = 0; i < lights.getNumLights(); i += inc) {
339 vertices->push_back(lights.getLight(i).position.osg());
340 SGVec4f color = lights.getLight(i).color;
341 color[3] = SGMiscf::max(0, SGMiscf::min(1, color[3] + alphaOff));
342 colors->push_back(color.osg());
345 osg::Geometry* geometry = new osg::Geometry;
347 geometry->setVertexArray(vertices);
348 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
349 geometry->setColorArray(colors);
350 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
352 osg::DrawArrays* drawArrays;
353 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
354 0, vertices->size());
355 geometry->addPrimitiveSet(drawArrays);
357 osg::StateSet* stateSet = geometry->getOrCreateStateSet();
358 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
360 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
362 osg::BlendFunc* blendFunc = new osg::BlendFunc;
363 stateSet->setAttribute(blendFunc);
364 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
366 osg::AlphaFunc* alphaFunc;
367 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
368 stateSet->setAttribute(alphaFunc);
369 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
376 SGLightFactory::getLights(const SGDirectionalLightBin& lights)
378 if (lights.getNumLights() <= 0)
381 osg::Vec3Array* vertices = new osg::Vec3Array;
382 osg::Vec4Array* colors = new osg::Vec4Array;
384 for (unsigned i = 0; i < lights.getNumLights(); ++i) {
385 SGVec4f visibleColor(lights.getLight(i).color);
386 SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
388 SGVec3f normal = normalize(lights.getLight(i).normal);
389 SGVec3f perp1 = perpendicular(normal);
390 SGVec3f perp2 = cross(normal, perp1);
391 SGVec3f position = lights.getLight(i).position;
392 vertices->push_back(position.osg());
393 vertices->push_back((position + perp1).osg());
394 vertices->push_back((position + perp2).osg());
395 colors->push_back(visibleColor.osg());
396 colors->push_back(invisibleColor.osg());
397 colors->push_back(invisibleColor.osg());
400 osg::Geometry* geometry = new osg::Geometry;
402 geometry->setVertexArray(vertices);
403 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
404 geometry->setColorArray(colors);
405 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
407 osg::DrawArrays* drawArrays;
408 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
409 0, vertices->size());
410 geometry->addPrimitiveSet(drawArrays);
412 osg::StateSet* stateSet = geometry->getOrCreateStateSet();
413 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
415 osg::Material* material = new osg::Material;
416 material->setColorMode(osg::Material::OFF);
417 stateSet->setAttribute(material);
419 osg::CullFace* cullFace = new osg::CullFace;
420 cullFace->setMode(osg::CullFace::BACK);
421 stateSet->setAttribute(cullFace, osg::StateAttribute::ON);
422 stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::ON);
424 osg::PolygonMode* polygonMode = new osg::PolygonMode;
425 polygonMode->setMode(osg::PolygonMode::FRONT, osg::PolygonMode::POINT);
426 stateSet->setAttribute(polygonMode);
428 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
430 osg::BlendFunc* blendFunc = new osg::BlendFunc;
431 stateSet->setAttribute(blendFunc);
432 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
434 osg::AlphaFunc* alphaFunc;
435 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
436 stateSet->setAttribute(alphaFunc);
437 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
442 static SGVasiDrawable*
443 buildVasi(const SGDirectionalLightBin& lights, const SGVec3f& up,
444 const SGVec4f& red, const SGVec4f& white)
446 unsigned count = lights.getNumLights();
448 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
450 // PAPI configuration
452 drawable->addLight(lights.getLight(0).position,
453 lights.getLight(0).normal, up, 3.5);
455 drawable->addLight(lights.getLight(1).position,
456 lights.getLight(1).normal, up, 3.167);
458 drawable->addLight(lights.getLight(2).position,
459 lights.getLight(2).normal, up, 2.833);
461 drawable->addLight(lights.getLight(3).position,
462 lights.getLight(3).normal, up, 2.5);
465 else if (count == 12) {
466 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
468 // probably vasi, first 6 are downwind bar (2.5 deg)
469 for (unsigned i = 0; i < 6; ++i)
470 drawable->addLight(lights.getLight(i).position,
471 lights.getLight(i).normal, up, 2.5);
472 // last 6 are upwind bar (3.0 deg)
473 for (unsigned i = 6; i < 12; ++i)
474 drawable->addLight(lights.getLight(i).position,
475 lights.getLight(i).normal, up, 3.0);
480 SG_LOG(SG_TERRAIN, SG_ALERT,
481 "unknown vasi/papi configuration, count = " << count);
487 SGLightFactory::getVasi(const SGVec3f& up, const SGDirectionalLightBin& lights,
488 const SGVec4f& red, const SGVec4f& white)
490 SGVasiDrawable* drawable = buildVasi(lights, up, red, white);
494 osg::StateSet* stateSet = drawable->getOrCreateStateSet();
495 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
496 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
498 osg::BlendFunc* blendFunc = new osg::BlendFunc;
499 stateSet->setAttribute(blendFunc);
500 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
502 osg::AlphaFunc* alphaFunc;
503 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
504 stateSet->setAttribute(alphaFunc);
505 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
511 SGLightFactory::getSequenced(const SGDirectionalLightBin& lights)
513 if (lights.getNumLights() <= 0)
516 // generate a repeatable random seed
517 sg_srandom(unsigned(lights.getLight(0).position[0]));
518 float flashTime = 2e-2 + 5e-3*sg_random();
519 osg::Sequence* sequence = new osg::Sequence;
520 sequence->setDefaultTime(flashTime);
522 for (int i = lights.getNumLights() - 1; 0 <= i; --i)
523 sequence->addChild(getLight(lights.getLight(i)), flashTime);
524 sequence->addChild(new osg::Group, 1 + 1e-1*sg_random());
525 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
526 sequence->setDuration(1.0f, -1);
527 sequence->setMode(osg::Sequence::START);
528 sequence->setSync(true);
530 osg::StateSet* stateSet = sequence->getOrCreateStateSet();
531 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
532 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
534 osg::BlendFunc* blendFunc = new osg::BlendFunc;
535 stateSet->setAttribute(blendFunc);
536 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
538 osg::AlphaFunc* alphaFunc;
539 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
540 stateSet->setAttribute(alphaFunc);
541 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
543 osg::Point* point = new osg::Point;
544 point->setMinSize(6);
545 point->setMaxSize(10);
547 point->setDistanceAttenuation(osg::Vec3(1.0, 0.0001, 0.00000001));
548 sequence->setCullCallback(new SGPointSpriteLightCullCallback(point));
554 SGLightFactory::getOdal(const SGLightBin& lights)
556 if (lights.getNumLights() < 2)
559 // generate a repeatable random seed
560 sg_srandom(unsigned(lights.getLight(0).position[0]));
561 float flashTime = 2e-2 + 5e-3*sg_random();
562 osg::Sequence* sequence = new osg::Sequence;
563 sequence->setDefaultTime(flashTime);
566 for (int i = lights.getNumLights() - 1; 2 <= i; --i)
567 sequence->addChild(getLight(lights.getLight(i)), flashTime);
570 osg::Group* group = new osg::Group;
571 for (unsigned i = 0; i < 2; ++i)
572 group->addChild(getLight(lights.getLight(i)));
573 sequence->addChild(group, flashTime);
575 // add an extra empty group for a break
576 sequence->addChild(new osg::Group, 9 + 1e-1*sg_random());
577 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
578 sequence->setDuration(1.0f, -1);
579 sequence->setMode(osg::Sequence::START);
580 sequence->setSync(true);
582 osg::StateSet* stateSet = sequence->getOrCreateStateSet();
583 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
584 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
586 osg::BlendFunc* blendFunc = new osg::BlendFunc;
587 stateSet->setAttribute(blendFunc);
588 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
590 osg::AlphaFunc* alphaFunc;
591 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
592 stateSet->setAttribute(alphaFunc);
593 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
595 osg::Point* point = new osg::Point;
596 point->setMinSize(6);
597 point->setMaxSize(10);
599 point->setDistanceAttenuation(osg::Vec3(1.0, 0.0001, 0.00000001));
600 sequence->setCullCallback(new SGPointSpriteLightCullCallback(point));