]> git.mxchange.org Git - simgear.git/blobdiff - simgear/scene/tgdb/pt_lights.cxx
Use Effect to implement point lights
[simgear.git] / simgear / scene / tgdb / pt_lights.cxx
index 5b3dcc2d4346a99e29eda693803c813893c81cc5..e15b7beaae44367220cd3a26124bb80453998ab7 100644 (file)
 //
 // You should have received a copy of the GNU General Public License
 // along with this program; if not, write to the Free Software
-// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 //
 // $Id$
 
-
-#include <plib/sg.h>
-
-#include <simgear/scene/material/mat.hxx>
-#include <simgear/scene/material/matlib.hxx>
-
-#include "vasi.hxx"
+#ifdef HAVE_CONFIG_H
+#  include <simgear_config.h>
+#endif
 
 #include "pt_lights.hxx"
 
-
-// strobe pre-draw (we want a larger point size)
-static int StrobePreDraw( ssgEntity *e ) {
-    glPushAttrib( GL_POINT_BIT );
-    glPointSize(4.0);
-    glEnable(GL_POINT_SMOOTH);
-
-    return true;
-}
-
-// strobe post-draw (we want a larger point size)
-static int StrobePostDraw( ssgEntity *e ) {
-    glPopAttrib();
-
-    return true;
+#include <map>
+#include <boost/tuple/tuple_comparison.hpp>
+
+#include <osg/Array>
+#include <osg/Geometry>
+#include <osg/CullFace>
+#include <osg/Geode>
+#include <osg/MatrixTransform>
+#include <osg/NodeCallback>
+#include <osg/NodeVisitor>
+#include <osg/Texture2D>
+#include <osg/AlphaFunc>
+#include <osg/BlendFunc>
+#include <osg/TexEnv>
+#include <osg/Sequence>
+#include <osg/PolygonMode>
+#include <osg/Fog>
+#include <osg/FragmentProgram>
+#include <osg/VertexProgram>
+#include <osg/Point>
+#include <osg/PointSprite>
+#include <osg/Material>
+#include <osg/Group>
+#include <osg/StateSet>
+
+#include <osgUtil/CullVisitor>
+
+#include <OpenThreads/Mutex>
+#include <OpenThreads/ScopedLock>
+
+#include <simgear/math/sg_random.h>
+#include <simgear/debug/logstream.hxx>
+#include <simgear/scene/util/RenderConstants.hxx>
+#include <simgear/scene/util/SGEnlargeBoundingBox.hxx>
+#include <simgear/scene/util/StateAttributeFactory.hxx>
+
+#include <simgear/scene/material/Effect.hxx>
+#include <simgear/scene/material/EffectGeode.hxx>
+#include <simgear/scene/material/Technique.hxx>
+#include <simgear/scene/material/Pass.hxx>
+
+#include "SGVasiDrawable.hxx"
+
+using OpenThreads::Mutex;
+using OpenThreads::ScopedLock;
+
+using namespace osg;
+using namespace simgear;
+
+static void
+setPointSpriteImage(unsigned char* data, unsigned log2resolution,
+                    unsigned charsPerPixel)
+{
+  int env_tex_res = (1 << log2resolution);
+  for (int i = 0; i < env_tex_res; ++i) {
+    for (int j = 0; j < env_tex_res; ++j) {
+      int xi = 2*i + 1 - env_tex_res;
+      int yi = 2*j + 1 - env_tex_res;
+      if (xi < 0)
+        xi = -xi;
+      if (yi < 0)
+        yi = -yi;
+      
+      xi -= 1;
+      yi -= 1;
+      
+      if (xi < 0)
+        xi = 0;
+      if (yi < 0)
+        yi = 0;
+      
+      float x = 1.5*xi/(float)(env_tex_res);
+      float y = 1.5*yi/(float)(env_tex_res);
+      //       float x = 2*xi/(float)(env_tex_res);
+      //       float y = 2*yi/(float)(env_tex_res);
+      float dist = sqrt(x*x + y*y);
+      float bright = SGMiscf::clip(255*(1-dist), 0, 255);
+      for (unsigned l = 0; l < charsPerPixel; ++l)
+        data[charsPerPixel*(i*env_tex_res + j) + l] = (unsigned char)bright;
+    }
+  }
 }
 
-
-// vasi pre-draw (we want a larger point size)
-static int VASIPreDraw( ssgEntity *e ) {
-    glPushAttrib( GL_POINT_BIT );
-    glPointSize(2.0);
-    glEnable(GL_POINT_SMOOTH);
-
-    return true;
+static osg::Image*
+getPointSpriteImage(int logResolution)
+{
+  osg::Image* image = new osg::Image;
+  
+  osg::Image::MipmapDataType mipmapOffsets;
+  unsigned off = 0;
+  for (int i = logResolution; 0 <= i; --i) {
+    unsigned res = 1 << i;
+    off += res*res;
+    mipmapOffsets.push_back(off);
+  }
+  
+  int env_tex_res = (1 << logResolution);
+  
+  unsigned char* imageData = new unsigned char[off];
+  image->setImage(env_tex_res, env_tex_res, 1,
+                  GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE, imageData,
+                  osg::Image::USE_NEW_DELETE);
+  image->setMipmapLevels(mipmapOffsets);
+  
+  for (int k = logResolution; 0 <= k; --k) {
+    setPointSpriteImage(image->getMipmapData(logResolution - k), k, 1);
+  }
+  
+  return image;
 }
 
-// vasi post-draw (we want a larger point size)
-static int VASIPostDraw( ssgEntity *e ) {
-    glPopAttrib();
+static Mutex lightMutex;
 
-    return true;
+static osg::Texture2D*
+gen_standard_light_sprite(void)
+{
+  // Always called from when the lightMutex is already taken
+  static osg::ref_ptr<osg::Texture2D> texture;
+  if (texture.valid())
+    return texture.get();
+  
+  texture = new osg::Texture2D;
+  texture->setImage(getPointSpriteImage(6));
+  texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
+  texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
+  
+  return texture.get();
 }
 
+namespace
+{
+typedef boost::tuple<float, osg::Vec3, float, float, bool> PointParams;
+typedef std::map<PointParams, ref_ptr<Effect> > EffectMap;
 
-// Generate a directional light
-ssgLeaf *sgMakeDirectionalLight( sgVec3 pt, sgVec3 dir, sgVec3 up, 
-                                 const SGMaterial *mat ) {
-
-    // calculate a vector perpendicular to dir and up
-    sgVec3 perp;
-    sgVectorProductVec3( perp, dir, up );
-
-    ssgVertexArray   *vl = new ssgVertexArray( 3 );
-    ssgNormalArray   *nl = new ssgNormalArray( 3 );
-    ssgColourArray   *cl = new ssgColourArray( 3 );
-
-    // front face
-    sgVec3 tmp3;
-    sgCopyVec3( tmp3, pt );
-    vl->add( tmp3 );
-    sgAddVec3( tmp3, up );
-    vl->add( tmp3 );
-    sgAddVec3( tmp3, perp );
-    vl->add( tmp3 );
-    // sgSubVec3( tmp3, up );
-    // vl->add( tmp3 );
-
-    nl->add( dir );
-    nl->add( dir );
-    nl->add( dir );
-    // nl->add( dir );
-
-    sgVec4 color;
-    sgSetVec4( color, 1.0, 1.0, 1.0, 1.0 );
-    cl->add( color );
-    sgSetVec4( color, 1.0, 1.0, 1.0, 0.0 );
-    cl->add( color );
-    cl->add( color );
-    // cl->add( color );
-
-    /*
-    // temporarily do back face
-    sgCopyVec3( tmp3, pt );
-    vl->add( tmp3 );
-    sgAddVec3( tmp3, up );
-    vl->add( tmp3 );
-    sgAddVec3( tmp3, perp );
-    vl->add( tmp3 );
-
-    sgNegateVec3( dir );
-    nl->add( dir );
-    nl->add( dir );
-    nl->add( dir );
-
-    sgSetVec4( color, 1.0, 1.0, 1.0, 1.0 );
-    cl->add( color );
-    sgSetVec4( color, 1.0, 1.0, 1.0, 0.0 );
-    cl->add( color );
-    cl->add( color );
-    */
-
-    /* ssgTexCoordArray *tl = new ssgTexCoordArray( 4 );
-    sgVec2 tmp2;
-    sgSetVec2( tmp2, 0.0, 0.0 );
-    tl->add( tmp2 );
-    sgSetVec2( tmp2, 1.0, 0.0 );
-    tl->add( tmp2 );
-    sgSetVec2( tmp2, 1.0, 1.0 );
-    tl->add( tmp2 );
-    sgSetVec2( tmp2, 0.0, 1.0 );
-    tl->add( tmp2 ); */
-
-    ssgLeaf *leaf = 
-        new ssgVtxTable ( GL_TRIANGLES, vl, nl, NULL, cl );
-
-    if ( mat != NULL ) {
-        leaf->setState( mat->get_state() );
-    } else {
-        SG_LOG( SG_TERRAIN, SG_ALERT, "Warning: mat = NULL" );
-    }
+EffectMap effectMap;
 
-    return leaf;
+ref_ptr<PolygonMode> polyMode = new PolygonMode(PolygonMode::FRONT,
+                                                PolygonMode::POINT);
+ref_ptr<PointSprite> pointSprite = new PointSprite;
 }
 
-
-static void calc_center_point( const point_list &nodes,
-                               const int_list &pnt_i,
-                               sgVec3 result ) {
-    sgVec3 pt;
-    sgSetVec3( pt, nodes[pnt_i[0]][0], nodes[pnt_i[0]][1], nodes[pnt_i[0]][2] );
-
-    double minx = pt[0];
-    double maxx = pt[0];
-    double miny = pt[1];
-    double maxy = pt[1];
-    double minz = pt[2];
-    double maxz = pt[2];
-
-    for ( unsigned int i = 0; i < pnt_i.size(); ++i ) {
-        sgSetVec3( pt, nodes[pnt_i[i]][0], nodes[pnt_i[i]][1],
-                   nodes[pnt_i[i]][2] );
-        if ( pt[0] < minx ) { minx = pt[0]; }
-        if ( pt[0] > maxx ) { minx = pt[0]; }
-        if ( pt[1] < miny ) { miny = pt[1]; }
-        if ( pt[1] > maxy ) { miny = pt[1]; }
-        if ( pt[2] < minz ) { minz = pt[2]; }
-        if ( pt[2] > maxz ) { minz = pt[2]; }
+Effect* getLightEffect(float size, const Vec3& attenuation,
+                       float minSize, float maxSize, bool directional)
+{
+    PointParams pointParams(size, attenuation, minSize, maxSize, directional);
+    ScopedLock<Mutex> lock(lightMutex);
+    EffectMap::iterator eitr = effectMap.find(pointParams);
+    if (eitr != effectMap.end())
+        return eitr->second.get();
+    // Basic stuff; no sprite or attenuation support
+    Pass *basicPass = new Pass;
+    basicPass->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
+    basicPass->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
+    StateAttributeFactory *attrFact = StateAttributeFactory::instance();
+    basicPass->setAttributeAndModes(attrFact->getStandardBlendFunc());
+    basicPass->setAttributeAndModes(attrFact->getStandardAlphaFunc());
+    if (directional) {
+        basicPass->setAttributeAndModes(attrFact->getCullFaceBack());
+        basicPass->setAttribute(polyMode.get());
     }
-
-    sgSetVec3( result, (minx + maxx) / 2.0, (miny + maxy) / 2.0,
-               (minz + maxz) / 2.0 );
+    Pass *attenuationPass = clone(basicPass, CopyOp::SHALLOW_COPY);
+    osg::Point* point = new osg::Point;
+    point->setMinSize(minSize);
+    point->setMaxSize(maxSize);
+    point->setSize(size);
+    point->setDistanceAttenuation(attenuation);
+    attenuationPass->setAttributeAndModes(point);
+    Pass *spritePass = clone(basicPass, CopyOp::SHALLOW_COPY);
+    spritePass->setTextureAttributeAndModes(0, pointSprite,
+                                            osg::StateAttribute::ON);
+    Texture2D* texture = gen_standard_light_sprite();
+    spritePass->setTextureAttribute(0, texture);
+    spritePass->setTextureMode(0, GL_TEXTURE_2D,
+                               osg::StateAttribute::ON);
+    spritePass->setTextureAttribute(0, attrFact->getStandardTexEnv());
+    Pass *combinedPass = clone(spritePass, CopyOp::SHALLOW_COPY);
+    combinedPass->setAttributeAndModes(point);
+    Effect* effect = new Effect;
+    std::vector<std::string> combinedExtensions;
+    combinedExtensions.push_back("GL_ARB_point_sprite");
+    combinedExtensions.push_back("GL_ARB_point_parameters");
+    Technique* combinedTniq = new Technique;
+    combinedTniq->passes.push_back(combinedPass);
+    combinedTniq->setGLExtensionsPred(2.0, combinedExtensions);
+    effect->techniques.push_back(combinedTniq);
+    std::vector<std::string> spriteExtensions;
+    spriteExtensions.push_back(combinedExtensions.front());
+    Technique* spriteTniq = new Technique;
+    spriteTniq->passes.push_back(spritePass);
+    spriteTniq->setGLExtensionsPred(2.0, spriteExtensions);
+    effect->techniques.push_back(spriteTniq);
+    std::vector<std::string> parameterExtensions;
+    parameterExtensions.push_back(combinedExtensions.back());
+    Technique* parameterTniq = new Technique;
+    parameterTniq->passes.push_back(attenuationPass);
+    parameterTniq->setGLExtensionsPred(1.4, parameterExtensions);
+    effect->techniques.push_back(parameterTniq);
+    Technique* basicTniq = new Technique(true);
+    basicTniq->passes.push_back(basicPass);
+    effect->techniques.push_back(basicTniq);
+    effectMap.insert(std::make_pair(pointParams, effect));
+    return effect;
 }
 
 
-static ssgTransform *gen_dir_light_group( const point_list &nodes,
-                                          const point_list &normals,
-                                          const int_list &pnt_i,
-                                          const int_list &nml_i,
-                                          const SGMaterial *mat,
-                                          sgVec3 up, bool vertical )
+osg::Drawable*
+SGLightFactory::getLightDrawable(const SGLightBin::Light& light)
 {
-    sgVec3 center;
-    calc_center_point( nodes, pnt_i, center );
-    // cout << center[0] << "," << center[1] << "," << center[2] << endl;
-
-
-    // find a vector perpendicular to the normal.
-    sgVec3 perp1;
-    if ( !vertical ) {
-        // normal isn't vertical so we can use up as our first vector
-        sgNormalizeVec3( perp1, up );
-    } else {
-        // normal is vertical so we have to work a bit harder to
-        // determine our first vector
-        sgVec3 pt1, pt2;
-        sgSetVec3( pt1, nodes[pnt_i[0]][0], nodes[pnt_i[0]][1],
-                   nodes[pnt_i[0]][2] );
-        sgSetVec3( pt2, nodes[pnt_i[1]][0], nodes[pnt_i[1]][1],
-                   nodes[pnt_i[1]][2] );
-
-        sgSubVec3( perp1, pt2, pt1 );
-        sgNormalizeVec3( perp1 );
-    }
-
-    ssgVertexArray *vl = new ssgVertexArray( 3 * pnt_i.size() );
-    ssgNormalArray *nl = new ssgNormalArray( 3 * pnt_i.size() );
-    ssgColourArray *cl = new ssgColourArray( 3 * pnt_i.size() );
-
-    unsigned int i;
-    sgVec3 pt, normal;
-    for ( i = 0; i < pnt_i.size(); ++i ) {
-        sgSetVec3( pt, nodes[pnt_i[i]][0], nodes[pnt_i[i]][1],
-                   nodes[pnt_i[i]][2] );
-        sgSubVec3( pt, center );
-        sgSetVec3( normal, normals[nml_i[i]][0], normals[nml_i[i]][1],
-                   normals[nml_i[i]][2] );
-
-        // calculate a vector perpendicular to dir and up
-        sgVec3 perp2;
-        sgVectorProductVec3( perp2, normal, perp1 );
-
-        // front face
-        sgVec3 tmp3;
-        sgCopyVec3( tmp3, pt );
-        vl->add( tmp3 );
-        sgAddVec3( tmp3, perp1 );
-        vl->add( tmp3 );
-        sgAddVec3( tmp3, perp2 );
-        vl->add( tmp3 );
-        // sgSubVec3( tmp3, perp1 );
-        // vl->add( tmp3 );
-
-        nl->add( normal );
-        nl->add( normal );
-        nl->add( normal );
-        // nl->add( normal );
-
-        sgVec4 color;
-        sgSetVec4( color, 1.0, 1.0, 1.0, 1.0 );
-        cl->add( color );
-        sgSetVec4( color, 1.0, 1.0, 1.0, 0.0 );
-        cl->add( color );
-        cl->add( color );
-        // cl->add( color );
-    }
-
-    ssgLeaf *leaf = 
-        new ssgVtxTable ( GL_TRIANGLES, vl, nl, NULL, cl );
-
-    if ( mat != NULL ) {
-        leaf->setState( mat->get_state() );
-    } else {
-        SG_LOG( SG_TERRAIN, SG_ALERT, "Warning: material = NULL" );
-    }
-
-    // put an LOD on each lighting component
-    ssgRangeSelector *lod = new ssgRangeSelector;
-    lod->setRange( 0, SG_ZERO );
-    lod->setRange( 1, 20000 );
-    lod->addKid( leaf );
-
-    // create the transformation.
-    sgCoord coord;
-    sgSetCoord( &coord, center[0], center[1], center[2], 0.0, 0.0, 0.0 );
-    ssgTransform *trans = new ssgTransform;
-    trans->setTransform( &coord );
-    trans->addKid( lod );
-
-    return trans;
+  osg::Vec3Array* vertices = new osg::Vec3Array;
+  osg::Vec4Array* colors = new osg::Vec4Array;
+
+  vertices->push_back(light.position.osg());
+  colors->push_back(light.color.osg());
+  
+  osg::Geometry* geometry = new osg::Geometry;
+  geometry->setVertexArray(vertices);
+  geometry->setNormalBinding(osg::Geometry::BIND_OFF);
+  geometry->setColorArray(colors);
+  geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
+
+  // Enlarge the bounding box to avoid such light nodes being victim to
+  // small feature culling.
+  geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
+  
+  osg::DrawArrays* drawArrays;
+  drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
+                                   0, vertices->size());
+  geometry->addPrimitiveSet(drawArrays);
+  return geometry;
 }
 
-
-static ssgTransform *gen_reil_lights( const point_list &nodes,
-                                      const point_list &normals,
-                                      const int_list &pnt_i,
-                                      const int_list &nml_i,
-                                      SGMaterialLib *matlib,
-                                      sgVec3 up )
+osg::Drawable*
+SGLightFactory::getLightDrawable(const SGDirectionalLightBin::Light& light)
 {
-    sgVec3 center;
-    calc_center_point( nodes, pnt_i, center );
-    // cout << center[0] << "," << center[1] << "," << center[2] << endl;
-
-    sgVec3 nup;
-    sgNormalizeVec3( nup, up );
-
-    ssgVertexArray   *vl = new ssgVertexArray( 3 * pnt_i.size() );
-    ssgNormalArray   *nl = new ssgNormalArray( 3 * pnt_i.size() );
-    ssgColourArray   *cl = new ssgColourArray( 3 * pnt_i.size() );
-
-    unsigned int i;
-    sgVec3 pt, normal;
-    for ( i = 0; i < pnt_i.size(); ++i ) {
-        sgSetVec3( pt, nodes[pnt_i[i]][0], nodes[pnt_i[i]][1],
-                   nodes[pnt_i[i]][2] );
-        sgSubVec3( pt, center );
-        sgSetVec3( normal, normals[nml_i[i]][0], normals[nml_i[i]][1],
-                   normals[nml_i[i]][2] );
-
-        // calculate a vector perpendicular to dir and up
-        sgVec3 perp;
-        sgVectorProductVec3( perp, normal, nup );
-
-        // front face
-        sgVec3 tmp3;
-        sgCopyVec3( tmp3, pt );
-        vl->add( tmp3 );
-        sgAddVec3( tmp3, nup );
-        vl->add( tmp3 );
-        sgAddVec3( tmp3, perp );
-        vl->add( tmp3 );
-        // sgSubVec3( tmp3, nup );
-        // vl->add( tmp3 );
-
-        nl->add( normal );
-        nl->add( normal );
-        nl->add( normal );
-        // nl->add( normal );
-
-        sgVec4 color;
-        sgSetVec4( color, 1.0, 1.0, 1.0, 1.0 );
-        cl->add( color );
-        sgSetVec4( color, 1.0, 1.0, 1.0, 0.0 );
-        cl->add( color );
-        cl->add( color );
-        // cl->add( color );
-    }
-
-    ssgLeaf *leaf = 
-        new ssgVtxTable ( GL_TRIANGLES, vl, nl, NULL, cl );
-
-    SGMaterial *mat = matlib->find( "RWY_WHITE_LIGHTS" );
-
-    if ( mat != NULL ) {
-        leaf->setState( mat->get_state() );
-    } else {
-        SG_LOG( SG_TERRAIN, SG_ALERT,
-                "Warning: can't find material = RWY_WHITE_LIGHTS" );
-    }
-
-    leaf->setCallback( SSG_CALLBACK_PREDRAW, StrobePreDraw );
-    leaf->setCallback( SSG_CALLBACK_POSTDRAW, StrobePostDraw );
-
-    ssgTimedSelector *reil = new ssgTimedSelector;
-
-    // need to add this twice to work around an ssg bug
-    reil->addKid( leaf );
-    reil->addKid( leaf );
-
-    reil->setDuration( 60 );
-    reil->setLimits( 0, 2 );
-    reil->setMode( SSG_ANIM_SHUTTLE );
-    reil->control( SSG_ANIM_START );
-   
-    // put an LOD on each lighting component
-    ssgRangeSelector *lod = new ssgRangeSelector;
-    lod->setRange( 0, SG_ZERO );
-    lod->setRange( 1, 12000 );
-    lod->addKid( reil );
-
-    // create the transformation.
-    sgCoord coord;
-    sgSetCoord( &coord, center[0], center[1], center[2], 0.0, 0.0, 0.0 );
-    ssgTransform *trans = new ssgTransform;
-    trans->setTransform( &coord );
-    trans->addKid( lod );
-
-    return trans;
+  osg::Vec3Array* vertices = new osg::Vec3Array;
+  osg::Vec4Array* colors = new osg::Vec4Array;
+
+  SGVec4f visibleColor(light.color);
+  SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
+                         visibleColor[2], 0);
+  SGVec3f normal = normalize(light.normal);
+  SGVec3f perp1 = perpendicular(normal);
+  SGVec3f perp2 = cross(normal, perp1);
+  SGVec3f position = light.position;
+  vertices->push_back(position.osg());
+  vertices->push_back((position + perp1).osg());
+  vertices->push_back((position + perp2).osg());
+  colors->push_back(visibleColor.osg());
+  colors->push_back(invisibleColor.osg());
+  colors->push_back(invisibleColor.osg());
+  
+  osg::Geometry* geometry = new osg::Geometry;
+  geometry->setVertexArray(vertices);
+  geometry->setNormalBinding(osg::Geometry::BIND_OFF);
+  geometry->setColorArray(colors);
+  geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
+  // Enlarge the bounding box to avoid such light nodes being victim to
+  // small feature culling.
+  geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
+  
+  osg::DrawArrays* drawArrays;
+  drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
+                                   0, vertices->size());
+  geometry->addPrimitiveSet(drawArrays);
+  return geometry;
 }
 
-
-static ssgTransform *gen_odals_lights( const point_list &nodes,
-                                       const point_list &normals,
-                                       const int_list &pnt_i,
-                                       const int_list &nml_i,
-                                       SGMaterialLib *matlib,
-                                       sgVec3 up )
+namespace
 {
-    sgVec3 center;
-    calc_center_point( nodes, pnt_i, center );
-    // cout << center[0] << "," << center[1] << "," << center[2] << endl;
-
-    ssgTimedSelector *odals = new ssgTimedSelector;
-
-    sgVec4 color;
-    sgSetVec4( color, 1.0, 1.0, 1.0, 1.0 );
-
-    // we don't want directional lights here
-    SGMaterial *mat = matlib->find( "GROUND_LIGHTS" );
-    if ( mat == NULL ) {
-        SG_LOG( SG_TERRAIN, SG_ALERT,
-                "Warning: can't material = GROUND_LIGHTS" );
-    }
-
-    // center line strobes
-    int i;
-    sgVec3 pt;
-    for ( i = (int)pnt_i.size() - 1; i >= 2; --i ) {
-        ssgVertexArray   *vl = new ssgVertexArray( 1 );
-        ssgColourArray   *cl = new ssgColourArray( 1 );
-     
-        sgSetVec3( pt, nodes[pnt_i[i]][0], nodes[pnt_i[i]][1],
-                   nodes[pnt_i[i]][2] );
-        sgSubVec3( pt, center );
-        vl->add( pt );
-
-        cl->add( color );
-
-        ssgLeaf *leaf = 
-            new ssgVtxTable ( GL_POINTS, vl, NULL, NULL, cl );
-
-        leaf->setState( mat->get_state() );
-        leaf->setCallback( SSG_CALLBACK_PREDRAW, StrobePreDraw );
-        leaf->setCallback( SSG_CALLBACK_POSTDRAW, StrobePostDraw );
-
-        odals->addKid( leaf );
-    }
-
-    // runway end strobes
-    ssgVertexArray   *vl = new ssgVertexArray( 2 );
-    ssgColourArray   *cl = new ssgColourArray( 2 );
-     
-    sgSetVec3( pt, nodes[pnt_i[0]][0], nodes[pnt_i[0]][1],
-               nodes[pnt_i[0]][2] );
-    sgSubVec3( pt, center );
-    vl->add( pt );
-    cl->add( color );
-
-    sgSetVec3( pt, nodes[pnt_i[1]][0], nodes[pnt_i[1]][1],
-               nodes[pnt_i[1]][2] );
-    sgSubVec3( pt, center );
-    vl->add( pt );
-    cl->add( color );
-
-    ssgLeaf *leaf = 
-        new ssgVtxTable ( GL_POINTS, vl, NULL, NULL, cl );
-
-    leaf->setState( mat->get_state() );
-    leaf->setCallback( SSG_CALLBACK_PREDRAW, StrobePreDraw );
-    leaf->setCallback( SSG_CALLBACK_POSTDRAW, StrobePostDraw );
-
-    odals->addKid( leaf );
-
-    // setup animition
-
-    odals->setDuration( 10 );
-    odals->setLimits( 0, pnt_i.size() - 1 );
-    odals->setMode( SSG_ANIM_SHUTTLE );
-    odals->control( SSG_ANIM_START );
-   
-    // put an LOD on each lighting component
-    ssgRangeSelector *lod = new ssgRangeSelector;
-    lod->setRange( 0, SG_ZERO );
-    lod->setRange( 1, 12000 );
-    lod->addKid( odals );
-
-    // create the transformation.
-    sgCoord coord;
-    sgSetCoord( &coord, center[0], center[1], center[2], 0.0, 0.0, 0.0 );
-    ssgTransform *trans = new ssgTransform;
-    trans->setTransform( &coord );
-    trans->addKid( lod );
-
-    return trans;
+  ref_ptr<StateSet> simpleLightSS;
 }
-
-
-static ssgTransform *gen_rabbit_lights( const point_list &nodes,
-                                        const point_list &normals,
-                                        const int_list &pnt_i,
-                                        const int_list &nml_i,
-                                        SGMaterialLib *matlib,
-                                        sgVec3 up )
+osg::Drawable*
+SGLightFactory::getLights(const SGLightBin& lights, unsigned inc, float alphaOff)
 {
-    sgVec3 center;
-    calc_center_point( nodes, pnt_i, center );
-    // cout << center[0] << "," << center[1] << "," << center[2] << endl;
-
-    sgVec3 nup;
-    sgNormalizeVec3( nup, up );
-
-    ssgTimedSelector *rabbit = new ssgTimedSelector;
-
-    SGMaterial *mat = matlib->find( "RWY_WHITE_LIGHTS" );
-    if ( mat == NULL ) {
-        SG_LOG( SG_TERRAIN, SG_ALERT,
-                "Warning: can't material = RWY_WHITE_LIGHTS" );
-    }
-
-    int i;
-    sgVec3 pt, normal;
-    for ( i = (int)pnt_i.size() - 1; i >= 0; --i ) {
-        ssgVertexArray   *vl = new ssgVertexArray( 3 );
-        ssgNormalArray   *nl = new ssgNormalArray( 3 );
-        ssgColourArray   *cl = new ssgColourArray( 3 );
-     
-        sgSetVec3( pt, nodes[pnt_i[i]][0], nodes[pnt_i[i]][1],
-                   nodes[pnt_i[i]][2] );
-        sgSubVec3( pt, center );
-
-        sgSetVec3( normal, normals[nml_i[i]][0], normals[nml_i[i]][1],
-                   normals[nml_i[i]][2] );
-
-        // calculate a vector perpendicular to dir and up
-        sgVec3 perp;
-        sgVectorProductVec3( perp, normal, nup );
-
-        // front face
-        sgVec3 tmp3;
-        sgCopyVec3( tmp3, pt );
-        vl->add( tmp3 );
-        sgAddVec3( tmp3, nup );
-        vl->add( tmp3 );
-        sgAddVec3( tmp3, perp );
-        vl->add( tmp3 );
-        // sgSubVec3( tmp3, nup );
-        // vl->add( tmp3 );
-
-        nl->add( normal );
-        nl->add( normal );
-        nl->add( normal );
-        // nl->add( normal );
-
-        sgVec4 color;
-        sgSetVec4( color, 1.0, 1.0, 1.0, 1.0 );
-        cl->add( color );
-        sgSetVec4( color, 1.0, 1.0, 1.0, 0.0 );
-        cl->add( color );
-        cl->add( color );
-        // cl->add( color );
-
-        ssgLeaf *leaf = 
-            new ssgVtxTable ( GL_TRIANGLES, vl, nl, NULL, cl );
-
-        leaf->setState( mat->get_state() );
-        leaf->setCallback( SSG_CALLBACK_PREDRAW, StrobePreDraw );
-        leaf->setCallback( SSG_CALLBACK_POSTDRAW, StrobePostDraw );
-
-        rabbit->addKid( leaf );
+  if (lights.getNumLights() <= 0)
+    return 0;
+  
+  osg::Vec3Array* vertices = new osg::Vec3Array;
+  osg::Vec4Array* colors = new osg::Vec4Array;
+
+  for (unsigned i = 0; i < lights.getNumLights(); i += inc) {
+    vertices->push_back(lights.getLight(i).position.osg());
+    SGVec4f color = lights.getLight(i).color;
+    color[3] = SGMiscf::max(0, SGMiscf::min(1, color[3] + alphaOff));
+    colors->push_back(color.osg());
+  }
+  
+  osg::Geometry* geometry = new osg::Geometry;
+  
+  geometry->setVertexArray(vertices);
+  geometry->setNormalBinding(osg::Geometry::BIND_OFF);
+  geometry->setColorArray(colors);
+  geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
+  
+  osg::DrawArrays* drawArrays;
+  drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
+                                   0, vertices->size());
+  geometry->addPrimitiveSet(drawArrays);
+
+  {
+    ScopedLock<Mutex> lock(lightMutex);
+    if (!simpleLightSS.valid()) {
+      StateAttributeFactory *attrFact = StateAttributeFactory::instance();
+      simpleLightSS = new StateSet;
+      simpleLightSS->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
+      simpleLightSS->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
+      simpleLightSS->setAttributeAndModes(attrFact->getStandardBlendFunc());
+      simpleLightSS->setAttributeAndModes(attrFact->getStandardAlphaFunc());
     }
-
-    rabbit->setDuration( 10 );
-    rabbit->setLimits( 0, pnt_i.size() - 1 );
-    rabbit->setMode( SSG_ANIM_SHUTTLE );
-    rabbit->control( SSG_ANIM_START );
-   
-    // put an LOD on each lighting component
-    ssgRangeSelector *lod = new ssgRangeSelector;
-    lod->setRange( 0, SG_ZERO );
-    lod->setRange( 1, 12000 );
-    lod->addKid( rabbit );
-
-    // create the transformation.
-    sgCoord coord;
-    sgSetCoord( &coord, center[0], center[1], center[2], 0.0, 0.0, 0.0 );
-    ssgTransform *trans = new ssgTransform;
-    trans->setTransform( &coord );
-    trans->addKid( lod );
-
-    return trans;
+  }
+  geometry->setStateSet(simpleLightSS.get());
+  return geometry;
 }
 
 
-#if 0 // debugging infrastructure
-// Generate a normal line 
-static ssgLeaf *gen_normal_line( SGMaterialLib *matlib,
-                                 sgVec3 pt, sgVec3 dir, sgVec3 up )
+osg::Drawable*
+SGLightFactory::getLights(const SGDirectionalLightBin& lights)
 {
+  if (lights.getNumLights() <= 0)
+    return 0;
+  
+  osg::Vec3Array* vertices = new osg::Vec3Array;
+  osg::Vec4Array* colors = new osg::Vec4Array;
+
+  for (unsigned i = 0; i < lights.getNumLights(); ++i) {
+    SGVec4f visibleColor(lights.getLight(i).color);
+    SGVec4f invisibleColor(visibleColor[0], visibleColor[1],
+                           visibleColor[2], 0);
+    SGVec3f normal = normalize(lights.getLight(i).normal);
+    SGVec3f perp1 = perpendicular(normal);
+    SGVec3f perp2 = cross(normal, perp1);
+    SGVec3f position = lights.getLight(i).position;
+    vertices->push_back(position.osg());
+    vertices->push_back((position + perp1).osg());
+    vertices->push_back((position + perp2).osg());
+    colors->push_back(visibleColor.osg());
+    colors->push_back(invisibleColor.osg());
+    colors->push_back(invisibleColor.osg());
+  }
+  
+  osg::Geometry* geometry = new osg::Geometry;
+  
+  geometry->setVertexArray(vertices);
+  geometry->setNormalBinding(osg::Geometry::BIND_OFF);
+  geometry->setColorArray(colors);
+  geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
+  
+  osg::DrawArrays* drawArrays;
+  drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
+                                   0, vertices->size());
+  geometry->addPrimitiveSet(drawArrays);
+  return geometry;
+}
 
-    ssgVertexArray *vl = new ssgVertexArray( 3 );
-    ssgColourArray *cl = new ssgColourArray( 3 );
-
-    sgVec3 tmp3;
-    sgCopyVec3( tmp3, pt );
-    vl->add( tmp3 );
-    sgAddVec3( tmp3, dir );
-    vl->add( tmp3 );
-
-    sgVec4 color;
-    sgSetVec4( color, 1.0, 0.0, 0.0, 1.0 );
-    cl->add( color );
-    cl->add( color );
-
-    ssgLeaf *leaf = 
-        new ssgVtxTable ( GL_LINES, vl, NULL, NULL, cl );
-
-    SGMaterial *mat = matlib->find( "GROUND_LIGHTS" );
-    leaf->setState( mat->get_state() );
-
-    return leaf;
+static SGVasiDrawable*
+buildVasi(const SGDirectionalLightBin& lights, const SGVec3f& up,
+       const SGVec4f& red, const SGVec4f& white)
+{
+  unsigned count = lights.getNumLights();
+  if ( count == 4 ) {
+    SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
+
+    // PAPI configuration
+    // papi D
+    drawable->addLight(lights.getLight(0).position,
+                       lights.getLight(0).normal, up, 3.5);
+    // papi C
+    drawable->addLight(lights.getLight(1).position,
+                       lights.getLight(1).normal, up, 3.167);
+    // papi B
+    drawable->addLight(lights.getLight(2).position,
+                       lights.getLight(2).normal, up, 2.833);
+    // papi A
+    drawable->addLight(lights.getLight(3).position,
+                       lights.getLight(3).normal, up, 2.5);
+    return drawable;
+  }
+  else if (count == 12) {
+    SGVasiDrawable* drawable = new SGVasiDrawable(red, white);
+    
+    // probably vasi, first 6 are downwind bar (2.5 deg)
+    for (unsigned i = 0; i < 6; ++i)
+      drawable->addLight(lights.getLight(i).position,
+                         lights.getLight(i).normal, up, 2.5);
+    // last 6 are upwind bar (3.0 deg)
+    for (unsigned i = 6; i < 12; ++i)
+      drawable->addLight(lights.getLight(i).position,
+                         lights.getLight(i).normal, up, 3.0);
+    
+    return drawable;
+  } else {
+    // fail safe
+    SG_LOG(SG_TERRAIN, SG_ALERT,
+           "unknown vasi/papi configuration, count = " << count);
+    return 0;
+  }
 }
-#endif
 
+osg::Drawable*
+SGLightFactory::getVasi(const SGVec3f& up, const SGDirectionalLightBin& lights,
+                        const SGVec4f& red, const SGVec4f& white)
+{
+  SGVasiDrawable* drawable = buildVasi(lights, up, red, white);
+  if (!drawable)
+    return 0;
+
+  osg::StateSet* stateSet = drawable->getOrCreateStateSet();
+  stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
+  stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
+
+  osg::BlendFunc* blendFunc = new osg::BlendFunc;
+  stateSet->setAttribute(blendFunc);
+  stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
+  
+  osg::AlphaFunc* alphaFunc;
+  alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
+  stateSet->setAttribute(alphaFunc);
+  stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
+
+  return drawable;
+}
 
-ssgBranch *sgMakeDirectionalLights( const point_list &nodes,
-                                    const point_list &normals,
-                                    const int_list &pnt_i,
-                                    const int_list &nml_i,
-                                    SGMaterialLib *matlib,
-                                    const string &material,
-                                    sgdVec3 dup )
+osg::Node*
+SGLightFactory::getSequenced(const SGDirectionalLightBin& lights)
 {
-    sgVec3 up;
-    sgSetVec3( up, dup );
-
-    sgVec3 nup;
-    sgNormalizeVec3( nup, up );
-
-    SGMaterial *mat = matlib->find( material );
-
-    if ( material == "RWY_REIL_LIGHTS" ) {
-        // cout << "found a reil" << endl;
-        ssgTransform *reil = gen_reil_lights( nodes, normals, pnt_i, nml_i,
-                                              matlib, up );
-        return reil;
-    } else if ( material == "RWY_ODALS_LIGHTS" ) {
-        // cout << "found a odals" << endl;
-        ssgTransform *odals = gen_odals_lights( nodes, normals, pnt_i, nml_i,
-                                                matlib, up );
-        return odals;
-    } else if ( material == "RWY_SEQUENCED_LIGHTS" ) {
-        // cout << "found a rabbit" << endl;
-        ssgTransform *rabbit = gen_rabbit_lights( nodes, normals,
-                                                  pnt_i, nml_i,
-                                                  matlib, up );
-        return rabbit;
-    } else if ( material == "RWY_VASI_LIGHTS" ) {
-        ssgTransform *light_group = gen_dir_light_group( nodes, normals, pnt_i,
-                                                         nml_i, mat, up,
-                                                         false );
-
-        // calculate the geocentric position of this vasi and use it
-        // to init the vasi structure and save it in the userdata slot
-        sgdVec3 pos;
-        sgdSetVec3( pos, nodes[pnt_i[0]][0], nodes[pnt_i[0]][1],
-                    nodes[pnt_i[0]][2] );
-        // dup is the double version of the "up" vector which is also
-        // the reference center point of this tile.  The reference
-        // center + the coordinate of the first light gives the actual
-        // location of the first light.
-        sgdAddVec3( pos, dup );
-
-        // extract a pointer to the leaf node so a) we can set the
-        // phat light call back and b) we can pass this to the vasi
-        // structure.
-        ssgRangeSelector *lod = (ssgRangeSelector *)light_group->getKid(0);
-        ssgLeaf *leaf = (ssgLeaf *)lod->getKid(0);
-        leaf->setCallback( SSG_CALLBACK_PREDRAW, VASIPreDraw );
-        leaf->setCallback( SSG_CALLBACK_POSTDRAW, VASIPostDraw );
-
-        SGVASIUserData *vasi = new SGVASIUserData( pos, leaf );
-
-        light_group->setUserData( vasi );
-
-        return light_group;
-    } else if ( material == "RWY_BLUE_TAXIWAY_LIGHTS" ) {
-        ssgTransform *light_group = gen_dir_light_group( nodes, normals, pnt_i,
-                                                         nml_i, mat, up,
-                                                         true );
-        return light_group;
-    } else {
-        ssgTransform *light_group = gen_dir_light_group( nodes, normals, pnt_i,
-                                                         nml_i, mat, up,
-                                                         false );
-        return light_group;
-    }
+  if (lights.getNumLights() <= 0)
+    return 0;
+
+  // generate a repeatable random seed
+  sg_srandom(unsigned(lights.getLight(0).position[0]));
+  float flashTime = 2e-2 + 5e-3*sg_random();
+  osg::Sequence* sequence = new osg::Sequence;
+  sequence->setDefaultTime(flashTime);
+  Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.0001, 0.00000001),
+                                  6.0f, 10.0f, true);
+  for (int i = lights.getNumLights() - 1; 0 <= i; --i) {
+    EffectGeode* egeode = new EffectGeode;
+    egeode->setEffect(effect);
+    egeode->addDrawable(getLightDrawable(lights.getLight(i)));
+    sequence->addChild(egeode, flashTime);
+  }
+  sequence->addChild(new osg::Group, 1 + 1e-1*sg_random());
+  sequence->setInterval(osg::Sequence::LOOP, 0, -1);
+  sequence->setDuration(1.0f, -1);
+  sequence->setMode(osg::Sequence::START);
+  sequence->setSync(true);
+  return sequence;
+}
 
-    return NULL;
+osg::Node*
+SGLightFactory::getOdal(const SGLightBin& lights)
+{
+  if (lights.getNumLights() < 2)
+    return 0;
+
+  // generate a repeatable random seed
+  sg_srandom(unsigned(lights.getLight(0).position[0]));
+  float flashTime = 2e-2 + 5e-3*sg_random();
+  osg::Sequence* sequence = new osg::Sequence;
+  sequence->setDefaultTime(flashTime);
+  Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.0001, 0.00000001),
+                                  6.0, 10.0, false);
+  // centerline lights
+  for (int i = lights.getNumLights() - 1; 2 <= i; --i) {
+    EffectGeode* egeode = new EffectGeode;
+    egeode->setEffect(effect);
+    egeode->addDrawable(getLightDrawable(lights.getLight(i)));
+    sequence->addChild(egeode, flashTime);
+  }
+  // runway end lights
+  osg::Group* group = new osg::Group;
+  for (unsigned i = 0; i < 2; ++i) {
+    EffectGeode* egeode = new EffectGeode;
+    egeode->setEffect(effect);
+    egeode->addDrawable(getLightDrawable(lights.getLight(i)));
+    group->addChild(egeode);
+  }
+  sequence->addChild(group, flashTime);
+
+  // add an extra empty group for a break
+  sequence->addChild(new osg::Group, 9 + 1e-1*sg_random());
+  sequence->setInterval(osg::Sequence::LOOP, 0, -1);
+  sequence->setDuration(1.0f, -1);
+  sequence->setMode(osg::Sequence::START);
+  sequence->setSync(true);
+
+  return sequence;
 }