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 <OpenThreads/Mutex>
54 #include <OpenThreads/ScopedLock>
56 #include <simgear/math/sg_random.h>
57 #include <simgear/debug/logstream.hxx>
58 #include <simgear/scene/util/RenderConstants.hxx>
59 #include <simgear/scene/util/SGEnlargeBoundingBox.hxx>
61 #include "SGVasiDrawable.hxx"
63 using OpenThreads::Mutex;
64 using OpenThreads::ScopedLock;
66 using namespace simgear;
69 setPointSpriteImage(unsigned char* data, unsigned log2resolution,
70 unsigned charsPerPixel)
72 int env_tex_res = (1 << log2resolution);
73 for (int i = 0; i < env_tex_res; ++i) {
74 for (int j = 0; j < env_tex_res; ++j) {
75 int xi = 2*i + 1 - env_tex_res;
76 int yi = 2*j + 1 - env_tex_res;
90 float x = 1.5*xi/(float)(env_tex_res);
91 float y = 1.5*yi/(float)(env_tex_res);
92 // float x = 2*xi/(float)(env_tex_res);
93 // float y = 2*yi/(float)(env_tex_res);
94 float dist = sqrt(x*x + y*y);
95 float bright = SGMiscf::clip(255*(1-dist), 0, 255);
96 for (unsigned l = 0; l < charsPerPixel; ++l)
97 data[charsPerPixel*(i*env_tex_res + j) + l] = (unsigned char)bright;
103 getPointSpriteImage(int logResolution)
105 osg::Image* image = new osg::Image;
107 osg::Image::MipmapDataType mipmapOffsets;
109 for (int i = logResolution; 0 <= i; --i) {
110 unsigned res = 1 << i;
112 mipmapOffsets.push_back(off);
115 int env_tex_res = (1 << logResolution);
117 unsigned char* imageData = new unsigned char[off];
118 image->setImage(env_tex_res, env_tex_res, 1,
119 GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE, imageData,
120 osg::Image::USE_NEW_DELETE);
121 image->setMipmapLevels(mipmapOffsets);
123 for (int k = logResolution; 0 <= k; --k) {
124 setPointSpriteImage(image->getMipmapData(logResolution - k), k, 1);
130 static Mutex lightMutex;
132 static osg::Texture2D*
133 gen_standard_light_sprite(void)
135 // double checked locking ...
136 static osg::ref_ptr<osg::Texture2D> texture;
138 return texture.get();
140 ScopedLock<Mutex> lock(lightMutex);
142 return texture.get();
144 texture = new osg::Texture2D;
145 texture->setImage(getPointSpriteImage(6));
146 texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
147 texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
149 return texture.get();
152 SGPointSpriteLightCullCallback::SGPointSpriteLightCullCallback(const osg::Vec3& da,
154 _pointSpriteStateSet(new osg::StateSet),
155 _distanceAttenuationStateSet(new osg::StateSet)
157 osg::PointSprite* pointSprite = new osg::PointSprite;
158 _pointSpriteStateSet->setTextureAttributeAndModes(0, pointSprite,
159 osg::StateAttribute::ON);
160 osg::Texture2D* texture = gen_standard_light_sprite();
161 _pointSpriteStateSet->setTextureAttribute(0, texture);
162 _pointSpriteStateSet->setTextureMode(0, GL_TEXTURE_2D,
163 osg::StateAttribute::ON);
164 osg::TexEnv* texEnv = new osg::TexEnv;
165 texEnv->setMode(osg::TexEnv::MODULATE);
166 _pointSpriteStateSet->setTextureAttribute(0, texEnv);
168 osg::Point* point = new osg::Point;
169 point->setFadeThresholdSize(1);
170 point->setMinSize(1);
171 point->setMaxSize(sz);
173 point->setDistanceAttenuation(da);
174 _distanceAttenuationStateSet->setAttributeAndModes(point);
177 // FIXME make state sets static
178 SGPointSpriteLightCullCallback::SGPointSpriteLightCullCallback(osg::Point* point) :
179 _pointSpriteStateSet(new osg::StateSet),
180 _distanceAttenuationStateSet(new osg::StateSet)
182 osg::PointSprite* pointSprite = new osg::PointSprite;
183 _pointSpriteStateSet->setTextureAttributeAndModes(0, pointSprite,
184 osg::StateAttribute::ON);
185 osg::Texture2D* texture = gen_standard_light_sprite();
186 _pointSpriteStateSet->setTextureAttribute(0, texture);
187 _pointSpriteStateSet->setTextureMode(0, GL_TEXTURE_2D,
188 osg::StateAttribute::ON);
189 osg::TexEnv* texEnv = new osg::TexEnv;
190 texEnv->setMode(osg::TexEnv::MODULATE);
191 _pointSpriteStateSet->setTextureAttribute(0, texEnv);
193 _distanceAttenuationStateSet->setAttributeAndModes(point);
197 SGPointSpriteLightCullCallback::operator()(osg::Node* node,
198 osg::NodeVisitor* nv)
200 assert(dynamic_cast<osgUtil::CullVisitor*>(nv));
201 osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(nv);
203 // Test for point sprites and point parameters availibility
204 unsigned contextId = cv->getRenderInfo().getContextID();
205 SGSceneFeatures* features = SGSceneFeatures::instance();
206 bool usePointSprite = features->getEnablePointSpriteLights(contextId);
207 bool usePointParameters = features->getEnableDistanceAttenuationLights(contextId);
210 cv->pushStateSet(_pointSpriteStateSet.get());
212 if (usePointParameters)
213 cv->pushStateSet(_distanceAttenuationStateSet.get());
217 if (usePointParameters)
225 SGLightFactory::getLight(const SGLightBin::Light& light)
227 osg::Vec3Array* vertices = new osg::Vec3Array;
228 osg::Vec4Array* colors = new osg::Vec4Array;
230 vertices->push_back(light.position.osg());
231 colors->push_back(light.color.osg());
233 osg::Geometry* geometry = new osg::Geometry;
234 geometry->setVertexArray(vertices);
235 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
236 geometry->setColorArray(colors);
237 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
239 // Enlarge the bounding box to avoid such light nodes being victim to
240 // small feature culling.
241 geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
243 osg::DrawArrays* drawArrays;
244 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
245 0, vertices->size());
246 geometry->addPrimitiveSet(drawArrays);
248 osg::StateSet* stateSet = geometry->getOrCreateStateSet();
249 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
250 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
252 osg::BlendFunc* blendFunc = new osg::BlendFunc;
253 stateSet->setAttribute(blendFunc);
254 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
256 osg::AlphaFunc* alphaFunc;
257 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
258 stateSet->setAttribute(alphaFunc);
259 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
261 osg::Geode* geode = new osg::Geode;
262 geode->addDrawable(geometry);
268 SGLightFactory::getLight(const SGDirectionalLightBin::Light& light)
270 osg::Vec3Array* vertices = new osg::Vec3Array;
271 osg::Vec4Array* colors = new osg::Vec4Array;
273 SGVec4f visibleColor(light.color);
274 SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
276 SGVec3f normal = normalize(light.normal);
277 SGVec3f perp1 = perpendicular(normal);
278 SGVec3f perp2 = cross(normal, perp1);
279 SGVec3f position = light.position;
280 vertices->push_back(position.osg());
281 vertices->push_back((position + perp1).osg());
282 vertices->push_back((position + perp2).osg());
283 colors->push_back(visibleColor.osg());
284 colors->push_back(invisibleColor.osg());
285 colors->push_back(invisibleColor.osg());
287 osg::Geometry* geometry = new osg::Geometry;
288 geometry->setVertexArray(vertices);
289 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
290 geometry->setColorArray(colors);
291 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
292 // Enlarge the bounding box to avoid such light nodes being victim to
293 // small feature culling.
294 geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
296 osg::DrawArrays* drawArrays;
297 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
298 0, vertices->size());
299 geometry->addPrimitiveSet(drawArrays);
301 osg::StateSet* stateSet = geometry->getOrCreateStateSet();
302 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
304 osg::Material* material = new osg::Material;
305 material->setColorMode(osg::Material::OFF);
306 stateSet->setAttribute(material);
308 osg::CullFace* cullFace = new osg::CullFace;
309 cullFace->setMode(osg::CullFace::BACK);
310 stateSet->setAttribute(cullFace, osg::StateAttribute::ON);
311 stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::ON);
313 osg::PolygonMode* polygonMode = new osg::PolygonMode;
314 polygonMode->setMode(osg::PolygonMode::FRONT, osg::PolygonMode::POINT);
315 stateSet->setAttribute(polygonMode);
317 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
319 osg::BlendFunc* blendFunc = new osg::BlendFunc;
320 stateSet->setAttribute(blendFunc);
321 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
323 osg::AlphaFunc* alphaFunc;
324 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
325 stateSet->setAttribute(alphaFunc);
326 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
328 osg::Geode* geode = new osg::Geode;
329 geode->addDrawable(geometry);
335 SGLightFactory::getLights(const SGLightBin& lights, unsigned inc, float alphaOff)
337 if (lights.getNumLights() <= 0)
340 osg::Vec3Array* vertices = new osg::Vec3Array;
341 osg::Vec4Array* colors = new osg::Vec4Array;
343 for (unsigned i = 0; i < lights.getNumLights(); i += inc) {
344 vertices->push_back(lights.getLight(i).position.osg());
345 SGVec4f color = lights.getLight(i).color;
346 color[3] = SGMiscf::max(0, SGMiscf::min(1, color[3] + alphaOff));
347 colors->push_back(color.osg());
350 osg::Geometry* geometry = new osg::Geometry;
352 geometry->setVertexArray(vertices);
353 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
354 geometry->setColorArray(colors);
355 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
357 osg::DrawArrays* drawArrays;
358 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
359 0, vertices->size());
360 geometry->addPrimitiveSet(drawArrays);
362 osg::StateSet* stateSet = geometry->getOrCreateStateSet();
363 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
365 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
367 osg::BlendFunc* blendFunc = new osg::BlendFunc;
368 stateSet->setAttribute(blendFunc);
369 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
371 osg::AlphaFunc* alphaFunc;
372 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
373 stateSet->setAttribute(alphaFunc);
374 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
381 SGLightFactory::getLights(const SGDirectionalLightBin& lights)
383 if (lights.getNumLights() <= 0)
386 osg::Vec3Array* vertices = new osg::Vec3Array;
387 osg::Vec4Array* colors = new osg::Vec4Array;
389 for (unsigned i = 0; i < lights.getNumLights(); ++i) {
390 SGVec4f visibleColor(lights.getLight(i).color);
391 SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
393 SGVec3f normal = normalize(lights.getLight(i).normal);
394 SGVec3f perp1 = perpendicular(normal);
395 SGVec3f perp2 = cross(normal, perp1);
396 SGVec3f position = lights.getLight(i).position;
397 vertices->push_back(position.osg());
398 vertices->push_back((position + perp1).osg());
399 vertices->push_back((position + perp2).osg());
400 colors->push_back(visibleColor.osg());
401 colors->push_back(invisibleColor.osg());
402 colors->push_back(invisibleColor.osg());
405 osg::Geometry* geometry = new osg::Geometry;
407 geometry->setVertexArray(vertices);
408 geometry->setNormalBinding(osg::Geometry::BIND_OFF);
409 geometry->setColorArray(colors);
410 geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
412 osg::DrawArrays* drawArrays;
413 drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
414 0, vertices->size());
415 geometry->addPrimitiveSet(drawArrays);
417 osg::StateSet* stateSet = geometry->getOrCreateStateSet();
418 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
420 osg::Material* material = new osg::Material;
421 material->setColorMode(osg::Material::OFF);
422 stateSet->setAttribute(material);
424 osg::CullFace* cullFace = new osg::CullFace;
425 cullFace->setMode(osg::CullFace::BACK);
426 stateSet->setAttribute(cullFace, osg::StateAttribute::ON);
427 stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::ON);
429 osg::PolygonMode* polygonMode = new osg::PolygonMode;
430 polygonMode->setMode(osg::PolygonMode::FRONT, osg::PolygonMode::POINT);
431 stateSet->setAttribute(polygonMode);
433 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
435 osg::BlendFunc* blendFunc = new osg::BlendFunc;
436 stateSet->setAttribute(blendFunc);
437 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
439 osg::AlphaFunc* alphaFunc;
440 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
441 stateSet->setAttribute(alphaFunc);
442 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
447 static SGVasiDrawable*
448 buildVasi(const SGDirectionalLightBin& lights, const SGVec3f& up,
449 const SGVec4f& red, const SGVec4f& white)
451 unsigned count = lights.getNumLights();
453 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
455 // PAPI configuration
457 drawable->addLight(lights.getLight(0).position,
458 lights.getLight(0).normal, up, 3.5);
460 drawable->addLight(lights.getLight(1).position,
461 lights.getLight(1).normal, up, 3.167);
463 drawable->addLight(lights.getLight(2).position,
464 lights.getLight(2).normal, up, 2.833);
466 drawable->addLight(lights.getLight(3).position,
467 lights.getLight(3).normal, up, 2.5);
470 else if (count == 12) {
471 SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
473 // probably vasi, first 6 are downwind bar (2.5 deg)
474 for (unsigned i = 0; i < 6; ++i)
475 drawable->addLight(lights.getLight(i).position,
476 lights.getLight(i).normal, up, 2.5);
477 // last 6 are upwind bar (3.0 deg)
478 for (unsigned i = 6; i < 12; ++i)
479 drawable->addLight(lights.getLight(i).position,
480 lights.getLight(i).normal, up, 3.0);
485 SG_LOG(SG_TERRAIN, SG_ALERT,
486 "unknown vasi/papi configuration, count = " << count);
492 SGLightFactory::getVasi(const SGVec3f& up, const SGDirectionalLightBin& lights,
493 const SGVec4f& red, const SGVec4f& white)
495 SGVasiDrawable* drawable = buildVasi(lights, up, red, white);
499 osg::StateSet* stateSet = drawable->getOrCreateStateSet();
500 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
501 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
503 osg::BlendFunc* blendFunc = new osg::BlendFunc;
504 stateSet->setAttribute(blendFunc);
505 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
507 osg::AlphaFunc* alphaFunc;
508 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
509 stateSet->setAttribute(alphaFunc);
510 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
516 SGLightFactory::getSequenced(const SGDirectionalLightBin& lights)
518 if (lights.getNumLights() <= 0)
521 // generate a repeatable random seed
522 sg_srandom(unsigned(lights.getLight(0).position[0]));
523 float flashTime = 2e-2 + 5e-3*sg_random();
524 osg::Sequence* sequence = new osg::Sequence;
525 sequence->setDefaultTime(flashTime);
527 for (int i = lights.getNumLights() - 1; 0 <= i; --i)
528 sequence->addChild(getLight(lights.getLight(i)), flashTime);
529 sequence->addChild(new osg::Group, 1 + 1e-1*sg_random());
530 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
531 sequence->setDuration(1.0f, -1);
532 sequence->setMode(osg::Sequence::START);
533 sequence->setSync(true);
535 osg::StateSet* stateSet = sequence->getOrCreateStateSet();
536 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
537 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
539 osg::BlendFunc* blendFunc = new osg::BlendFunc;
540 stateSet->setAttribute(blendFunc);
541 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
543 osg::AlphaFunc* alphaFunc;
544 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
545 stateSet->setAttribute(alphaFunc);
546 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
548 osg::Point* point = new osg::Point;
549 point->setMinSize(6);
550 point->setMaxSize(10);
552 point->setDistanceAttenuation(osg::Vec3(1.0, 0.0001, 0.00000001));
553 sequence->setCullCallback(new SGPointSpriteLightCullCallback(point));
559 SGLightFactory::getOdal(const SGLightBin& lights)
561 if (lights.getNumLights() < 2)
564 // generate a repeatable random seed
565 sg_srandom(unsigned(lights.getLight(0).position[0]));
566 float flashTime = 2e-2 + 5e-3*sg_random();
567 osg::Sequence* sequence = new osg::Sequence;
568 sequence->setDefaultTime(flashTime);
571 for (int i = lights.getNumLights() - 1; 2 <= i; --i)
572 sequence->addChild(getLight(lights.getLight(i)), flashTime);
575 osg::Group* group = new osg::Group;
576 for (unsigned i = 0; i < 2; ++i)
577 group->addChild(getLight(lights.getLight(i)));
578 sequence->addChild(group, flashTime);
580 // add an extra empty group for a break
581 sequence->addChild(new osg::Group, 9 + 1e-1*sg_random());
582 sequence->setInterval(osg::Sequence::LOOP, 0, -1);
583 sequence->setDuration(1.0f, -1);
584 sequence->setMode(osg::Sequence::START);
585 sequence->setSync(true);
587 osg::StateSet* stateSet = sequence->getOrCreateStateSet();
588 stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
589 stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
591 osg::BlendFunc* blendFunc = new osg::BlendFunc;
592 stateSet->setAttribute(blendFunc);
593 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
595 osg::AlphaFunc* alphaFunc;
596 alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
597 stateSet->setAttribute(alphaFunc);
598 stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
600 osg::Point* point = new osg::Point;
601 point->setMinSize(6);
602 point->setMaxSize(10);
604 point->setDistanceAttenuation(osg::Vec3(1.0, 0.0001, 0.00000001));
605 sequence->setCullCallback(new SGPointSpriteLightCullCallback(point));