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/SGEnlargeBoundingBox.hxx>
59 #include "SGVasiDrawable.hxx"
62 setPointSpriteImage(unsigned char* data, unsigned log2resolution,
63 unsigned charsPerPixel)
65 int env_tex_res = (1 << log2resolution);
66 for (int i = 0; i < env_tex_res; ++i) {
67 for (int j = 0; j < env_tex_res; ++j) {
68 int xi = 2*i + 1 - env_tex_res;
69 int yi = 2*j + 1 - env_tex_res;
83 float x = 1.5*xi/(float)(env_tex_res);
84 float y = 1.5*yi/(float)(env_tex_res);
85 // float x = 2*xi/(float)(env_tex_res);
86 // float y = 2*yi/(float)(env_tex_res);
87 float dist = sqrt(x*x + y*y);
88 float bright = SGMiscf::clip(255*(1-dist), 0, 255);
89 for (unsigned l = 0; l < charsPerPixel; ++l)
90 data[charsPerPixel*(i*env_tex_res + j) + l] = (unsigned char)bright;
96 getPointSpriteImage(int logResolution)
98 osg::Image* image = new osg::Image;
100 osg::Image::MipmapDataType mipmapOffsets;
102 for (int i = logResolution; 0 <= i; --i) {
103 unsigned res = 1 << i;
105 mipmapOffsets.push_back(off);
108 int env_tex_res = (1 << logResolution);
110 unsigned char* imageData = new unsigned char[off];
111 image->setImage(env_tex_res, env_tex_res, 1,
112 GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE, imageData,
113 osg::Image::USE_NEW_DELETE);
114 image->setMipmapLevels(mipmapOffsets);
116 for (int k = logResolution; 0 <= k; --k) {
117 setPointSpriteImage(image->getMipmapData(logResolution - k), k, 1);
123 static osg::Texture2D*
124 gen_standard_light_sprite(void)
126 // double checked locking ...
127 static osg::ref_ptr<osg::Texture2D> texture;
129 return texture.get();
131 static SGMutex mutex;
132 SGGuard<SGMutex> guard(mutex);
134 return texture.get();
136 texture = new osg::Texture2D;
137 texture->setImage(getPointSpriteImage(6));
138 texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
139 texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
141 return texture.get();
144 SGPointSpriteLightCullCallback::SGPointSpriteLightCullCallback(const osg::Vec3& da,
146 _pointSpriteStateSet(new osg::StateSet),
147 _distanceAttenuationStateSet(new osg::StateSet)
149 osg::PointSprite* pointSprite = new osg::PointSprite;
150 _pointSpriteStateSet->setTextureAttributeAndModes(0, pointSprite,
151 osg::StateAttribute::ON);
152 osg::Texture2D* texture = gen_standard_light_sprite();
153 _pointSpriteStateSet->setTextureAttribute(0, texture);
154 _pointSpriteStateSet->setTextureMode(0, GL_TEXTURE_2D,
155 osg::StateAttribute::ON);
156 osg::TexEnv* texEnv = new osg::TexEnv;
157 texEnv->setMode(osg::TexEnv::MODULATE);
158 _pointSpriteStateSet->setTextureAttribute(0, texEnv);
160 osg::Point* point = new osg::Point;
161 point->setFadeThresholdSize(1);
162 point->setMinSize(1);
163 point->setMaxSize(sz);
165 point->setDistanceAttenuation(da);
166 _distanceAttenuationStateSet->setAttributeAndModes(point);
169 // FIXME make state sets static
170 SGPointSpriteLightCullCallback::SGPointSpriteLightCullCallback(osg::Point* point) :
171 _pointSpriteStateSet(new osg::StateSet),
172 _distanceAttenuationStateSet(new osg::StateSet)
174 osg::PointSprite* pointSprite = new osg::PointSprite;
175 _pointSpriteStateSet->setTextureAttributeAndModes(0, pointSprite,
176 osg::StateAttribute::ON);
177 osg::Texture2D* texture = gen_standard_light_sprite();
178 _pointSpriteStateSet->setTextureAttribute(0, texture);
179 _pointSpriteStateSet->setTextureMode(0, GL_TEXTURE_2D,
180 osg::StateAttribute::ON);
181 osg::TexEnv* texEnv = new osg::TexEnv;
182 texEnv->setMode(osg::TexEnv::MODULATE);
183 _pointSpriteStateSet->setTextureAttribute(0, texEnv);
185 _distanceAttenuationStateSet->setAttributeAndModes(point);
189 SGPointSpriteLightCullCallback::operator()(osg::Node* node,
190 osg::NodeVisitor* nv)
192 assert(dynamic_cast<osgUtil::CullVisitor*>(nv));
193 osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(nv);
195 // Test for point sprites and point parameters availibility
196 unsigned contextId = cv->getRenderInfo().getContextID();
197 SGSceneFeatures* features = SGSceneFeatures::instance();
198 bool usePointSprite = features->getEnablePointSpriteLights(contextId);
199 bool usePointParameters = features->getEnableDistanceAttenuationLights(contextId);
202 cv->pushStateSet(_pointSpriteStateSet.get());
204 if (usePointParameters)
205 cv->pushStateSet(_distanceAttenuationStateSet.get());
209 if (usePointParameters)
217 SGLightFactory::getLight(const SGLightBin::Light& light)
219 osg::Vec3Array* vertices = new osg::Vec3Array;
220 osg::Vec4Array* colors = new osg::Vec4Array;
222 vertices->push_back(light.position.osg());
223 colors->push_back(light.color.osg());
225 osg::Geometry* geometry = new osg::Geometry;
226 geometry->setVertexArray(vertices);
227 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
228 geometry->setColorArray(colors);
229 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
231 // Enlarge the bounding box to avoid such light nodes being victim to
232 // small feature culling.
233 geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
235 osg::DrawArrays* drawArrays;
236 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
237 0, vertices->size());
238 geometry->addPrimitiveSet(drawArrays);
240 osg::StateSet* stateSet = geometry->getOrCreateStateSet();
241 stateSet->setRenderBinDetails(9, "DepthSortedBin");
242 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
244 osg::BlendFunc* blendFunc = new osg::BlendFunc;
245 stateSet->setAttribute(blendFunc);
246 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
248 osg::AlphaFunc* alphaFunc;
249 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
250 stateSet->setAttribute(alphaFunc);
251 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
253 osg::Geode* geode = new osg::Geode;
254 geode->addDrawable(geometry);
260 SGLightFactory::getLight(const SGDirectionalLightBin::Light& light)
262 osg::Vec3Array* vertices = new osg::Vec3Array;
263 osg::Vec4Array* colors = new osg::Vec4Array;
265 SGVec4f visibleColor(light.color);
266 SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
268 SGVec3f normal = normalize(light.normal);
269 SGVec3f perp1 = perpendicular(normal);
270 SGVec3f perp2 = cross(normal, perp1);
271 SGVec3f position = light.position;
272 vertices->push_back(position.osg());
273 vertices->push_back((position + perp1).osg());
274 vertices->push_back((position + perp2).osg());
275 colors->push_back(visibleColor.osg());
276 colors->push_back(invisibleColor.osg());
277 colors->push_back(invisibleColor.osg());
279 osg::Geometry* geometry = new osg::Geometry;
280 geometry->setVertexArray(vertices);
281 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
282 geometry->setColorArray(colors);
283 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
284 // Enlarge the bounding box to avoid such light nodes being victim to
285 // small feature culling.
286 geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
288 osg::DrawArrays* drawArrays;
289 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
290 0, vertices->size());
291 geometry->addPrimitiveSet(drawArrays);
293 osg::StateSet* stateSet = geometry->getOrCreateStateSet();
294 stateSet->setRenderBinDetails(9, "DepthSortedBin");
296 osg::Material* material = new osg::Material;
297 material->setColorMode(osg::Material::OFF);
298 stateSet->setAttribute(material);
300 osg::CullFace* cullFace = new osg::CullFace;
301 cullFace->setMode(osg::CullFace::BACK);
302 stateSet->setAttribute(cullFace, osg::StateAttribute::ON);
303 stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::ON);
305 osg::PolygonMode* polygonMode = new osg::PolygonMode;
306 polygonMode->setMode(osg::PolygonMode::FRONT, osg::PolygonMode::POINT);
307 stateSet->setAttribute(polygonMode);
309 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
311 osg::BlendFunc* blendFunc = new osg::BlendFunc;
312 stateSet->setAttribute(blendFunc);
313 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
315 osg::AlphaFunc* alphaFunc;
316 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
317 stateSet->setAttribute(alphaFunc);
318 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
320 osg::Geode* geode = new osg::Geode;
321 geode->addDrawable(geometry);
327 SGLightFactory::getLights(const SGLightBin& lights, unsigned inc, float alphaOff)
329 if (lights.getNumLights() <= 0)
332 osg::Vec3Array* vertices = new osg::Vec3Array;
333 osg::Vec4Array* colors = new osg::Vec4Array;
335 for (unsigned i = 0; i < lights.getNumLights(); i += inc) {
336 vertices->push_back(lights.getLight(i).position.osg());
337 SGVec4f color = lights.getLight(i).color;
338 color[3] = SGMiscf::max(0, SGMiscf::min(1, color[3] + alphaOff));
339 colors->push_back(color.osg());
342 osg::Geometry* geometry = new osg::Geometry;
344 geometry->setVertexArray(vertices);
345 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
346 geometry->setColorArray(colors);
347 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
349 osg::DrawArrays* drawArrays;
350 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
351 0, vertices->size());
352 geometry->addPrimitiveSet(drawArrays);
354 osg::StateSet* stateSet = geometry->getOrCreateStateSet();
355 stateSet->setRenderBinDetails(9, "DepthSortedBin");
357 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
359 osg::BlendFunc* blendFunc = new osg::BlendFunc;
360 stateSet->setAttribute(blendFunc);
361 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
363 osg::AlphaFunc* alphaFunc;
364 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
365 stateSet->setAttribute(alphaFunc);
366 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
373 SGLightFactory::getLights(const SGDirectionalLightBin& lights)
375 if (lights.getNumLights() <= 0)
378 osg::Vec3Array* vertices = new osg::Vec3Array;
379 osg::Vec4Array* colors = new osg::Vec4Array;
381 for (unsigned i = 0; i < lights.getNumLights(); ++i) {
382 SGVec4f visibleColor(lights.getLight(i).color);
383 SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
385 SGVec3f normal = normalize(lights.getLight(i).normal);
386 SGVec3f perp1 = perpendicular(normal);
387 SGVec3f perp2 = cross(normal, perp1);
388 SGVec3f position = lights.getLight(i).position;
389 vertices->push_back(position.osg());
390 vertices->push_back((position + perp1).osg());
391 vertices->push_back((position + perp2).osg());
392 colors->push_back(visibleColor.osg());
393 colors->push_back(invisibleColor.osg());
394 colors->push_back(invisibleColor.osg());
397 osg::Geometry* geometry = new osg::Geometry;
399 geometry->setVertexArray(vertices);
400 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
401 geometry->setColorArray(colors);
402 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
404 osg::DrawArrays* drawArrays;
405 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
406 0, vertices->size());
407 geometry->addPrimitiveSet(drawArrays);
409 osg::StateSet* stateSet = geometry->getOrCreateStateSet();
410 stateSet->setRenderBinDetails(9, "DepthSortedBin");
412 osg::Material* material = new osg::Material;
413 material->setColorMode(osg::Material::OFF);
414 stateSet->setAttribute(material);
416 osg::CullFace* cullFace = new osg::CullFace;
417 cullFace->setMode(osg::CullFace::BACK);
418 stateSet->setAttribute(cullFace, osg::StateAttribute::ON);
419 stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::ON);
421 osg::PolygonMode* polygonMode = new osg::PolygonMode;
422 polygonMode->setMode(osg::PolygonMode::FRONT, osg::PolygonMode::POINT);
423 stateSet->setAttribute(polygonMode);
425 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
427 osg::BlendFunc* blendFunc = new osg::BlendFunc;
428 stateSet->setAttribute(blendFunc);
429 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
431 osg::AlphaFunc* alphaFunc;
432 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
433 stateSet->setAttribute(alphaFunc);
434 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
439 static SGVasiDrawable*
440 buildVasi(const SGDirectionalLightBin& lights, const SGVec3f& up,
441 const SGVec4f& red, const SGVec4f& white)
443 unsigned count = lights.getNumLights();
445 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
447 // PAPI configuration
449 drawable->addLight(lights.getLight(0).position,
450 lights.getLight(0).normal, up, 3.5);
452 drawable->addLight(lights.getLight(1).position,
453 lights.getLight(1).normal, up, 3.167);
455 drawable->addLight(lights.getLight(2).position,
456 lights.getLight(2).normal, up, 2.833);
458 drawable->addLight(lights.getLight(3).position,
459 lights.getLight(3).normal, up, 2.5);
462 else if (count == 12) {
463 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
465 // probably vasi, first 6 are downwind bar (2.5 deg)
466 for (unsigned i = 0; i < 6; ++i)
467 drawable->addLight(lights.getLight(i).position,
468 lights.getLight(i).normal, up, 2.5);
469 // last 6 are upwind bar (3.0 deg)
470 for (unsigned i = 6; i < 12; ++i)
471 drawable->addLight(lights.getLight(i).position,
472 lights.getLight(i).normal, up, 3.0);
477 SG_LOG(SG_TERRAIN, SG_ALERT,
478 "unknown vasi/papi configuration, count = " << count);
484 SGLightFactory::getVasi(const SGVec3f& up, const SGDirectionalLightBin& lights,
485 const SGVec4f& red, const SGVec4f& white)
487 SGVasiDrawable* drawable = buildVasi(lights, up, red, white);
491 osg::StateSet* stateSet = drawable->getOrCreateStateSet();
492 stateSet->setRenderBinDetails(9, "DepthSortedBin");
493 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
495 osg::BlendFunc* blendFunc = new osg::BlendFunc;
496 stateSet->setAttribute(blendFunc);
497 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
499 osg::AlphaFunc* alphaFunc;
500 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
501 stateSet->setAttribute(alphaFunc);
502 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
508 SGLightFactory::getSequenced(const SGDirectionalLightBin& lights)
510 if (lights.getNumLights() <= 0)
513 // generate a repeatable random seed
514 sg_srandom(unsigned(lights.getLight(0).position[0]));
515 float flashTime = 2e-2 + 5e-3*sg_random();
516 osg::Sequence* sequence = new osg::Sequence;
517 sequence->setDefaultTime(flashTime);
519 for (int i = lights.getNumLights() - 1; 0 <= i; --i)
520 sequence->addChild(getLight(lights.getLight(i)), flashTime);
521 sequence->addChild(new osg::Group, 1 + 1e-1*sg_random());
522 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
523 sequence->setDuration(1.0f, -1);
524 sequence->setMode(osg::Sequence::START);
525 sequence->setSync(true);
527 osg::StateSet* stateSet = sequence->getOrCreateStateSet();
528 stateSet->setRenderBinDetails(9, "DepthSortedBin");
529 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
531 osg::BlendFunc* blendFunc = new osg::BlendFunc;
532 stateSet->setAttribute(blendFunc);
533 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
535 osg::AlphaFunc* alphaFunc;
536 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
537 stateSet->setAttribute(alphaFunc);
538 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
540 osg::Point* point = new osg::Point;
541 point->setMinSize(6);
542 point->setMaxSize(10);
544 point->setDistanceAttenuation(osg::Vec3(1.0, 0.0001, 0.00000001));
545 sequence->setCullCallback(new SGPointSpriteLightCullCallback(point));
551 SGLightFactory::getOdal(const SGLightBin& lights)
553 if (lights.getNumLights() < 2)
556 // generate a repeatable random seed
557 sg_srandom(unsigned(lights.getLight(0).position[0]));
558 float flashTime = 2e-2 + 5e-3*sg_random();
559 osg::Sequence* sequence = new osg::Sequence;
560 sequence->setDefaultTime(flashTime);
563 for (int i = lights.getNumLights() - 1; 2 <= i; --i)
564 sequence->addChild(getLight(lights.getLight(i)), flashTime);
567 osg::Group* group = new osg::Group;
568 for (unsigned i = 0; i < 2; ++i)
569 group->addChild(getLight(lights.getLight(i)));
570 sequence->addChild(group, flashTime);
572 // add an extra empty group for a break
573 sequence->addChild(new osg::Group, 9 + 1e-1*sg_random());
574 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
575 sequence->setDuration(1.0f, -1);
576 sequence->setMode(osg::Sequence::START);
577 sequence->setSync(true);
579 osg::StateSet* stateSet = sequence->getOrCreateStateSet();
580 stateSet->setRenderBinDetails(9, "DepthSortedBin");
581 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
583 osg::BlendFunc* blendFunc = new osg::BlendFunc;
584 stateSet->setAttribute(blendFunc);
585 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
587 osg::AlphaFunc* alphaFunc;
588 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
589 stateSet->setAttribute(alphaFunc);
590 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
592 osg::Point* point = new osg::Point;
593 point->setMinSize(6);
594 point->setMaxSize(10);
596 point->setDistanceAttenuation(osg::Vec3(1.0, 0.0001, 0.00000001));
597 sequence->setCullCallback(new SGPointSpriteLightCullCallback(point));