]> 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 71fbdcf263fcd774fa3b2178b84928e404e09f6f..e15b7beaae44367220cd3a26124bb80453998ab7 100644 (file)
 #  include <simgear_config.h>
 #endif
 
+#include "pt_lights.hxx"
+
+#include <map>
+#include <boost/tuple/tuple_comparison.hpp>
+
 #include <osg/Array>
 #include <osg/Geometry>
+#include <osg/CullFace>
 #include <osg/Geode>
-#include <osg/LOD>
 #include <osg/MatrixTransform>
 #include <osg/NodeCallback>
 #include <osg/NodeVisitor>
-#include <osg/Switch>
+#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/scene/material/mat.hxx>
-#include <simgear/screen/extensions.hxx>
 #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 "vasi.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 "pt_lights.hxx"
+#include "SGVasiDrawable.hxx"
 
-// static variables for use in ssg callbacks
-bool SGPointLightsUseSprites = false;
-bool SGPointLightsEnhancedLighting = false;
-bool SGPointLightsDistanceAttenuation = false;
+using OpenThreads::Mutex;
+using OpenThreads::ScopedLock;
 
+using namespace osg;
+using namespace simgear;
 
-// Specify the way we want to draw directional point lights (assuming the
-// appropriate extensions are available.)
-
-void SGConfigureDirectionalLights( bool use_point_sprites,
-                                   bool enhanced_lighting,
-                                   bool distance_attenuation ) {
-    SGPointLightsUseSprites = use_point_sprites;
-    SGPointLightsEnhancedLighting = enhanced_lighting;
-    SGPointLightsDistanceAttenuation = distance_attenuation;
-}
-
-static void calc_center_point( const point_list &nodes,
-                               const int_list &pnt_i,
-                               Point3D& result ) {
-    double minx = nodes[pnt_i[0]][0];
-    double maxx = nodes[pnt_i[0]][0];
-    double miny = nodes[pnt_i[0]][1];
-    double maxy = nodes[pnt_i[0]][1];
-    double minz = nodes[pnt_i[0]][2];
-    double maxz = nodes[pnt_i[0]][2];
-
-    for ( unsigned int i = 0; i < pnt_i.size(); ++i ) {
-        Point3D pt = nodes[pnt_i[i]];
-        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]; }
+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;
     }
-
-    result = Point3D((minx + maxx) / 2.0, (miny + maxy) / 2.0,
-                     (minz + maxz) / 2.0);
+  }
 }
 
-
-static osg::Node*
-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,
-                     const osg::Vec3& up, bool vertical, bool vasi )
+static osg::Image*
+getPointSpriteImage(int logResolution)
 {
-    Point3D center;
-    calc_center_point( nodes, pnt_i, center );
-    // cout << center[0] << "," << center[1] << "," << center[2] << endl;
-
-
-    // find a vector perpendicular to the normal.
-    osg::Vec3 perp1;
-    if ( !vertical ) {
-        // normal isn't vertical so we can use up as our first vector
-        perp1 = up;
-    } else {
-        // normal is vertical so we have to work a bit harder to
-        // determine our first vector
-        osg::Vec3 pt1(nodes[pnt_i[0]][0], nodes[pnt_i[0]][1],
-                      nodes[pnt_i[0]][2] );
-        osg::Vec3 pt2(nodes[pnt_i[1]][0], nodes[pnt_i[1]][1],
-                      nodes[pnt_i[1]][2] );
-
-        perp1 = pt2 - pt1;
-    }
-    perp1.normalize();
-
-    osg::Vec3Array *vl = new osg::Vec3Array;
-    osg::Vec3Array *nl = new osg::Vec3Array;
-    osg::Vec4Array *cl = new osg::Vec4Array;
-
-    unsigned int i;
-    for ( i = 0; i < pnt_i.size(); ++i ) {
-        Point3D ppt = nodes[pnt_i[i]] - center;
-        osg::Vec3 pt(ppt[0], ppt[1], ppt[2]);
-        osg::Vec3 normal(normals[nml_i[i]][0], normals[nml_i[i]][1],
-                         normals[nml_i[i]][2] );
-
-        // calculate a vector perpendicular to dir and up
-        osg::Vec3 perp2 = normal^perp1;
-
-        // front face
-        osg::Vec3 tmp3 = pt;
-        vl->push_back( tmp3 );
-        tmp3 += perp1;
-        vl->push_back( tmp3 );
-        tmp3 += perp2;
-        vl->push_back( tmp3 );
-
-        nl->push_back( normal );
-        nl->push_back( normal );
-        nl->push_back( normal );
-
-        cl->push_back(osg::Vec4(1, 1, 1, 1));
-        cl->push_back(osg::Vec4(1, 1, 1, 0));
-        cl->push_back(osg::Vec4(1, 1, 1, 0));
-    }
-
-    osg::Geometry* geometry = new osg::Geometry;
-    geometry->setName("Dir Lights " + mat->get_names().front());
-    geometry->setVertexArray(vl);
-    geometry->setNormalArray(nl);
-    geometry->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
-    geometry->setColorArray(cl);
-    geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
-    geometry->addPrimitiveSet(new osg::DrawArrays(GL_TRIANGLES, 0, vl->size()));
-    osg::Geode* geode = new osg::Geode;
-    geode->addDrawable(geometry);
-
-    if (vasi) {
-        // this one is dynamic in its colors, so do not bother with dlists
-        geometry->setUseDisplayList(false);
-        geometry->setUseVertexBufferObjects(false);
-        osg::Vec3 dir(normals[nml_i[0]][0], normals[nml_i[0]][1],
-                      normals[nml_i[0]][2]);
-
-        // calculate the reference position of this vasi and use it
-        // to init the vasi structure
-        Point3D ppt = nodes[pnt_i[0]] - center;
-        osg::Vec3 pt(ppt[0], ppt[1], ppt[2]);
-        // up is 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.
-        pt += up;
-
-        // Set up the callback
-        geode->setCullCallback(new SGVasiUpdateCallback(cl, pt, up, dir));
-    }
-
-    if ( mat != NULL ) {
-        geode->setStateSet(mat->get_state());
-    } else {
-        SG_LOG( SG_TERRAIN, SG_ALERT, "Warning: material = NULL" );
-    }
-
-    // put an LOD on each lighting component
-    osg::LOD *lod = new osg::LOD;
-    lod->addChild( geode, 0, 20000 );
+  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;
+}
 
-    // create the transformation.
-    osg::MatrixTransform *trans = new osg::MatrixTransform;
-    trans->setMatrix(osg::Matrixd::translate(osg::Vec3d(center[0], center[1], center[2])));
-    trans->addChild( lod );
+static Mutex lightMutex;
 
-    return trans;
+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();
 }
 
-static osg::Node *gen_reil_lights( const point_list &nodes,
-                                   const point_list &normals,
-                                   const int_list &pnt_i,
-                                   const int_list &nml_i,
-                                   SGMaterialLib *matlib,
-                                   const osg::Vec3& up )
+namespace
 {
-    Point3D center;
-    calc_center_point( nodes, pnt_i, center );
-    // cout << center[0] << "," << center[1] << "," << center[2] << endl;
-
-    osg::Vec3 nup = up;
-    nup.normalize();
-
-    osg::Vec3Array   *vl = new osg::Vec3Array;
-    osg::Vec3Array   *nl = new osg::Vec3Array;
-    osg::Vec4Array   *cl = new osg::Vec4Array;
-
-    unsigned int i;
-    for ( i = 0; i < pnt_i.size(); ++i ) {
-        Point3D ppt = nodes[pnt_i[i]] - center;
-        osg::Vec3 pt(ppt[0], ppt[1], ppt[2]);
-        osg::Vec3 normal(normals[nml_i[i]][0], normals[nml_i[i]][1],
-                         normals[nml_i[i]][2] );
-
-        // calculate a vector perpendicular to dir and up
-        osg::Vec3 perp = normal^up;
-
-        // front face
-        osg::Vec3 tmp3 = pt;
-        vl->push_back( tmp3 );
-        tmp3 += nup;
-        vl->push_back( tmp3 );
-        tmp3 += perp;
-        vl->push_back( tmp3 );
-
-        nl->push_back( normal );
-        nl->push_back( normal );
-        nl->push_back( normal );
-
-        cl->push_back(osg::Vec4(1, 1, 1, 1));
-        cl->push_back(osg::Vec4(1, 1, 1, 0));
-        cl->push_back(osg::Vec4(1, 1, 1, 0));
-    }
+typedef boost::tuple<float, osg::Vec3, float, float, bool> PointParams;
+typedef std::map<PointParams, ref_ptr<Effect> > EffectMap;
 
-    SGMaterial *mat = matlib->find( "RWY_WHITE_LIGHTS" );
-
-    osg::Geometry* geometry = new osg::Geometry;
-    geometry->setName("Reil Lights " + mat->get_names().front());
-    geometry->setVertexArray(vl);
-    geometry->setNormalArray(nl);
-    geometry->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
-    geometry->setColorArray(cl);
-    geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
-    geometry->addPrimitiveSet(new osg::DrawArrays(GL_TRIANGLES, 0, vl->size()));
-    osg::Geode* geode = new osg::Geode;
-    geode->addDrawable(geometry);
-
-    if ( mat != NULL ) {
-        geode->setStateSet( mat->get_state() );
-    } else {
-        SG_LOG( SG_TERRAIN, SG_ALERT,
-                "Warning: can't find material = RWY_WHITE_LIGHTS" );
-    }
+EffectMap effectMap;
 
-    // OSGFIXME
-//     leaf->setCallback( SSG_CALLBACK_PREDRAW, StrobePreDraw );
-//     leaf->setCallback( SSG_CALLBACK_POSTDRAW, StrobePostDraw );
-
-    // OSGFIXME: implement an update callback that switches on/off
-    // based on the osg::FrameStamp
-    osg::Switch *reil = new osg::Switch;
-//     reil->setDuration( 60 );
-//     reil->setLimits( 0, 2 );
-//     reil->setMode( SSG_ANIM_SHUTTLE );
-//     reil->control( SSG_ANIM_START );
-
-    // need to add this twice to work around an ssg bug
-    reil->addChild(geode, true);
-   
-    // put an LOD on each lighting component
-    osg::LOD *lod = new osg::LOD;
-    lod->addChild( reil, 0, 12000 /*OSGFIXME: hardcoded here?*/);
-
-    // create the transformation.
-    osg::MatrixTransform *trans = new osg::MatrixTransform;
-    trans->setMatrix(osg::Matrixd::translate(osg::Vec3d(center[0], center[1], center[2])));
-    trans->addChild( lod );
-
-    return trans;
+ref_ptr<PolygonMode> polyMode = new PolygonMode(PolygonMode::FRONT,
+                                                PolygonMode::POINT);
+ref_ptr<PointSprite> pointSprite = new PointSprite;
 }
 
-
-static osg::Node *gen_odals_lights( const point_list &nodes,
-                                               const point_list &normals,
-                                               const int_list &pnt_i,
-                                               const int_list &nml_i,
-                                               SGMaterialLib *matlib,
-                                               const osg::Vec3& up )
+Effect* getLightEffect(float size, const Vec3& attenuation,
+                       float minSize, float maxSize, bool directional)
 {
-    Point3D center;
-    calc_center_point( nodes, pnt_i, center );
-    // cout << center[0] << "," << center[1] << "," << center[2] << endl;
-
-    // OSGFIXME: implement like above
-//     osg::Switch *odals = new osg::Switch;
-    osg::Group *odals = new osg::Group;
-
-    // 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" );
+    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());
     }
+    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;
+}
 
-    osg::Vec3Array   *vl = new osg::Vec3Array;
-    osg::Vec4Array   *cl = new osg::Vec4Array;
-     
-    cl->push_back(osg::Vec4(1, 1, 1, 1));
 
-    // center line strobes
-    for ( unsigned i = pnt_i.size() - 1; i >= 2; --i ) {
-        Point3D ppt = nodes[pnt_i[i]] - center;
-        osg::Vec3 pt(ppt[0], ppt[1], ppt[2]);
-        vl->push_back(pt);
-    }
+osg::Drawable*
+SGLightFactory::getLightDrawable(const SGLightBin::Light& light)
+{
+  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;
+}
 
-    // runway end strobes
-     
-    Point3D ppt = nodes[pnt_i[0]] - center;
-    osg::Vec3 pt(ppt[0], ppt[1], ppt[2]);
-    vl->push_back(pt);
-
-    ppt = nodes[pnt_i[1]] - center;
-    pt = osg::Vec3(ppt[0], ppt[1], ppt[2]);
-    vl->push_back(pt);
-
-    osg::Geometry* geometry = new osg::Geometry;
-    geometry->setName("Odal Lights " + mat->get_names().front());
-    geometry->setVertexArray(vl);
-    geometry->setColorArray(cl);
-    geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
-    geometry->addPrimitiveSet(new osg::DrawArrays(GL_POINTS, 0, vl->size()));
-    osg::Geode* geode = new osg::Geode;
-    geode->addDrawable(geometry);
-
-    geode->setStateSet( mat->get_state() );
-    // OSGFIXME
-//         leaf->setCallback( SSG_CALLBACK_PREDRAW, StrobePreDraw );
-//         leaf->setCallback( SSG_CALLBACK_POSTDRAW, StrobePostDraw );
-
-    odals->addChild( geode );
-
-    // 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
-    osg::LOD *lod = new osg::LOD;
-    lod->addChild( odals, 0, 12000 /*OSGFIXME hardcoded visibility*/ );
-
-    // create the transformation.
-    osg::MatrixTransform *trans = new osg::MatrixTransform;
-    trans->setMatrix(osg::Matrixd::translate(osg::Vec3d(center[0], center[1], center[2])));
-    trans->addChild(lod);
-
-    return trans;
+osg::Drawable*
+SGLightFactory::getLightDrawable(const SGDirectionalLightBin::Light& light)
+{
+  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;
 }
 
-class SGRabbitUpdateCallback : public osg::NodeCallback {
-public:
-  SGRabbitUpdateCallback(double duration) :
-    mBaseTime(sg_random()), mDuration(duration)
-  {
-    if (fabs(mDuration) < 1e-3)
-      mDuration = 1e-3;
-    mBaseTime -= mDuration*floor(mBaseTime/mDuration);
+namespace
+{
+  ref_ptr<StateSet> simpleLightSS;
+}
+osg::Drawable*
+SGLightFactory::getLights(const SGLightBin& lights, unsigned inc, float alphaOff)
+{
+  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);
 
-  virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
   {
-    assert(dynamic_cast<osg::Switch*>(node));
-    osg::Switch* sw = static_cast<osg::Switch*>(node);
-    double frameTime = nv->getFrameStamp()->getReferenceTime();
-    double timeDiff = (frameTime - mBaseTime)/mDuration;
-    double reminder = timeDiff - unsigned(floor(timeDiff));
-    unsigned nChildren = sw->getNumChildren();
-    unsigned activeChild = unsigned(nChildren*reminder);
-    if (nChildren <= activeChild)
-      activeChild = nChildren;
-    sw->setSingleChildOn(activeChild);
-
-    osg::NodeCallback::operator()(node, nv);
+    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());
+    }
   }
-public:
-  double mBaseTime;
-  double mDuration;
-};
-
-
-static osg::Node *gen_rabbit_lights( const point_list &nodes,
-                                     const point_list &normals,
-                                     const int_list &pnt_i,
-                                     const int_list &nml_i,
-                                     SGMaterialLib *matlib,
-                                     const osg::Vec3& up )
-{
-    Point3D center;
-    calc_center_point( nodes, pnt_i, center );
-    // cout << center[0] << "," << center[1] << "," << center[2] << endl;
-
-    osg::Vec3 nup = up;
-    nup.normalize();
+  geometry->setStateSet(simpleLightSS.get());
+  return geometry;
+}
 
-    // OSGFIXME: implement like above ...
-    osg::Switch *rabbit = new osg::Switch;
-    rabbit->setUpdateCallback(new SGRabbitUpdateCallback(10));
 
-    SGMaterial *mat = matlib->find( "RWY_WHITE_LIGHTS" );
-    if ( mat == NULL ) {
-        SG_LOG( SG_TERRAIN, SG_ALERT,
-                "Warning: can't material = RWY_WHITE_LIGHTS" );
-    }
+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;
+}
 
-    for ( int i = pnt_i.size() - 1; i >= 0; --i ) {
-        osg::Vec3Array   *vl = new osg::Vec3Array;
-        osg::Vec3Array   *nl = new osg::Vec3Array;
-        osg::Vec4Array   *cl = new osg::Vec4Array;
-
-
-        Point3D ppt = nodes[pnt_i[i]] - center;
-        osg::Vec3 pt(ppt[0], ppt[1], ppt[2]);
-        osg::Vec3 normal(normals[nml_i[i]][0], normals[nml_i[i]][1],
-                         normals[nml_i[i]][2] );
-
-        // calculate a vector perpendicular to dir and up
-        osg::Vec3 perp = normal^nup;
-
-        // front face
-        osg::Vec3 tmp3 = pt;
-        vl->push_back( tmp3 );
-        tmp3 += nup;
-        vl->push_back( tmp3 );
-        tmp3 += perp;
-        vl->push_back( tmp3 );
-
-        nl->push_back(normal);
-
-        cl->push_back(osg::Vec4(1, 1, 1, 1));
-        cl->push_back(osg::Vec4(1, 1, 1, 0));
-        cl->push_back(osg::Vec4(1, 1, 1, 0));
-
-        osg::Geometry* geometry = new osg::Geometry;
-        geometry->setName("Rabbit Lights " + mat->get_names().front());
-        geometry->setVertexArray(vl);
-        geometry->setNormalArray(nl);
-        geometry->setNormalBinding(osg::Geometry::BIND_OVERALL);
-        geometry->setColorArray(cl);
-        geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
-        geometry->addPrimitiveSet(new osg::DrawArrays(GL_TRIANGLES, 0, vl->size()));
-        osg::Geode* geode = new osg::Geode;
-        geode->addDrawable(geometry);
-        
-        geode->setStateSet( mat->get_state() );
-
-        // OSGFIXME
-//         leaf->setCallback( SSG_CALLBACK_PREDRAW, StrobePreDraw );
-//         leaf->setCallback( SSG_CALLBACK_POSTDRAW, StrobePostDraw );
-
-        rabbit->addChild( geode );
-    }
+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;
+  }
+}
 
-//     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
-    osg::LOD *lod = new osg::LOD;
-    lod->addChild( rabbit, 0, 12000 /*OSGFIXME: hadcoded*/ );
-
-    // create the transformation.
-    osg::MatrixTransform *trans = new osg::MatrixTransform;
-    trans->setMatrix(osg::Matrixd::translate(osg::Vec3d(center[0], center[1], center[2])));
-    trans->addChild(lod);
-
-    return trans;
+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;
 }
 
+osg::Node*
+SGLightFactory::getSequenced(const SGDirectionalLightBin& lights)
+{
+  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;
+}
 
-osg::Node *SGMakeDirectionalLights( const point_list &nodes,
-                                    const point_list &normals,
-                                    const int_list &pnt_i,
-                                    const int_list &nml_i,
-                                    SGMaterialLib *matlib,
-                                    const string &material,
-                                    const SGVec3d& dup )
+osg::Node*
+SGLightFactory::getOdal(const SGLightBin& lights)
 {
-    osg::Vec3 nup = toVec3f(dup).osg();
-    nup.normalize();
-
-    SGMaterial *mat = matlib->find( material );
-
-    if ( material == "RWY_REIL_LIGHTS" ) {
-        // cout << "found a reil" << endl;
-        return gen_reil_lights( nodes, normals, pnt_i, nml_i,
-                                matlib, nup );
-    } else if ( material == "RWY_ODALS_LIGHTS" ) {
-        // cout << "found a odals" << endl;
-        return gen_odals_lights( nodes, normals, pnt_i, nml_i,
-                                 matlib, nup );
-    } else if ( material == "RWY_SEQUENCED_LIGHTS" ) {
-        // cout << "found a rabbit" << endl;
-        return gen_rabbit_lights( nodes, normals, pnt_i, nml_i,
-                                  matlib, nup );
-    } else if ( material == "RWY_VASI_LIGHTS" ) {
-        return gen_dir_light_group( nodes, normals, pnt_i,
-                                    nml_i, mat, nup, false, true );
-    } else if ( material == "RWY_BLUE_TAXIWAY_LIGHTS" ) {
-        return gen_dir_light_group( nodes, normals, pnt_i, nml_i, mat, nup,
-                                    true, false );
-    } else {
-        return gen_dir_light_group( nodes, normals, pnt_i, nml_i, mat, nup,
-                                    false, false );
-    }
+  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 NULL;
+  return sequence;
 }