]> git.mxchange.org Git - simgear.git/commitdiff
memory reduced tile loading.
authorPeter Sadrozinski <psadrozinski@gmail.com>
Sat, 10 Jan 2015 12:58:21 +0000 (07:58 -0500)
committerChristian Schmitt <chris@ilovelinux.de>
Mon, 19 Jan 2015 20:06:27 +0000 (21:06 +0100)
- do not save the TileGeometryBin and matcach in the randomObjectCallback
- recreate matcache, and get TileGeometry from scenegraph
- split obj.cxx into three distinct files - loadBTG, load surface geometry, and load tile details
- includes fix for sceneries that have missing materials

simgear/scene/material/EffectGeode.hxx
simgear/scene/material/mat.cxx
simgear/scene/material/mat.hxx
simgear/scene/tgdb/CMakeLists.txt
simgear/scene/tgdb/SGNodeTriangles.hxx [new file with mode: 0644]
simgear/scene/tgdb/SGTileDetailsCallback.hxx [new file with mode: 0644]
simgear/scene/tgdb/SGTileGeometryBin.hxx [new file with mode: 0644]
simgear/scene/tgdb/obj.cxx

index 34b87e3af0f36117a13d4554facd64d7c308a76e..e4b024b7f6d52f0e029806d9157f097be50d66f8 100644 (file)
@@ -23,6 +23,7 @@
 #include <boost/iterator/iterator_adaptor.hpp>
 
 #include "Effect.hxx"
+#include "mat.hxx"
 
 namespace simgear
 {
@@ -69,6 +70,8 @@ class EffectGeode : public osg::Geode
     META_Node(simgear,EffectGeode);
     Effect* getEffect() const { return _effect.get(); }
     void setEffect(Effect* effect);
+    SGMaterial* getMaterial() const { return _material; }
+    void setMaterial(SGMaterial* mat) { _material = mat; }
     virtual void resizeGLObjectBuffers(unsigned int maxSize);
     virtual void releaseGLObjects(osg::State* = 0) const;
 
@@ -83,6 +86,7 @@ class EffectGeode : public osg::Geode
     void runGenerators(osg::Geometry *geometry);
 private:
     osg::ref_ptr<Effect> _effect;
+    SGMaterial* _material;
 };
 }
 #endif
index 546b807e208d9df36cbf532eb00c5de6bc1d7c00..a39c71c153cffa098ce2e6299a99952ca5ab111b 100644 (file)
@@ -441,7 +441,7 @@ Effect* SGMaterial::get_effect(int i)
     return _status[i].effect.get();
 }
 
-Effect* SGMaterial::get_effect(const SGTexturedTriangleBin& triangleBin)
+Effect* SGMaterial::get_one_effect(int texIndex)
 {
     SGGuard<SGMutex> g(_lock);
     if (_status.empty()) {
@@ -449,7 +449,7 @@ Effect* SGMaterial::get_effect(const SGTexturedTriangleBin& triangleBin)
         return 0;
     }
     
-    int i = triangleBin.getTextureIndex() % _status.size();
+    int i = texIndex % _status.size();
     return get_effect(i);
 }
 
@@ -460,7 +460,7 @@ Effect* SGMaterial::get_effect()
 }
 
 
-osg::Texture2D* SGMaterial::get_object_mask(const SGTexturedTriangleBin& triangleBin)
+osg::Texture2D* SGMaterial::get_one_object_mask(int texIndex)
 {
     if (_status.empty()) {
         SG_LOG( SG_GENERAL, SG_WARN, "No mask available.");
@@ -469,7 +469,7 @@ osg::Texture2D* SGMaterial::get_object_mask(const SGTexturedTriangleBin& triangl
     
     // Note that the object mask is closely linked to the texture/effect
     // so we index based on the texture index, 
-    unsigned int i = triangleBin.getTextureIndex() % _status.size();
+    unsigned int i = texIndex % _status.size();
     if (i < _masks.size()) {
         return _masks[i].get();
     } else {
index 5a37b756f0024d1591cc73b27e4563b9fcc51dea..ef420d947552a5efbff9250729814e392fd0e0cd 100644 (file)
@@ -119,13 +119,13 @@ public:
   /**
    * Get the textured state.
    */
-  simgear::Effect* get_effect(const SGTexturedTriangleBin& triangleBin);
+  simgear::Effect* get_one_effect(int texIndex);
   simgear::Effect* get_effect();
 
   /**
    * Get the textured state.
    */
-  osg::Texture2D* get_object_mask(const SGTexturedTriangleBin& triangleBin);
+  osg::Texture2D* get_one_object_mask(int texIndex);
 
 
   /**
index cdf6a3dec155450db7e75203e8bd642db878a395..590f227051b4c436e52aa6044122e049315c277a 100644 (file)
@@ -8,9 +8,12 @@ set(HEADERS
     SGDirectionalLightBin.hxx
     SGLightBin.hxx
     SGModelBin.hxx
+    SGNodeTriangles.hxx
     SGOceanTile.hxx
     SGReaderWriterBTG.hxx
     SGTexturedTriangleBin.hxx
+    SGTileDetailsCallback.hxx
+    SGTileGeometryBin.hxx
     SGTriangleBin.hxx
     SGVasiDrawable.hxx
     SGVertexArrayBin.hxx
diff --git a/simgear/scene/tgdb/SGNodeTriangles.hxx b/simgear/scene/tgdb/SGNodeTriangles.hxx
new file mode 100644 (file)
index 0000000..9ec3765
--- /dev/null
@@ -0,0 +1,482 @@
+// future API - just run through once to convert from OSG to SG
+// then we can use these triangle lists for random 
+// trees/lights/buildings/objects
+struct SGTexturedTriangle
+{
+public:
+    std::vector<SGVec3f> vertices;
+    std::vector<SGVec2f> texcoords;
+};
+
+struct SGBorderContour
+{
+public:
+    SGVec3d start;
+    SGVec3d end;
+};
+
+class SGTriangleInfo
+{
+public:
+    SGTriangleInfo( const SGVec3d& center ) {
+        gbs_center = center;
+        mt_init(&seed, 123);
+    }
+
+    // API used to build the Info by the visitor
+    void addGeometry( osg::Geometry* g ) { 
+        geometries.push_back(g); 
+    }
+    
+    void setMaterial( SGMaterial* m ) { 
+        mat = m; 
+    }
+
+    SGMaterial* getMaterial( void ) const {
+        return mat; 
+    }
+    
+    // API used to get a specific texture or effect from a material.  Materials can have
+    // multiple textures - use the floor of the x coordinate of the first vertes to select it.
+    // This will be constant, and give the same result each time to select one effect/texture per drawable.
+    int getTextureIndex( void ) const {
+        int texInfo = 0;
+        const osg::Vec3Array* vertices =  dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
+        if ( vertices ) {
+            const osg::Vec3 *v0 = &vertices->operator[](0);
+            texInfo = floor(v0->x()); 
+        }
+        return texInfo;
+    }
+    
+    // new API - TODO
+    void getTriangles( std::vector<SGTexturedTriangle>& tris )
+    {
+        const osg::Vec3Array* vertices  = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
+        const osg::Vec2Array* texcoords = dynamic_cast<osg::Vec2Array*>(geometries[0]->getTexCoordArray(0));
+        
+        int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
+        if ( numPrimitiveSets > 0 ) {
+            const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
+            unsigned int numIndices = ps->getNumIndices();
+            
+            for ( unsigned int i=2; i<numIndices; i+= 3 ) {
+                SGTexturedTriangle tri;
+                
+                tri.vertices.push_back( toSG(vertices->operator[](ps->index(i-2))) );
+                tri.vertices.push_back( toSG(vertices->operator[](ps->index(i-1))) );
+                tri.vertices.push_back( toSG(vertices->operator[](ps->index(i-0))) );
+                
+                tri.texcoords.push_back( toSG(texcoords->operator[](ps->index(i-2))) );
+                tri.texcoords.push_back( toSG(texcoords->operator[](ps->index(i-1))) );
+                tri.texcoords.push_back( toSG(texcoords->operator[](ps->index(i-0))) );
+            }
+        }
+    }
+    
+    void getBorderContours( std::vector<SGBorderContour>& border )
+    {
+        // each structure contains a list of target indexes and a count
+        int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
+        if ( numPrimitiveSets > 0 ) {
+            const osg::Vec3Array* vertices  = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
+
+            const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
+            unsigned int numTriangles = ps->getNumIndices()/3;
+
+            // use a map for fast lookup map the segment as a 64 bit int
+            std::map<uint64_t, int> segCounter;
+            uint32_t idx1, idx2;
+            uint64_t key;
+            
+            for ( unsigned int i=0; i<numTriangles; i+= 3 ) {
+                // first seg
+                if ( ps->index(i+0) < ps->index(i+1) ) {
+                    idx1 = ps->index(i+0);
+                    idx2 = ps->index(i+1);
+                } else {
+                    idx1 = ps->index(i+1);
+                    idx2 = ps->index(i+0);                    
+                }
+                
+                key=( (uint64_t)idx1<<32) | (uint64_t)idx2;
+                SG_LOG(SG_TERRAIN, SG_ALERT, "key " << std::hex << key << std::dec << " count is " << segCounter[key] );
+                segCounter[key]++;
+                SG_LOG(SG_TERRAIN, SG_ALERT, "after increment key " << std::hex << key << std::dec << " count is " << segCounter[key] );
+                
+                // second seg
+                if ( ps->index(i+1) < ps->index(i+2) ) {
+                    idx1 = ps->index(i+1);
+                    idx2 = ps->index(i+2);
+                } else {
+                    idx1 = ps->index(i+2);
+                    idx2 = ps->index(i+1);             
+                }
+                
+                key=( (uint64_t)idx1<<32) | (uint64_t)idx2;
+                SG_LOG(SG_TERRAIN, SG_ALERT, "key " << std::hex << key << std::dec << " count is " << segCounter[key] );
+                segCounter[key]++;
+                SG_LOG(SG_TERRAIN, SG_ALERT, "after increment key " << std::hex << key << std::dec << " count is " << segCounter[key] );
+                
+                // third seg
+                if ( ps->index(i+2) < ps->index(i+0) ) {
+                    idx1 = ps->index(i+2);
+                    idx2 = ps->index(i+0);
+                } else {
+                    idx1 = ps->index(i+0);
+                    idx2 = ps->index(i+2);                    
+                }
+                
+                key=( (uint64_t)idx1<<32) | (uint64_t)idx2;
+                SG_LOG(SG_TERRAIN, SG_ALERT, "key " << std::hex << key << std::dec << " count is " << segCounter[key] );
+                segCounter[key]++;
+                SG_LOG(SG_TERRAIN, SG_ALERT, "after increment key " << std::hex << key << std::dec << " count is " << segCounter[key] );
+            }
+            
+            // return all segments with count = 1 ( border )
+            std::map<uint64_t, int>::iterator segIt = segCounter.begin();            
+            while ( segIt != segCounter.end() ) {
+                if ( segIt->second == 1 ) {
+                    SG_LOG(SG_TERRAIN, SG_ALERT, "key " << std::hex << segIt->first << std::dec << " count is " << segIt->second );
+                    
+                    unsigned int iStart = segIt->first >> 32;
+                    unsigned int iEnd   = segIt->first & 0x00000000FFFFFFFF;
+                    
+                    SGBorderContour bc;
+                    
+                    bc.start = toVec3d(toSG(vertices->operator[](iStart)));
+                    bc.end   = toVec3d(toSG(vertices->operator[](iEnd)));
+                    border.push_back( bc );
+                }
+                segIt++;
+            }
+            
+#if 0
+            // debug out - requires GDAL
+            //
+            //
+            //
+            SGGeod  geodPos = SGGeod::fromCart(gbs_center);
+            SGQuatd hlOr = SGQuatd::fromLonLat(geodPos)*SGQuatd::fromEulerDeg(0, 0, 180);
+
+            for ( unsigned int i=0; i<border.size(); i++ ){
+                // de-rotate and translate : todo - create a paralell vertex list so we just do this 
+                // once per vertex, not for every triangle's use of the vertex
+                SGVec3d sgVStart = hlOr.backTransform( border[i].start) + gbs_center;
+                SGVec3d sgVEnd   = hlOr.backTransform( border[i].end)   + gbs_center;
+                    
+                // convert from cartesian to Geodetic, and save as a list of Geods for output
+                SGGeod gStart = SGGeod::fromCart(sgVStart);
+                SGGeod gEnd   = SGGeod::fromCart(sgVEnd);
+                
+                SGShapefile::FromSegment( gStart, gEnd, true, "./borders", mat->get_names()[0], "border" );
+            }
+#endif            
+        }
+    }
+        
+    // Random buildings API - get num triangles, then get a triangle at index
+    unsigned int getNumTriangles( void ) const {
+        unsigned int num_triangles = 0;
+
+        if ( !geometries.empty() ) {
+            int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
+            if ( numPrimitiveSets > 0 ) {
+                const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
+                unsigned int numIndices = ps->getNumIndices();
+                num_triangles = numIndices/3;
+            }
+        }
+        
+        return num_triangles;
+    }
+    
+    void getTriangle(unsigned int i, std::vector<SGVec3f>& triVerts, std::vector<SGVec2f>& triTCs) const {
+        const osg::Vec3Array* vertices  = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
+        const osg::Vec2Array* texcoords = dynamic_cast<osg::Vec2Array*>(geometries[0]->getTexCoordArray(0));
+
+        if ( !geometries.empty() ) {
+            int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
+            if ( numPrimitiveSets > 0 ) {
+                const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
+                int idxStart = i*3;
+                
+                triVerts.push_back( toSG(vertices->operator[](ps->index(idxStart+0))) );
+                triVerts.push_back( toSG(vertices->operator[](ps->index(idxStart+1))) );
+                triVerts.push_back( toSG(vertices->operator[](ps->index(idxStart+2))) );
+                
+                triTCs.push_back( toSG(texcoords->operator[](ps->index(idxStart+0))) );
+                triTCs.push_back( toSG(texcoords->operator[](ps->index(idxStart+1))) );
+                triTCs.push_back( toSG(texcoords->operator[](ps->index(idxStart+2))) );
+            }
+        }
+    }
+    
+    // random lights and trees - just get a list of points on where to add the light / tree
+    // TODO move this out - and handle in the random light / tree code
+    // just use generic triangle API.
+    void addRandomSurfacePoints(float coverage, float offset,
+                                osg::Texture2D* object_mask,
+                                std::vector<SGVec3f>& points)
+    {
+        if ( !geometries.empty() ) {
+            const osg::Vec3Array* vertices  = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
+            const osg::Vec2Array* texcoords = dynamic_cast<osg::Vec2Array*>(geometries[0]->getTexCoordArray(0));
+            
+            int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
+            if ( numPrimitiveSets > 0 ) {
+                const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
+                unsigned int numIndices = ps->getNumIndices();
+                
+                for ( unsigned int i=2; i<numIndices; i+= 3 ) {                    
+                    SGVec3f v0 = toSG(vertices->operator[](ps->index(i-2)));
+                    SGVec3f v1 = toSG(vertices->operator[](ps->index(i-1)));
+                    SGVec3f v2 = toSG(vertices->operator[](ps->index(i-0)));
+                    
+                    SGVec2f t0 = toSG(texcoords->operator[](ps->index(i-2)));
+                    SGVec2f t1 = toSG(texcoords->operator[](ps->index(i-1)));
+                    SGVec2f t2 = toSG(texcoords->operator[](ps->index(i-0)));
+                    
+                    SGVec3f normal = cross(v1 - v0, v2 - v0);
+            
+                    // Compute the area
+                    float area = 0.5f*length(normal);
+                    if (area <= SGLimitsf::min())
+                        continue;
+            
+                    // For partial units of area, use a zombie door method to
+                    // create the proper random chance of a light being created
+                    // for this triangle
+                    float unit = area + mt_rand(&seed)*coverage;
+            
+                    SGVec3f offsetVector = offset*normalize(normal);
+                    // generate a light point for each unit of area
+            
+                    while ( coverage < unit ) {
+                        float a = mt_rand(&seed);
+                        float b = mt_rand(&seed);
+                
+                        if ( a + b > 1 ) {
+                            a = 1 - a;
+                            b = 1 - b;
+                        }
+                        float c = 1 - a - b;
+                        SGVec3f randomPoint = offsetVector + a*v0 + b*v1 + c*v2;
+                
+                        if (object_mask != NULL) {
+                            SGVec2f texCoord = a*t0 + b*t1 + c*t2;
+                    
+                            // Check this random point against the object mask
+                            // red channel.
+                            osg::Image* img = object_mask->getImage();            
+                            unsigned int x = (int) (img->s() * texCoord.x()) % img->s();
+                            unsigned int y = (int) (img->t() * texCoord.y()) % img->t();
+                    
+                            if (mt_rand(&seed) < img->getColor(x, y).r()) {                
+                                points.push_back(randomPoint);        
+                            }                    
+                        } else {      
+                            // No object mask, so simply place the object  
+                            points.push_back(randomPoint);        
+                        }
+                        unit -= coverage;        
+                    }
+                }
+            }
+        }
+    }
+    
+    void addRandomTreePoints(float wood_coverage, 
+                             osg::Texture2D* object_mask,
+                             float vegetation_density,
+                             float cos_max_density_angle,
+                             float cos_zero_density_angle,
+                             std::vector<SGVec3f>& points)
+    {
+        if ( !geometries.empty() ) {
+            const osg::Vec3Array* vertices  = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
+            const osg::Vec2Array* texcoords = dynamic_cast<osg::Vec2Array*>(geometries[0]->getTexCoordArray(0));
+            
+            int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
+            if ( numPrimitiveSets > 0 ) {
+                const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
+                unsigned int numIndices = ps->getNumIndices();
+                
+                for ( unsigned int i=2; i<numIndices; i+= 3 ) {                    
+                    SGVec3f v0 = toSG(vertices->operator[](ps->index(i-2)));
+                    SGVec3f v1 = toSG(vertices->operator[](ps->index(i-1)));
+                    SGVec3f v2 = toSG(vertices->operator[](ps->index(i-0)));
+                    
+                    SGVec2f t0 = toSG(texcoords->operator[](ps->index(i-2)));
+                    SGVec2f t1 = toSG(texcoords->operator[](ps->index(i-1)));
+                    SGVec2f t2 = toSG(texcoords->operator[](ps->index(i-0)));
+                    
+                    SGVec3f normal = cross(v1 - v0, v2 - v0);
+            
+                    // Ensure the slope isn't too steep by checking the
+                    // cos of the angle between the slope normal and the
+                    // vertical (conveniently the z-component of the normalized
+                    // normal) and values passed in.                   
+                    float alpha = normalize(normal).z();
+                    float slope_density = 1.0;
+            
+                    if (alpha < cos_zero_density_angle) 
+                        continue; // Too steep for any vegetation      
+                        
+                    if (alpha < cos_max_density_angle) {
+                        slope_density = 
+                        (alpha - cos_zero_density_angle) / (cos_max_density_angle - cos_zero_density_angle);
+                    }
+                    
+                    // Compute the area
+                    float area = 0.5f*length(normal);
+                    if (area <= SGLimitsf::min())
+                        continue;
+                
+                    // Determine the number of trees, taking into account vegetation
+                    // density (which is linear) and the slope density factor.
+                    // Use a zombie door method to create the proper random chance 
+                    // of a tree being created for partial values.
+                    int woodcount = (int) (vegetation_density * vegetation_density * 
+                    slope_density *
+                    area / wood_coverage + mt_rand(&seed));
+                    
+                    for (int j = 0; j < woodcount; j++) {
+                        float a = mt_rand(&seed);
+                        float b = mt_rand(&seed);
+                        
+                        if ( a + b > 1.0f ) {
+                            a = 1.0f - a;
+                            b = 1.0f - b;
+                        }
+                        
+                        float c = 1.0f - a - b;
+                        
+                        SGVec3f randomPoint = a*v0 + b*v1 + c*v2;
+                        
+                        if (object_mask != NULL) {
+                            SGVec2f texCoord = a*t0 + b*t1 + c*t2;
+                            
+                            // Check this random point against the object mask
+                            // green (for trees) channel. 
+                            osg::Image* img = object_mask->getImage();            
+                            unsigned int x = (int) (img->s() * texCoord.x()) % img->s();
+                            unsigned int y = (int) (img->t() * texCoord.y()) % img->t();
+                            
+                            if (mt_rand(&seed) < img->getColor(x, y).g()) {  
+                                // The red channel contains the rotation for this object                                  
+                                points.push_back(randomPoint);
+                            }
+                        } else {
+                            points.push_back(randomPoint);
+                        }                
+                    }
+                }
+            }
+        }
+    }
+    
+#if 0    
+    // debug : this will save the tile as a shapefile that can be viewed in QGIS.
+    // NOTE: this is really slow....
+    // remember - we need to de-rotate the tile, then translate back to gbs_center.
+    void dumpBorder() {
+        //dump the first triangle only of the first geometry, for now...
+        SG_LOG(SG_TERRAIN, SG_ALERT, "effect geode has " << geometries.size() << " geometries" );
+        
+        const osg::Vec3Array* vertices =  dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
+        if ( vertices ) {
+            SG_LOG(SG_TERRAIN, SG_ALERT, " geometry has " << vertices->getNumElements() << " vertices" );
+        }
+        
+        if ( !geometries.empty() ) {
+            int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
+            SG_LOG(SG_TERRAIN, SG_ALERT, " geometry has " << numPrimitiveSets << " primitive sets" );
+            
+            if ( numPrimitiveSets > 0 ) {
+                const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
+                unsigned int numIndices = ps->getNumIndices();
+
+                // create the same quat we used to rotate here 
+                // - use backTransform to go back to original node location
+                SGGeod  geodPos = SGGeod::fromCart(gbs_center);
+                SGQuatd hlOr = SGQuatd::fromLonLat(geodPos)*SGQuatd::fromEulerDeg(0, 0, 180);
+                
+                SG_LOG(SG_TERRAIN, SG_ALERT, "  primitive set has has " << numIndices << " indices" );                
+                for ( unsigned int i=2; i<numIndices; i+= 3 ) {
+                    if ( numIndices >= 3 ) {                    
+                        unsigned int v0i = ps->index(i-2);
+                        unsigned int v1i = ps->index(i-1);
+                        unsigned int v2i = ps->index(i-0);
+                    
+                        const osg::Vec3 *v0 = &vertices->operator[](v0i);
+                        const osg::Vec3 *v1 = &vertices->operator[](v1i);
+                        const osg::Vec3 *v2 = &vertices->operator[](v2i);
+                   
+                        // de-rotate and translate : todo - create a paralell vertex list so we just do this 
+                        // once per vertex, not for every triangle's use of the vertex
+                        SGVec3d vec0 = hlOr.backTransform( toVec3d(toSG(*v0))) + gbs_center;
+                        SGVec3d vec1 = hlOr.backTransform( toVec3d(toSG(*v1))) + gbs_center;
+                        SGVec3d vec2 = hlOr.backTransform( toVec3d(toSG(*v2))) + gbs_center;
+                    
+                        // convert from cartesian to Geodetic, and save as a list of Geods for output
+                        std::vector<SGGeod> triangle;
+                        triangle.push_back( SGGeod::fromCart(vec0) );
+                        triangle.push_back( SGGeod::fromCart(vec1) );
+                        triangle.push_back( SGGeod::fromCart(vec2) );
+                    
+                        SGShapefile::FromGeodList( triangle, true, "./triangles", mat->get_names()[0], "tri" );
+                    }
+                }
+            }            
+            
+        }
+    }
+#endif    
+    
+private:
+    mt seed;
+    SGMaterial* mat;
+    SGVec3d gbs_center;
+    std::vector<osg::Geometry*> geometries;
+    std::vector<int> polygon_border;    // TODO
+};
+
+// This visitor will generate an SGTriangleInfo.
+// currently, it looks like it could save multiple lists, which could be the case
+// if multiple osg::geods are found with osg::Geometry.
+// But right now, we store a single PrimitiveSet under a single EffectGeod.  
+// so the traversal should only find a single EffectGeod - building a single SGTriangleInfo
+class GetNodeTriangles : public osg::NodeVisitor
+{
+public:
+    GetNodeTriangles(const SGVec3d& c, std::vector<SGTriangleInfo>* nt) : osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ), center(c), nodeTris(nt) {}
+    
+    // This method gets called for every node in the scene
+    //   graph. Check each node to see if it has user
+    //   out target. If so, save the node's address.
+    virtual void apply( osg::Node& node )
+    {
+        EffectGeode* eg = dynamic_cast<EffectGeode*>(&node);
+        if ( eg ) {
+            // get the material from the user info
+            SGTriangleInfo triInfo( center );
+            triInfo.setMaterial( eg->getMaterial() );
+
+            // let's find the drawables for this node
+            int numDrawables = eg->getNumDrawables();
+            for ( int i=0; i<numDrawables; i++ ) {
+                triInfo.addGeometry( eg->getDrawable(i)->asGeometry() );
+            }
+            
+            nodeTris->push_back( triInfo );
+        }
+        
+        // Keep traversing the rest of the scene graph.
+        traverse( node );
+    }
+    
+protected:
+    SGVec3d                         center;
+    std::vector<SGTriangleInfo>*    nodeTris;
+};
diff --git a/simgear/scene/tgdb/SGTileDetailsCallback.hxx b/simgear/scene/tgdb/SGTileDetailsCallback.hxx
new file mode 100644 (file)
index 0000000..d7ffb5b
--- /dev/null
@@ -0,0 +1,1140 @@
+// obj.cxx -- routines to handle loading scenery and building the plib
+//            scene graph.
+//
+// Written by Curtis Olson, started October 1997.
+//
+// Copyright (C) 1997  Curtis L. Olson  - http://www.flightgear.org/~curt
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//
+// $Id$
+
+
+#ifdef HAVE_CONFIG_H
+#  include <simgear_config.h>
+#endif
+
+#include <osg/LOD>
+#include <osgUtil/Simplifier>
+
+#include <boost/foreach.hpp>
+
+#include <simgear/scene/material/matmodel.hxx>
+#include <simgear/scene/model/SGOffsetTransform.hxx>
+#include <simgear/scene/util/QuadTreeBuilder.hxx>
+#include <simgear/scene/util/SGReaderWriterOptions.hxx>
+#include <simgear/scene/util/OptionsReadFileCallback.hxx>
+#include <simgear/scene/util/SGNodeMasks.hxx>
+
+#include "SGNodeTriangles.hxx"
+#include "GroundLightManager.hxx"
+#include "SGLightBin.hxx"
+#include "SGDirectionalLightBin.hxx"
+#include "SGModelBin.hxx"
+#include "SGBuildingBin.hxx"
+#include "TreeBin.hxx"
+
+#include "pt_lights.hxx"
+
+
+typedef std::list<SGLightBin> SGLightListBin;
+typedef std::list<SGDirectionalLightBin> SGDirectionalLightListBin;
+
+#define SG_SIMPLIFIER_RATIO         (0.001)
+#define SG_SIMPLIFIER_MAX_LENGTH    (1000.0)
+#define SG_SIMPLIFIER_MAX_ERROR     (2000.0)
+#define SG_OBJECT_RANGE             (9000.0)
+#define SG_TILE_RADIUS              (14000.0)
+#define SG_TILE_MIN_EXPIRY          (180.0)
+
+using namespace simgear;
+
+// QuadTreeBuilder is used by Random Objects Generator
+typedef std::pair<osg::Node*, int> ModelLOD;
+struct MakeQuadLeaf {
+    osg::LOD* operator() () const { return new osg::LOD; }
+};
+struct AddModelLOD {
+    void operator() (osg::LOD* leaf, ModelLOD& mlod) const
+    {
+        leaf->addChild(mlod.first, 0, mlod.second);
+    }
+};
+struct GetModelLODCoord {
+    GetModelLODCoord() {}
+    GetModelLODCoord(const GetModelLODCoord& rhs)
+    {}
+    osg::Vec3 operator() (const ModelLOD& mlod) const
+    {
+        return mlod.first->getBound().center();
+    }
+};
+typedef QuadTreeBuilder<osg::LOD*, ModelLOD, MakeQuadLeaf, AddModelLOD,
+                        GetModelLODCoord>  RandomObjectsQuadtree;
+
+
+// needs constructor
+static unsigned int num_tdcb = 0;
+class SGTileDetailsCallback : public OptionsReadFileCallback {
+public:
+    SGTileDetailsCallback() 
+    {
+        num_tdcb++;
+    }
+
+    virtual ~SGTileDetailsCallback() 
+    {
+        num_tdcb--;
+        SG_LOG( SG_GENERAL, SG_INFO, "SGTileDetailsCallback::~SGTileDetailsCallback() num cbs left " << num_tdcb  );
+    }
+    
+    virtual osgDB::ReaderWriter::ReadResult readNode(
+        const std::string&, const osgDB::Options*)
+    {
+        SGMaterialLibPtr matlib;
+        osg::ref_ptr<SGMaterialCache> matcache; 
+        
+        osg::ref_ptr<osg::Group> group = new osg::Group;
+        group->setDataVariance(osg::Object::STATIC);
+
+        // generate textured triangle list
+        std::vector<SGTriangleInfo> matTris;
+        GetNodeTriangles nodeTris(_gbs_center, &matTris);
+        _rootNode->accept( nodeTris );
+
+        // build matcache
+        matlib = _options->getMaterialLib();
+        if (matlib) {
+            SGGeod geodPos = SGGeod::fromCart(_gbs_center);            
+            matcache = matlib->generateMatCache(geodPos);
+        }
+        
+#if 0
+        // TEST : See if we can regenerate landclass shapes from node
+        for ( unsigned int i=0; i<matTris.size(); i++ ) {
+            matTris[i].dumpBorder(_gbs_center);
+        }
+#endif
+
+        osg::Node* node = loadTerrain();
+        if (node) {
+            group->addChild(node);
+        }
+
+        osg::LOD* lightLOD = generateLightingTileObjects(matTris, matcache);
+        if (lightLOD) {
+            group->addChild(lightLOD);
+        }
+
+        osg::LOD* objectLOD = generateRandomTileObjects(matTris, matcache);
+        if (objectLOD) {
+            group->addChild(objectLOD);
+        }
+        
+        return group.release();
+    }
+
+    static SGVec4f getMaterialLightColor(const SGMaterial* material)
+    {
+        if (!material) {
+            return SGVec4f(1, 1, 1, 0.8);
+        }
+        
+        return material->get_light_color();
+    }
+    
+    static void
+    addPointGeometry(SGLightBin& lights,
+                     const std::vector<SGVec3d>& vertices,
+                     const SGVec4f& color,
+                     const int_list& pts_v)
+    {
+        for (unsigned i = 0; i < pts_v.size(); ++i)
+            lights.insert(toVec3f(vertices[pts_v[i]]), color);
+    }
+    
+    static void
+    addPointGeometry(SGDirectionalLightBin& lights,
+                     const std::vector<SGVec3d>& vertices,
+                     const std::vector<SGVec3f>& normals,
+                     const SGVec4f& color,
+                     const int_list& pts_v,
+                     const int_list& pts_n)
+    {
+        // If the normal indices match the vertex indices, use seperate
+        // normal indices. Else reuse the vertex indices for the normals.
+        if (pts_v.size() == pts_n.size()) {
+            for (unsigned i = 0; i < pts_v.size(); ++i)
+                lights.insert(toVec3f(vertices[pts_v[i]]), normals[pts_n[i]], color);
+        } else {
+            for (unsigned i = 0; i < pts_v.size(); ++i)
+                lights.insert(toVec3f(vertices[pts_v[i]]), normals[pts_v[i]], color);
+        }
+    }
+    
+    bool insertPtGeometry(const SGBinObject& obj, SGMaterialCache* matcache)
+    {
+        if (obj.get_pts_v().size() != obj.get_pts_n().size()) {
+            SG_LOG(SG_TERRAIN, SG_ALERT,
+                   "Group list sizes for points do not match!");
+            return false;
+        }
+        
+        for (unsigned grp = 0; grp < obj.get_pts_v().size(); ++grp) {
+            std::string materialName = obj.get_pt_materials()[grp];
+            SGMaterial* material = matcache->find(materialName);
+            SGVec4f color = getMaterialLightColor(material);
+            
+            if (3 <= materialName.size() && materialName.substr(0, 3) != "RWY") {
+                // Just plain lights. Not something for the runway.
+                addPointGeometry(tileLights, obj.get_wgs84_nodes(), color,
+                                 obj.get_pts_v()[grp]);
+            } else if (materialName == "RWY_BLUE_TAXIWAY_LIGHTS"
+                || materialName == "RWY_GREEN_TAXIWAY_LIGHTS") {
+                addPointGeometry(taxiLights, obj.get_wgs84_nodes(), obj.get_normals(),
+                                 color, obj.get_pts_v()[grp], obj.get_pts_n()[grp]);
+                } else if (materialName == "RWY_VASI_LIGHTS") {
+                    vasiLights.push_back(SGDirectionalLightBin());
+                    addPointGeometry(vasiLights.back(), obj.get_wgs84_nodes(),
+                                     obj.get_normals(), color, obj.get_pts_v()[grp],
+                                     obj.get_pts_n()[grp]);
+                } else if (materialName == "RWY_SEQUENCED_LIGHTS") {
+                    rabitLights.push_back(SGDirectionalLightBin());
+                    addPointGeometry(rabitLights.back(), obj.get_wgs84_nodes(),
+                                     obj.get_normals(), color, obj.get_pts_v()[grp],
+                                     obj.get_pts_n()[grp]);
+                } else if (materialName == "RWY_ODALS_LIGHTS") {
+                    odalLights.push_back(SGLightBin());
+                    addPointGeometry(odalLights.back(), obj.get_wgs84_nodes(),
+                                     color, obj.get_pts_v()[grp]);
+                } else if (materialName == "RWY_YELLOW_PULSE_LIGHTS") {
+                    holdshortLights.push_back(SGDirectionalLightBin());
+                    addPointGeometry(holdshortLights.back(), obj.get_wgs84_nodes(),
+                                     obj.get_normals(), color, obj.get_pts_v()[grp],
+                                     obj.get_pts_n()[grp]);
+                } else if (materialName == "RWY_GUARD_LIGHTS") {
+                    guardLights.push_back(SGDirectionalLightBin());
+                    addPointGeometry(guardLights.back(), obj.get_wgs84_nodes(),
+                                     obj.get_normals(), color, obj.get_pts_v()[grp],
+                                     obj.get_pts_n()[grp]);
+                } else if (materialName == "RWY_REIL_LIGHTS") {
+                    reilLights.push_back(SGDirectionalLightBin());
+                    addPointGeometry(reilLights.back(), obj.get_wgs84_nodes(),
+                                     obj.get_normals(), color, obj.get_pts_v()[grp],
+                                     obj.get_pts_n()[grp]);
+                } else {
+                    // what is left must be runway lights
+                    addPointGeometry(runwayLights, obj.get_wgs84_nodes(),
+                                     obj.get_normals(), color, obj.get_pts_v()[grp],
+                                     obj.get_pts_n()[grp]);
+                }
+        }
+        
+        return true;
+    }
+    
+    
+    
+    // Load terrain if required
+    // todo - this is the same code as when we load a btg from the .STG - can we combine?
+    osg::Node* loadTerrain()
+    {
+      if (! _loadterrain)
+        return NULL;
+
+      SGBinObject tile;
+      if (!tile.read_bin(_path))
+        return NULL;
+
+      SGMaterialLibPtr matlib;
+      SGMaterialCache* matcache = 0;
+      bool useVBOs = false;
+      bool simplifyNear    = false;
+      double ratio       = SG_SIMPLIFIER_RATIO;
+      double maxLength   = SG_SIMPLIFIER_MAX_LENGTH;
+      double maxError    = SG_SIMPLIFIER_MAX_ERROR;
+
+      if (_options) {
+        matlib = _options->getMaterialLib();
+        useVBOs = (_options->getPluginStringData("SimGear::USE_VBOS") == "ON");
+        SGPropertyNode* propertyNode = _options->getPropertyNode().get();
+        simplifyNear = propertyNode->getBoolValue("/sim/rendering/terrain/simplifier/enabled-near", simplifyNear);
+        ratio = propertyNode->getDoubleValue("/sim/rendering/terrain/simplifier/ratio", ratio);
+        maxLength = propertyNode->getDoubleValue("/sim/rendering/terrain/simplifier/max-length", maxLength);
+        maxError = propertyNode->getDoubleValue("/sim/rendering/terrain/simplifier/max-error", maxError);
+      }
+
+      // PSADRO TODO : we can do this in terragear 
+      // - why not add a bitmask of flags to the btg so we can precompute this?
+      // and only do it if it hasn't been done already
+      SGVec3d center = tile.get_gbs_center();
+      SGGeod geodPos = SGGeod::fromCart(center);
+      SGQuatd hlOr = SGQuatd::fromLonLat(geodPos)*SGQuatd::fromEulerDeg(0, 0, 180);
+
+      // Generate a materials cache
+      if (matlib) {
+          matcache = matlib->generateMatCache(geodPos);
+      }
+      
+      // rotate the tiles so that the bounding boxes get nearly axis aligned.
+      // this will help the collision tree's bounding boxes a bit ...
+      std::vector<SGVec3d> nodes = tile.get_wgs84_nodes();
+      for (unsigned i = 0; i < nodes.size(); ++i) {
+        nodes[i] = hlOr.transform(nodes[i]);
+      }
+      tile.set_wgs84_nodes(nodes);
+
+      SGQuatf hlOrf(hlOr[0], hlOr[1], hlOr[2], hlOr[3]);
+      std::vector<SGVec3f> normals = tile.get_normals();
+      for (unsigned i = 0; i < normals.size(); ++i) {
+        normals[i] = hlOrf.transform(normals[i]);
+      }
+      tile.set_normals(normals);
+
+      osg::ref_ptr<SGTileGeometryBin> tileGeometryBin = new SGTileGeometryBin;
+
+      if (!tileGeometryBin->insertSurfaceGeometry(tile, matcache)) {
+        return NULL;
+      }
+      
+      osg::Node* node = tileGeometryBin->getSurfaceGeometry(matcache, useVBOs);
+      if (node && simplifyNear) {
+        osgUtil::Simplifier simplifier(ratio, maxError, maxLength);
+        node->accept(simplifier);
+      }
+
+      return node;
+    }
+
+    float min_dist_to_seg_squared( const SGVec3f p, const SGVec3d& a, const SGVec3d& b )
+    {
+        const float l2 = distSqr(a, b);
+        SGVec3d pd = toVec3d( p );
+        if (l2 == 0.0) {
+            return distSqr(pd, a); // if a == b, just return distance to A
+        }
+        
+        // Consider the line extending the segment, parameterized as a + t (b - a).
+        // We find projection of pt onto the line. 
+        // It falls where t = [(p-a) . (b-a)] / |b-a|^2
+        const float t = dot(pd-a, b-a) / l2;
+        
+        if (t < 0.0) {
+            return distSqr(pd, a);
+        } else if (t > 1.0) {
+            return distSqr(pd, b);
+        } else {
+            const SGVec3d proj = a + t * (b-a);
+            return distSqr(pd, proj);
+        }
+    }
+    
+    float min_dist_from_borders( SGVec3f p, const std::vector<SGBorderContour>& bsegs )
+    {
+        // calc min dist to each line 
+        // calc distance squared to keep this as fast as we can
+        // first, we must be able to project the point onto the segment
+        std::vector<float> distances;
+        for ( unsigned int b=0; b<bsegs.size(); b++ )
+        {
+            distances.push_back( min_dist_to_seg_squared( p, bsegs[b].start, bsegs[b].end ) );
+        }
+        
+        float min_dist_sq = *std::min_element( distances.begin(), distances.end() );        
+        return sqrt( min_dist_sq );
+    }
+    
+    // let's break random objects from randomBuildings
+    void computeRandomObjectsAndBuildings(
+        std::vector<SGTriangleInfo>& matTris, 
+        float building_density,
+        bool use_random_objects,
+        bool use_random_buildings,
+        bool useVBOs,
+        SGMatModelBin&     randomModels,
+        SGBuildingBinList& randomBuildings )
+    {
+        unsigned int m;
+        
+        // Only compute the random objects if we haven't already done so
+        if (_tileRandomObjectsComputed) {
+            return;
+        }
+        _tileRandomObjectsComputed = true;
+        
+        // generate a repeatable random seed
+        mt seed;
+        mt_init(&seed, unsigned(123));
+        
+        for ( m=0; m<matTris.size(); m++ ) {
+            SGMaterial *mat = matTris[m].getMaterial();
+            if (!mat)
+                continue;
+                        
+            osg::Texture2D* object_mask  = mat->get_one_object_mask(matTris[m].getTextureIndex());
+            
+            int   group_count            = mat->get_object_group_count();
+            float building_coverage      = mat->get_building_coverage();
+            float cos_zero_density_angle = mat->get_cos_object_zero_density_slope_angle();
+            float cos_max_density_angle  = mat->get_cos_object_max_density_slope_angle();
+            
+            if (building_coverage == 0)
+                continue;
+            
+            SGBuildingBin* bin = NULL;
+            
+            if (building_coverage > 0) {
+                bin = new SGBuildingBin(mat, useVBOs);                
+                randomBuildings.push_back(bin);
+            }
+            
+            unsigned num = matTris[m].getNumTriangles();
+            int random_dropped = 0;
+            int mask_dropped = 0;
+            int building_dropped = 0;
+            int triangle_dropped = 0;
+            
+            // get the polygon border segments
+//            std::vector<SGBorderContour> borderSegs;
+//            matTris[m].getBorderContours( borderSegs );
+            
+            for (unsigned i = 0; i < num; ++i) {
+                std::vector<SGVec3f> triVerts;
+                std::vector<SGVec2f> triTCs;
+                matTris[m].getTriangle(i, triVerts, triTCs);
+                
+                SGVec3f vorigin = triVerts[0];
+                SGVec3f v0 = triVerts[1] - vorigin;
+                SGVec3f v1 = triVerts[2] - vorigin;
+                SGVec2f torigin = triTCs[0];
+                SGVec2f t0 = triTCs[1] - torigin;
+                SGVec2f t1 = triTCs[2] - torigin;
+                SGVec3f normal = cross(v0, v1);
+                
+                // Ensure the slope isn't too steep by checking the
+                // cos of the angle between the slope normal and the
+                // vertical (conveniently the z-component of the normalized
+                // normal) and values passed in.
+                float cos = normalize(normal).z();
+                float slope_density = 1.0;
+                if (cos < cos_zero_density_angle) continue; // Too steep for any objects
+                if (cos < cos_max_density_angle) {
+                    slope_density =
+                    (cos - cos_zero_density_angle) /
+                    (cos_max_density_angle - cos_zero_density_angle);
+                }
+                
+                // Containers to hold the random buildings and objects generated
+                // for this triangle for collision detection purposes.
+                std::vector< std::pair< SGVec3f, float> > triangleObjectsList;
+                std::vector< std::pair< SGVec3f, float> > triangleBuildingList;
+                
+                // Compute the area : todo - we only want to stop if the area of the POLY
+                // is too small
+                // so we need to know area of each poly....
+                float area = 0.5f*length(normal);
+                if (area <= SGLimitsf::min())
+                    continue;
+                
+                // Generate any random objects
+                if (use_random_objects && (group_count > 0))
+                {
+                    for (int j = 0; j < group_count; j++)
+                    {
+                        SGMatModelGroup *object_group =  mat->get_object_group(j);
+                        int nObjects = object_group->get_object_count();
+                        
+                        if (nObjects == 0) continue;
+                        
+                        // For each of the random models in the group, determine an appropriate
+                        // number of random placements and insert them.
+                        for (int k = 0; k < nObjects; k++) {
+                            SGMatModel * object = object_group->get_object(k);
+                            
+                            // Determine the number of objecst to place, taking into account
+                            // the slope density factor.
+                            double n = slope_density * area / object->get_coverage_m2();
+                            
+                            // Use the zombie door method to determine fractional object placement.
+                            n = n + mt_rand(&seed);
+                            
+                            // place an object each unit of area
+                            while ( n > 1.0 ) {
+                                n -= 1.0;
+                                
+                                float a = mt_rand(&seed);
+                                float b = mt_rand(&seed);
+                                if ( a + b > 1 ) {
+                                    a = 1 - a;
+                                    b = 1 - b;
+                                }
+                                
+                                SGVec3f randomPoint = vorigin + a*v0 + b*v1;
+                                float rotation = static_cast<float>(mt_rand(&seed));
+                                
+                                // Check that the point is sufficiently far from
+                                // the edge of the triangle by measuring the distance
+                                // from the three lines that make up the triangle.
+                                float spacing = object->get_spacing_m();
+                                
+                                SGVec3f p = randomPoint - vorigin;
+#if 1
+                                float edges[] = { 
+                                    length(cross(p     , p - v0)) / length(v0),
+                                    length(cross(p - v0, p - v1)) / length(v1 - v0),
+                                    length(cross(p - v1, p     )) / length(v1)      };
+                                    float edge_dist = *std::min_element(edges, edges + 3);
+#else
+                                    float edge_dist = min_dist_from_borders( randomPoint, borderSegs );
+#endif
+                                    if (edge_dist < spacing) {
+                                        continue;
+                                    }
+                                    
+                                    if (object_mask != NULL) {
+                                        SGVec2f texCoord = torigin + a*t0 + b*t1;
+                                        
+                                        // Check this random point against the object mask
+                                        // blue (for buildings) channel.
+                                        osg::Image* img = object_mask->getImage();
+                                        unsigned int x = (int) (img->s() * texCoord.x()) % img->s();
+                                        unsigned int y = (int) (img->t() * texCoord.y()) % img->t();
+                                        
+                                        if (mt_rand(&seed) > img->getColor(x, y).b()) {
+                                            // Failed object mask check
+                                            continue;
+                                        }
+                                        
+                                        rotation = img->getColor(x,y).r();
+                                    }
+                                    
+                                    bool close = false;
+                                    
+                                    // Check it isn't too close to any other random objects in the triangle
+                                    std::vector<std::pair<SGVec3f, float> >::iterator l;
+                                    for (l = triangleObjectsList.begin(); l != triangleObjectsList.end(); ++l) {
+                                        float min_dist2 = (l->second + object->get_spacing_m()) *
+                                        (l->second + object->get_spacing_m());
+                                        
+                                        if (distSqr(l->first, randomPoint) < min_dist2) {
+                                            close = true;
+                                            continue;
+                                        }
+                                    }
+                                    
+                                    if (!close) {
+                                        triangleObjectsList.push_back(std::make_pair(randomPoint, object->get_spacing_m()));
+                                        randomModels.insert(randomPoint,
+                                                            object,
+                                                            (int)object->get_randomized_range_m(&seed),
+                                                            rotation);
+                                    }
+                            }
+                        }
+                    }
+                }
+                
+                // Random objects now generated.  Now generate the random buildings (if any);
+                if (use_random_buildings && (building_coverage > 0) && (building_density > 0)) {
+                    
+                    // Calculate the number of buildings, taking into account building density (which is linear)
+                    // and the slope density factor.
+                    double num = building_density * building_density * slope_density * area / building_coverage;
+                    
+                    // For partial units of area, use a zombie door method to
+                    // create the proper random chance of an object being created
+                    // for this triangle.
+                    num = num + mt_rand(&seed);
+                    
+                    if (num < 1.0f) {
+                        continue;
+                    }
+                    
+                    // Cosine of the angle between the two vectors.
+                    float cosine = (dot(v0, v1) / (length(v0) * length(v1)));
+                    
+                    // Determine a grid spacing in each vector such that the correct
+                    // coverage will result.
+                    float stepv0 = (sqrtf(building_coverage) / building_density) / length(v0) / sqrtf(1 - cosine * cosine);
+                    float stepv1 = (sqrtf(building_coverage) / building_density) / length(v1);
+                    
+                    stepv0 = std::min(stepv0, 1.0f);
+                    stepv1 = std::min(stepv1, 1.0f);
+                    
+                    // Start at a random point. a will be immediately incremented below.
+                    float a = -mt_rand(&seed) * stepv0;
+                    float b = mt_rand(&seed) * stepv1;
+                    
+                    // Place an object each unit of area
+                    while (num > 1.0) {
+                        num -= 1.0;
+                        
+                        // Set the next location to place a building
+                        a += stepv0;
+                        
+                        if ((a + b) > 1.0f) {
+                            // Reached the end of the scan-line on v0. Reset and increment
+                            // scan-line on v1
+                            a = mt_rand(&seed) * stepv0;
+                            b += stepv1;
+                        }
+                        
+                        if (b > 1.0f) {
+                            // In a degenerate case of a single point, we might be outside the
+                            // scanline.  Note that we need to still ensure that a+b < 1.
+                            b = mt_rand(&seed) * stepv1 * (1.0f - a);
+                        }
+                        
+                        if ((a + b) > 1.0f ) {
+                            // Truly degenerate case - simply choose a random point guaranteed
+                            // to fulfil the constraing of a+b < 1.
+                            a = mt_rand(&seed);
+                            b = mt_rand(&seed) * (1.0f - a);
+                        }
+                        
+                        SGVec3f randomPoint = vorigin + a*v0 + b*v1;
+                        float rotation = mt_rand(&seed);
+                        
+                        if (object_mask != NULL) {
+                            SGVec2f texCoord = torigin + a*t0 + b*t1;
+                            osg::Image* img = object_mask->getImage();
+                            int x = (int) (img->s() * texCoord.x()) % img->s();
+                            int y = (int) (img->t() * texCoord.y()) % img->t();
+                            
+                            // In some degenerate cases x or y can be < 1, in which case the mod operand fails
+                            while (x < 0) x += img->s();
+                            while (y < 0) y += img->t();
+                            
+                            if (mt_rand(&seed) < img->getColor(x, y).b()) {
+                                // Object passes mask. Rotation is taken from the red channel
+                                rotation = img->getColor(x,y).r();
+                            } else {
+                                // Fails mask test - try again.
+                                mask_dropped++;
+                                continue;
+                            }
+                        }
+                        
+                        // Check building isn't too close to the triangle edge.
+                        float type_roll = mt_rand(&seed);
+                        SGBuildingBin::BuildingType buildingtype = bin->getBuildingType(type_roll);
+                        float radius = bin->getBuildingMaxRadius(buildingtype);
+                        
+                        // Determine the actual center of the building, by shifting from the
+                        // center of the front face to the true center.
+                        osg::Matrix rotationMat = osg::Matrix::rotate(- rotation * M_PI * 2,
+                                                                      osg::Vec3f(0.0, 0.0, 1.0));
+                        SGVec3f buildingCenter = randomPoint + toSG(osg::Vec3f(-0.5 * bin->getBuildingMaxDepth(buildingtype), 0.0, 0.0) * rotationMat);
+                        
+                        SGVec3f p = buildingCenter - vorigin;
+#if 1
+                        float edges[] = { length(cross(p     , p - v0)) / length(v0),
+                            length(cross(p - v0, p - v1)) / length(v1 - v0),
+                            length(cross(p - v1, p     )) / length(v1)      };
+                            float edge_dist = *std::min_element(edges, edges + 3);
+#else
+                            float edge_dist = min_dist_from_borders(randomPoint, borderSegs);
+#endif
+                            if (edge_dist < radius) {
+                                triangle_dropped++;
+                                continue;
+                            }
+                            
+                            // Check building isn't too close to random objects and other buildings.
+                            bool close = false;
+                            std::vector<std::pair<SGVec3f, float> >::iterator iter;
+                            
+                            for (iter = triangleBuildingList.begin(); iter != triangleBuildingList.end(); ++iter) {
+                                float min_dist = iter->second + radius;
+                                if (distSqr(iter->first, buildingCenter) < min_dist * min_dist) {
+                                    close = true;
+                                    continue;
+                                }
+                            }
+                            
+                            if (close) {
+                                building_dropped++;
+                                continue;
+                            }
+                            
+                            for (iter = triangleObjectsList.begin(); iter != triangleObjectsList.end(); ++iter) {
+                                float min_dist = iter->second + radius;
+                                if (distSqr(iter->first, buildingCenter) < min_dist * min_dist) {
+                                    close = true;
+                                    continue;
+                                }
+                            }
+                            
+                            if (close) {
+                                random_dropped++;
+                                continue;
+                            }
+                            
+                            std::pair<SGVec3f, float> pt = std::make_pair(buildingCenter, radius);
+                            triangleBuildingList.push_back(pt);
+                            bin->insert(randomPoint, rotation, buildingtype);
+                    }
+                }
+                
+                triangleObjectsList.clear();
+                triangleBuildingList.clear();
+            }
+            
+            SG_LOG(SG_TERRAIN, SG_DEBUG, "Random Buildings: " << ((bin) ? bin->getNumBuildings() : 0));
+            SG_LOG(SG_TERRAIN, SG_DEBUG, "  Dropped due to mask: " << mask_dropped);
+            SG_LOG(SG_TERRAIN, SG_DEBUG, "  Dropped due to random object: " << random_dropped);
+            SG_LOG(SG_TERRAIN, SG_DEBUG, "  Dropped due to other buildings: " << building_dropped);
+        }
+    }
+    
+    void computeRandomForest(std::vector<SGTriangleInfo>& matTris, float vegetation_density, SGTreeBinList& randomForest)
+    {        
+        unsigned int i;
+        
+        // generate a repeatable random seed
+        mt seed;
+        mt_init(&seed, unsigned(586));
+        
+        for ( i=0; i<matTris.size(); i++ ) {
+            SGMaterial *mat = matTris[i].getMaterial();
+            if (!mat)
+                continue;
+            
+            float wood_coverage = mat->get_wood_coverage();
+            if ((wood_coverage <= 0) || (vegetation_density <= 0))
+                continue;
+            
+            // Attributes that don't vary by tree but do vary by material
+            bool found = false;
+            TreeBin* bin = NULL;
+            
+            BOOST_FOREACH(bin, randomForest)
+            {
+                if ((bin->texture           == mat->get_tree_texture()  ) &&
+                    (bin->texture_varieties == mat->get_tree_varieties()) &&
+                    (bin->range             == mat->get_tree_range()    ) &&
+                    (bin->width             == mat->get_tree_width()    ) &&
+                    (bin->height            == mat->get_tree_height()   )   ) {
+                    found = true;
+                break;
+                    }
+            }
+            
+            if (!found) {
+                bin = new TreeBin();
+                bin->texture = mat->get_tree_texture();
+                SG_LOG(SG_INPUT, SG_DEBUG, "Tree texture " << bin->texture);
+                bin->range   = mat->get_tree_range();
+                bin->width   = mat->get_tree_width();
+                bin->height  = mat->get_tree_height();
+                bin->texture_varieties = mat->get_tree_varieties();
+                randomForest.push_back(bin);
+            }
+            
+            std::vector<SGVec3f> randomPoints;
+            matTris[i].addRandomTreePoints(wood_coverage,
+                                           mat->get_one_object_mask(matTris[i].getTextureIndex()),
+                                           vegetation_density,
+                                           mat->get_cos_tree_max_density_slope_angle(),
+                                           mat->get_cos_tree_zero_density_slope_angle(),
+                                           randomPoints);
+            
+            std::vector<SGVec3f>::iterator k;
+            for (k = randomPoints.begin(); k != randomPoints.end(); ++k) {
+                bin->insert(*k);
+            }
+        }
+    }
+    
+    void computeRandomSurfaceLights(std::vector<SGTriangleInfo>& matTris, SGLightBin& randomTileLights )
+    {
+        unsigned int i;
+        
+        // Only compute the lights if we haven't already done so.
+        // For example, the light data will still exist if the
+        // PagedLOD expires.
+        if ( _randomSurfaceLightsComputed ) 
+        {
+            return;
+        } 
+        _randomSurfaceLightsComputed = true;
+        
+        // generate a repeatable random seed
+        mt seed;
+        mt_init(&seed, unsigned(123));
+
+        for ( i=0; i<matTris.size(); i++ ) {
+            SGMaterial *mat = matTris[i].getMaterial();
+            if (!mat)
+                continue;
+            
+            float coverage = mat->get_light_coverage();
+            if (coverage <= 0)
+                continue;
+            if (coverage < 10000.0) {
+                SG_LOG(SG_INPUT, SG_ALERT, "Light coverage is "
+                << coverage << ", pushing up to 10000");
+                coverage = 10000;
+            }
+                        
+            int texIndex = matTris[i].getTextureIndex();
+            
+            std::vector<SGVec3f> randomPoints;
+            matTris[i].addRandomSurfacePoints(coverage, 3, mat->get_one_object_mask(texIndex), randomPoints);
+            std::vector<SGVec3f>::iterator j;
+            for (j = randomPoints.begin(); j != randomPoints.end(); ++j) {
+                float zombie = mt_rand(&seed);
+                // factor = sg_random() ^ 2, range = 0 .. 1 concentrated towards 0
+                float factor = mt_rand(&seed);
+                factor *= factor;
+                
+                float bright = 1;
+                SGVec4f color;
+                if ( zombie > 0.5 ) {
+                    // 50% chance of yellowish
+                    color = SGVec4f(0.9f, 0.9f, 0.3f, bright - factor * 0.2f);
+                } else if (zombie > 0.15f) {
+                    // 35% chance of whitish
+                    color = SGVec4f(0.9, 0.9f, 0.8f, bright - factor * 0.2f);
+                } else if (zombie > 0.05f) {
+                    // 10% chance of orangish
+                    color = SGVec4f(0.9f, 0.6f, 0.2f, bright - factor * 0.2f);
+                } else {
+                    // 5% chance of redish
+                    color = SGVec4f(0.9f, 0.2f, 0.2f, bright - factor * 0.2f);
+                }
+                randomTileLights.insert(*j, color);
+            }
+        }
+    }
+    
+    // Generate all the lighting objects for the tile.
+    osg::LOD* generateLightingTileObjects(std::vector<SGTriangleInfo>& matTris, const SGMaterialCache* matcache)
+    {
+      SGLightBin randomTileLights;
+      computeRandomSurfaceLights(matTris, randomTileLights);
+      
+      GroundLightManager* lightManager = GroundLightManager::instance();
+      osg::ref_ptr<osg::Group> lightGroup = new SGOffsetTransform(0.94);
+      SGVec3f up(0, 0, 1);
+
+      if (tileLights.getNumLights() > 0 || randomTileLights.getNumLights() > 0) {
+        osg::ref_ptr<osg::Group> groundLights0 = new osg::Group;
+
+        groundLights0->setStateSet(lightManager->getGroundLightStateSet());
+        groundLights0->setNodeMask(GROUNDLIGHTS0_BIT);
+
+        osg::ref_ptr<EffectGeode> geode = new EffectGeode;        
+        osg::ref_ptr<Effect> lightEffect = getLightEffect(24, osg::Vec3(1, 0.001, 0.00001), 1, 8, false, _options);
+        
+        geode->setEffect(lightEffect);                
+        geode->addDrawable(SGLightFactory::getLights(tileLights));
+        geode->addDrawable(SGLightFactory::getLights(randomTileLights, 4, -0.3f));
+        groundLights0->addChild(geode);
+        lightGroup->addChild(groundLights0);
+      }
+
+      if (randomTileLights.getNumLights() > 0) {
+        osg::ref_ptr<osg::Group> groundLights1 = new osg::Group;
+        groundLights1->setStateSet(lightManager->getGroundLightStateSet());
+        groundLights1->setNodeMask(GROUNDLIGHTS1_BIT);
+        
+        osg::ref_ptr<osg::Group> groundLights2 = new osg::Group;
+        groundLights2->setStateSet(lightManager->getGroundLightStateSet());
+        groundLights2->setNodeMask(GROUNDLIGHTS2_BIT);
+
+        osg::ref_ptr<EffectGeode> geode1 = new EffectGeode;
+        
+        osg::ref_ptr<Effect> lightEffect = getLightEffect(24, osg::Vec3(1, 0.001, 0.00001), 1, 8, false, _options);        
+        geode1->setEffect(lightEffect);        
+        geode1->addDrawable(SGLightFactory::getLights(randomTileLights, 2, -0.15f));
+        groundLights1->addChild(geode1);
+        lightGroup->addChild(groundLights1);
+        
+        osg::ref_ptr<EffectGeode> geode2 = new EffectGeode;
+        
+        geode2->setEffect(lightEffect);
+        geode2->addDrawable(SGLightFactory::getLights(randomTileLights));
+        groundLights2->addChild(geode2);
+        lightGroup->addChild(groundLights2);
+      }
+
+      if (vasiLights.empty()) {
+        EffectGeode* vasiGeode = new EffectGeode;        
+        Effect* vasiEffect = getLightEffect(24, osg::Vec3(1, 0.0001, 0.000001), 1, 24, true, _options);
+        vasiGeode->setEffect(vasiEffect);
+        SGVec4f red(1, 0, 0, 1);
+        SGMaterial* mat = 0;
+        if (matcache)
+          mat = matcache->find("RWY_RED_LIGHTS");
+        if (mat) {
+          red = mat->get_light_color();
+        }
+        
+        SGVec4f white(1, 1, 1, 1);
+        mat = 0;
+        if (matcache)
+          mat = matcache->find("RWY_WHITE_LIGHTS");
+        if (mat) {
+          white = mat->get_light_color();
+        }
+        SGDirectionalLightListBin::const_iterator i;
+        for (i = vasiLights.begin();
+             i != vasiLights.end(); ++i) {
+            osg::Drawable* vasiDraw = SGLightFactory::getVasi(up, *i, red, white);
+            vasiGeode->addDrawable( vasiDraw );
+        }
+        osg::StateSet* ss = lightManager->getRunwayLightStateSet();
+        vasiGeode->setStateSet( ss );
+        lightGroup->addChild(vasiGeode);
+      }
+
+      Effect* runwayEffect = 0;
+      if (runwayLights.getNumLights() > 0
+          || !rabitLights.empty()
+          || !reilLights.empty()
+          || !odalLights.empty()
+          || taxiLights.getNumLights() > 0) {
+          
+          runwayEffect = getLightEffect(16, osg::Vec3(1, 0.001, 0.0002), 1, 16, true, _options);
+      }
+      
+      if (runwayLights.getNumLights() > 0
+          || !rabitLights.empty()
+          || !reilLights.empty()
+          || !odalLights.empty()
+          || !holdshortLights.empty()
+          || !guardLights.empty()) {
+        osg::Group* rwyLights = new osg::Group;
+
+        osg::StateSet* ss = lightManager->getRunwayLightStateSet();      
+        rwyLights->setStateSet(ss);      
+        rwyLights->setNodeMask(RUNWAYLIGHTS_BIT);
+        
+        if (runwayLights.getNumLights() != 0) {
+          EffectGeode* geode = new EffectGeode;
+          geode->setEffect(runwayEffect);
+          
+          osg::Drawable* rldraw = SGLightFactory::getLights(runwayLights);
+          geode->addDrawable( rldraw );
+          
+          rwyLights->addChild(geode);
+        }
+        SGDirectionalLightListBin::const_iterator i;
+        for (i = rabitLights.begin();
+             i != rabitLights.end(); ++i) {
+            osg::Node* seqNode = SGLightFactory::getSequenced(*i, _options);
+            rwyLights->addChild( seqNode );
+        }
+        for (i = reilLights.begin();
+             i != reilLights.end(); ++i) {
+            osg::Node* seqNode = SGLightFactory::getSequenced(*i, _options);
+            rwyLights->addChild(seqNode);
+        }
+        for (i = holdshortLights.begin();
+             i != holdshortLights.end(); ++i) {
+            osg::Node* seqNode = SGLightFactory::getHoldShort(*i, _options);
+            rwyLights->addChild(seqNode);
+        }
+        for (i = guardLights.begin();
+             i != guardLights.end(); ++i) {
+            osg::Node* seqNode = SGLightFactory::getGuard(*i, _options);
+            rwyLights->addChild(seqNode);
+        }
+        SGLightListBin::const_iterator j;
+        for (j = odalLights.begin();
+             j != odalLights.end(); ++j) {
+            osg::Node* seqNode = SGLightFactory::getOdal(*j, _options);
+            rwyLights->addChild(seqNode);
+        }
+        lightGroup->addChild(rwyLights);
+      }
+
+      if (taxiLights.getNumLights() > 0) {
+        osg::Group* taxiLightsGroup = new osg::Group;
+        taxiLightsGroup->setStateSet(lightManager->getTaxiLightStateSet());
+        taxiLightsGroup->setNodeMask(RUNWAYLIGHTS_BIT);
+        EffectGeode* geode = new EffectGeode;
+        geode->setEffect(runwayEffect);
+        geode->addDrawable(SGLightFactory::getLights(taxiLights));
+        taxiLightsGroup->addChild(geode);
+        lightGroup->addChild(taxiLightsGroup);
+      }
+
+      osg::LOD* lightLOD = NULL;
+
+      if (lightGroup->getNumChildren() > 0) {
+        lightLOD = new osg::LOD;
+        lightLOD->addChild(lightGroup.get(), 0, 60000);
+        // VASI is always on, so doesn't use light bits.
+        lightLOD->setNodeMask(LIGHTS_BITS | MODEL_BIT | PERMANENTLIGHT_BIT);
+      }
+
+      return lightLOD;
+    }
+
+    // Generate all the random forest, objects and buildings for the tile
+    osg::LOD* generateRandomTileObjects(std::vector<SGTriangleInfo>& matTris, const SGMaterialCache* matcache)
+    {
+      SGMaterialLibPtr matlib;
+      bool use_random_objects = false;
+      bool use_random_vegetation = false;
+      bool use_random_buildings = false;
+      float vegetation_density = 1.0f;
+      float building_density = 1.0f;
+      bool useVBOs = false;
+      
+      osg::ref_ptr<osg::Group> randomObjects;
+      osg::ref_ptr<osg::Group> forestNode;
+      osg::ref_ptr<osg::Group> buildingNode;
+
+      if (_options) {
+        matlib = _options->getMaterialLib();
+        SGPropertyNode* propertyNode = _options->getPropertyNode().get();
+        if (propertyNode) {
+            use_random_objects
+                = propertyNode->getBoolValue("/sim/rendering/random-objects",
+                                             use_random_objects);
+            use_random_vegetation
+                = propertyNode->getBoolValue("/sim/rendering/random-vegetation",
+                                             use_random_vegetation);
+            vegetation_density
+                = propertyNode->getFloatValue("/sim/rendering/vegetation-density",
+                                              vegetation_density);
+            use_random_buildings
+                = propertyNode->getBoolValue("/sim/rendering/random-buildings",
+                                             use_random_buildings);
+            building_density
+                = propertyNode->getFloatValue("/sim/rendering/building-density",
+                                              building_density);
+        }
+        
+        useVBOs = (_options->getPluginStringData("SimGear::USE_VBOS") == "ON");
+      }
+
+      SGMatModelBin     randomModels;
+      
+      SGBuildingBinList randomBuildings;
+      
+      if (matlib && (use_random_objects || use_random_buildings)) {
+          computeRandomObjectsAndBuildings( matTris, 
+                                            building_density,
+                                            use_random_objects,
+                                            use_random_buildings,
+                                            useVBOs,
+                                            randomModels,
+                                            randomBuildings
+                                          );
+      }
+
+      if (randomModels.getNumModels() > 0) {
+        // Generate a repeatable random seed
+        mt seed;
+        mt_init(&seed, unsigned(123));
+
+        std::vector<ModelLOD> models;
+        for (unsigned int i = 0; i < randomModels.getNumModels(); i++) {
+          SGMatModelBin::MatModel obj = randomModels.getMatModel(i);
+
+          SGPropertyNode* root = _options->getPropertyNode()->getRootNode();
+          osg::Node* node = obj.model->get_random_model(root, &seed);
+
+          // Create a matrix to place the object in the correct
+          // location, and then apply the rotation matrix created
+          // above, with an additional random (or taken from
+          // the object mask) heading rotation if appropriate.
+          osg::Matrix transformMat;
+          transformMat = osg::Matrix::translate(toOsg(obj.position));
+          if (obj.model->get_heading_type() == SGMatModel::HEADING_RANDOM) {
+            // Rotate the object around the z axis.
+            double hdg = mt_rand(&seed) * M_PI * 2;
+            transformMat.preMult(osg::Matrix::rotate(hdg,
+                                                     osg::Vec3d(0.0, 0.0, 1.0)));
+          }
+
+          if (obj.model->get_heading_type() == SGMatModel::HEADING_MASK) {
+            // Rotate the object around the z axis.
+            double hdg =  - obj.rotation * M_PI * 2;
+            transformMat.preMult(osg::Matrix::rotate(hdg,
+                                                     osg::Vec3d(0.0, 0.0, 1.0)));
+          }
+
+          osg::MatrixTransform* position =
+            new osg::MatrixTransform(transformMat);
+          position->setName("positionRandomModel");
+          position->addChild(node);
+          models.push_back(ModelLOD(position, obj.lod));
+        }
+        RandomObjectsQuadtree quadtree((GetModelLODCoord()), (AddModelLOD()));
+        quadtree.buildQuadTree(models.begin(), models.end());
+        randomObjects = quadtree.getRoot();
+        randomObjects->setName("Random objects");
+      }
+
+      if (!randomBuildings.empty()) {
+        buildingNode = createRandomBuildings(randomBuildings, osg::Matrix::identity(), _options);
+        buildingNode->setName("Random buildings");
+        randomBuildings.clear();
+      }
+
+      if (use_random_vegetation && matlib) {
+        // Now add some random forest.
+        SGTreeBinList randomForest;
+        computeRandomForest(matTris, vegetation_density, randomForest);
+
+        if (!randomForest.empty()) {
+          forestNode = createForest(randomForest, osg::Matrix::identity(),_options);
+          forestNode->setName("Random trees");
+        }
+      }
+
+      osg::LOD* objectLOD = NULL;
+
+      if (randomObjects.valid() ||  forestNode.valid() || buildingNode.valid()) {
+        objectLOD = new osg::LOD;
+
+        if (randomObjects.valid()) objectLOD->addChild(randomObjects.get(), 0, 20000);
+        if (forestNode.valid())  objectLOD->addChild(forestNode.get(), 0, 20000);
+        if (buildingNode.valid()) objectLOD->addChild(buildingNode.get(), 0, 20000);
+
+        unsigned nodeMask = SG_NODEMASK_CASTSHADOW_BIT | SG_NODEMASK_RECEIVESHADOW_BIT | SG_NODEMASK_TERRAIN_BIT;
+        objectLOD->setNodeMask(nodeMask);
+      }
+
+      return objectLOD;
+    }
+
+    /// The original options to use for this bunch of models
+    osg::ref_ptr<SGReaderWriterOptions>     _options;
+    string                                  _path;
+    bool                                    _loadterrain;
+    osg::ref_ptr<osg::Node>                 _rootNode;
+    SGVec3d                                 _gbs_center;
+    bool                                    _randomSurfaceLightsComputed;
+    bool                                    _tileRandomObjectsComputed;
+    
+    // most of these are just point and color arrays - extracted from the 
+    // .BTG PointGeometry at tile load time.
+    // It shouldn't be too much to keep this in memory even if we don't use it.
+    SGLightBin                              tileLights;
+    SGDirectionalLightBin                   runwayLights;
+    SGDirectionalLightBin                   taxiLights;
+    SGDirectionalLightListBin               vasiLights;
+    SGDirectionalLightListBin               rabitLights;
+    SGLightListBin                          odalLights;
+    SGDirectionalLightListBin               holdshortLights;
+    SGDirectionalLightListBin               guardLights;
+    SGDirectionalLightListBin               reilLights;    
+};
\ No newline at end of file
diff --git a/simgear/scene/tgdb/SGTileGeometryBin.hxx b/simgear/scene/tgdb/SGTileGeometryBin.hxx
new file mode 100644 (file)
index 0000000..409ded6
--- /dev/null
@@ -0,0 +1,304 @@
+
+#ifdef HAVE_CONFIG_H
+#  include <simgear_config.h>
+#endif
+
+#include "obj.hxx"
+
+#include <simgear/scene/material/EffectGeode.hxx>
+#include <simgear/scene/material/matlib.hxx>
+#include <simgear/scene/material/mat.hxx>
+
+#include "SGTexturedTriangleBin.hxx"
+
+using namespace simgear;
+
+typedef std::map<std::string,SGTexturedTriangleBin> SGMaterialTriangleMap;
+
+// Class handling the initial BTG loading : should probably be in its own file
+// it is very closely coupled with SGTexturedTriangleBin.hxx
+// it was used to load fans, strips, and triangles.
+// WS2.0 no longer uses fans or strips, but people still use ws1.0, so we need 
+// to keep this functionality.
+class SGTileGeometryBin : public osg::Referenced {
+public:
+  SGMaterialTriangleMap materialTriangleMap;
+
+  SGTileGeometryBin() {}
+
+  static SGVec2f
+  getTexCoord(const std::vector<SGVec2f>& texCoords, const int_list& tc,
+              const SGVec2f& tcScale, unsigned i)
+  {
+    if (tc.empty())
+      return tcScale;
+    else if (tc.size() == 1)
+      return mult(texCoords[tc[0]], tcScale);
+    else
+      return mult(texCoords[tc[i]], tcScale);
+  }
+
+  SGVec2f getTexCoordScale(const std::string& name, SGMaterialCache* matcache)
+  {
+    if (!matcache)
+      return SGVec2f(1, 1);
+    SGMaterial* material = matcache->find(name);
+    if (!material)
+      return SGVec2f(1, 1);
+
+    return material->get_tex_coord_scale();
+  }
+  
+  static void
+  addTriangleGeometry(SGTexturedTriangleBin& triangles,
+                      const SGBinObject& obj, unsigned grp,
+                      const SGVec2f& tc0Scale, 
+                      const SGVec2f& tc1Scale)
+  {
+    const std::vector<SGVec3d>& vertices(obj.get_wgs84_nodes());
+    const std::vector<SGVec3f>& normals(obj.get_normals());
+    const std::vector<SGVec2f>& texCoords(obj.get_texcoords());
+    const int_list& tris_v(obj.get_tris_v()[grp]);
+    const int_list& tris_n(obj.get_tris_n()[grp]);
+    const tci_list& tris_tc(obj.get_tris_tcs()[grp]);
+    bool  num_norms_is_num_verts = true;  
+    
+    if (tris_v.size() != tris_n.size()) {
+        // If the normal indices do not match, they should be inmplicitly
+        // the same than the vertex indices. 
+        num_norms_is_num_verts = false;
+    }
+
+    if ( !tris_tc[1].empty() ) {
+        triangles.hasSecondaryTexCoord(true);
+    }
+    
+    for (unsigned i = 2; i < tris_v.size(); i += 3) {
+        SGVertNormTex v0;
+        v0.SetVertex( toVec3f(vertices[tris_v[i-2]]) );
+        v0.SetNormal( num_norms_is_num_verts ? normals[tris_n[i-2]] : 
+                                               normals[tris_v[i-2]] );
+        v0.SetTexCoord( 0, getTexCoord(texCoords, tris_tc[0], tc0Scale, i-2) );
+        if (!tris_tc[1].empty()) {
+            v0.SetTexCoord( 1, getTexCoord(texCoords, tris_tc[1], tc1Scale, i-2) );
+        }
+        SGVertNormTex v1;
+        v1.SetVertex( toVec3f(vertices[tris_v[i-1]]) );
+        v1.SetNormal( num_norms_is_num_verts ? normals[tris_n[i-1]] : 
+                                               normals[tris_v[i-1]] );
+        v1.SetTexCoord( 0, getTexCoord(texCoords, tris_tc[0], tc0Scale, i-1) );
+        if (!tris_tc[1].empty()) {
+            v1.SetTexCoord( 1, getTexCoord(texCoords, tris_tc[1], tc1Scale, i-1) );
+        }
+        SGVertNormTex v2;
+        v2.SetVertex( toVec3f(vertices[tris_v[i]]) );
+        v2.SetNormal( num_norms_is_num_verts ? normals[tris_n[i]] : 
+                                               normals[tris_v[i]] );
+        v2.SetTexCoord( 0, getTexCoord(texCoords, tris_tc[0], tc0Scale, i) );
+        if (!tris_tc[1].empty()) {
+            v2.SetTexCoord( 1, getTexCoord(texCoords, tris_tc[1], tc1Scale, i) );
+        }
+        
+        triangles.insert(v0, v1, v2);
+    }
+  }
+
+  static void
+  addStripGeometry(SGTexturedTriangleBin& triangles,
+                   const SGBinObject& obj, unsigned grp,
+                   const SGVec2f& tc0Scale, 
+                   const SGVec2f& tc1Scale)
+  {
+      const std::vector<SGVec3d>& vertices(obj.get_wgs84_nodes());
+      const std::vector<SGVec3f>& normals(obj.get_normals());
+      const std::vector<SGVec2f>& texCoords(obj.get_texcoords());
+      const int_list& strips_v(obj.get_strips_v()[grp]);
+      const int_list& strips_n(obj.get_strips_n()[grp]);
+      const tci_list& strips_tc(obj.get_strips_tcs()[grp]);
+      bool  num_norms_is_num_verts = true;  
+      
+      if (strips_v.size() != strips_n.size()) {
+          // If the normal indices do not match, they should be inmplicitly
+          // the same than the vertex indices. 
+          num_norms_is_num_verts = false;
+      }
+      
+      if ( !strips_tc[1].empty() ) {
+          triangles.hasSecondaryTexCoord(true);
+      }
+      
+    for (unsigned i = 2; i < strips_v.size(); ++i) {
+      SGVertNormTex v0;
+      v0.SetVertex( toVec3f(vertices[strips_v[i-2]]) );
+      v0.SetNormal( num_norms_is_num_verts ? normals[strips_n[i-2]] : 
+                                             normals[strips_v[i-2]] );
+      v0.SetTexCoord( 0, getTexCoord(texCoords, strips_tc[0], tc0Scale, i-2) );
+      if (!strips_tc[1].empty()) {
+          v0.SetTexCoord( 1, getTexCoord(texCoords, strips_tc[1], tc1Scale, i-2) );
+      }
+      SGVertNormTex v1;
+      v1.SetVertex( toVec3f(vertices[strips_v[i-1]]) );
+      v1.SetNormal( num_norms_is_num_verts ? normals[strips_n[i-1]] : 
+                                             normals[strips_v[i-1]] );
+      v1.SetTexCoord( 0, getTexCoord(texCoords, strips_tc[1], tc0Scale, i-1) );
+      if (!strips_tc[1].empty()) {
+          v1.SetTexCoord( 1, getTexCoord(texCoords, strips_tc[1], tc1Scale, i-1) );
+      }
+      SGVertNormTex v2;
+      v2.SetVertex( toVec3f(vertices[strips_v[i]]) );
+      v2.SetNormal( num_norms_is_num_verts ? normals[strips_n[i]] : 
+                                             normals[strips_v[i]] );
+      v2.SetTexCoord( 0, getTexCoord(texCoords, strips_tc[0], tc0Scale, i) );
+      if (!strips_tc[1].empty()) {
+          v2.SetTexCoord( 1, getTexCoord(texCoords, strips_tc[1], tc1Scale, i) );
+      }
+      if (i%2)
+        triangles.insert(v1, v0, v2);
+      else
+        triangles.insert(v0, v1, v2);
+    }
+  }
+
+  static void
+  addFanGeometry(SGTexturedTriangleBin& triangles,
+                 const SGBinObject& obj, unsigned grp,
+                 const SGVec2f& tc0Scale, 
+                 const SGVec2f& tc1Scale)
+  {
+      const std::vector<SGVec3d>& vertices(obj.get_wgs84_nodes());
+      const std::vector<SGVec3f>& normals(obj.get_normals());
+      const std::vector<SGVec2f>& texCoords(obj.get_texcoords());
+      const int_list& fans_v(obj.get_fans_v()[grp]);
+      const int_list& fans_n(obj.get_fans_n()[grp]);
+      const tci_list& fans_tc(obj.get_fans_tcs()[grp]);
+      bool  num_norms_is_num_verts = true;  
+      
+      if (fans_v.size() != fans_n.size()) {
+          // If the normal indices do not match, they should be inmplicitly
+          // the same than the vertex indices. 
+          num_norms_is_num_verts = false;
+      }
+      
+      if ( !fans_tc[1].empty() ) {
+          triangles.hasSecondaryTexCoord(true);
+      }
+      
+    SGVertNormTex v0;
+    v0.SetVertex( toVec3f(vertices[fans_v[0]]) );
+    v0.SetNormal( num_norms_is_num_verts ? normals[fans_n[0]] : 
+                                           normals[fans_v[0]] );
+    v0.SetTexCoord( 0, getTexCoord(texCoords, fans_tc[0], tc0Scale, 0) );
+    if (!fans_tc[1].empty()) {
+        v0.SetTexCoord( 1, getTexCoord(texCoords, fans_tc[1], tc1Scale, 0) );
+    }
+    SGVertNormTex v1;
+    v1.SetVertex( toVec3f(vertices[fans_v[1]]) );
+    v1.SetNormal( num_norms_is_num_verts ? normals[fans_n[1]] : 
+                                           normals[fans_v[1]] );
+    v1.SetTexCoord( 0, getTexCoord(texCoords, fans_tc[0], tc0Scale, 1) );
+    if (!fans_tc[1].empty()) {
+        v1.SetTexCoord( 1, getTexCoord(texCoords, fans_tc[1], tc1Scale, 1) );
+    }
+    for (unsigned i = 2; i < fans_v.size(); ++i) {
+      SGVertNormTex v2;
+      v2.SetVertex( toVec3f(vertices[fans_v[i]]) );
+      v2.SetNormal( num_norms_is_num_verts ? normals[fans_n[i]] : 
+                                             normals[fans_v[i]] );
+      v2.SetTexCoord( 0, getTexCoord(texCoords, fans_tc[0], tc0Scale, i) );
+      if (!fans_tc[1].empty()) {
+          v2.SetTexCoord( 1, getTexCoord(texCoords, fans_tc[1], tc1Scale, i) );
+      }
+      triangles.insert(v0, v1, v2);
+      v1 = v2;
+    }
+  }
+
+  bool
+  insertSurfaceGeometry(const SGBinObject& obj, SGMaterialCache* matcache)
+  {
+    if (obj.get_tris_n().size() < obj.get_tris_v().size() ||
+        obj.get_tris_tcs().size() < obj.get_tris_v().size()) {
+      SG_LOG(SG_TERRAIN, SG_ALERT,
+             "Group list sizes for triangles do not match!");
+      return false;
+    }
+
+    for (unsigned grp = 0; grp < obj.get_tris_v().size(); ++grp) {
+      std::string materialName = obj.get_tri_materials()[grp];
+      SGVec2f tc0Scale = getTexCoordScale(materialName, matcache);
+      SGVec2f tc1Scale(1.0, 1.0);
+      addTriangleGeometry(materialTriangleMap[materialName],
+                          obj, grp, tc0Scale, tc1Scale );
+    }
+
+    if (obj.get_strips_n().size() < obj.get_strips_v().size() ||
+        obj.get_strips_tcs().size() < obj.get_strips_v().size()) {
+      SG_LOG(SG_TERRAIN, SG_ALERT,
+             "Group list sizes for strips do not match!");
+      return false;
+    }
+    for (unsigned grp = 0; grp < obj.get_strips_v().size(); ++grp) {
+      std::string materialName = obj.get_strip_materials()[grp];
+      SGVec2f tc0Scale = getTexCoordScale(materialName, matcache);
+      SGVec2f tc1Scale(1.0, 1.0);
+      addStripGeometry(materialTriangleMap[materialName],
+                          obj, grp, tc0Scale, tc1Scale);
+    }
+
+    if (obj.get_fans_n().size() < obj.get_fans_v().size() ||
+        obj.get_fans_tcs().size() < obj.get_fans_v().size()) {
+      SG_LOG(SG_TERRAIN, SG_ALERT,
+             "Group list sizes for fans do not match!");
+      return false;
+    }
+    for (unsigned grp = 0; grp < obj.get_fans_v().size(); ++grp) {
+      std::string materialName = obj.get_fan_materials()[grp];
+      SGVec2f tc0Scale = getTexCoordScale(materialName, matcache);
+      SGVec2f tc1Scale(1.0, 1.0);
+      addFanGeometry(materialTriangleMap[materialName],
+                       obj, grp, tc0Scale, tc1Scale );
+    }
+    return true;
+  }
+
+  osg::Node* getSurfaceGeometry(SGMaterialCache* matcache, bool useVBOs) const
+  {
+    if (materialTriangleMap.empty())
+      return 0;
+
+    EffectGeode* eg = NULL;
+    osg::Group* group = (materialTriangleMap.size() > 1 ? new osg::Group : NULL);
+    if (group) {
+        group->setName("surfaceGeometryGroup");
+    }
+    
+    //osg::Geode* geode = new osg::Geode;
+    SGMaterialTriangleMap::const_iterator i;
+    for (i = materialTriangleMap.begin(); i != materialTriangleMap.end(); ++i) {
+      osg::Geometry* geometry = i->second.buildGeometry(useVBOs);
+      SGMaterial *mat = NULL;
+      if (matcache) {
+        mat = matcache->find(i->first);
+      }
+      eg = new EffectGeode;
+      eg->setName("EffectGeode");
+      if (mat) {
+        eg->setMaterial(mat);
+        eg->setEffect(mat->get_one_effect(i->second.getTextureIndex()));
+      } else {
+        eg->setMaterial(NULL);
+      }
+      eg->addDrawable(geometry);
+      eg->runGenerators(geometry);  // Generate extra data needed by effect
+      if (group) {
+        group->addChild(eg);
+      }
+    }
+    
+    if (group) {
+        return group;
+    } else {
+        return eg;
+    }
+  }
+};
index 1fd3a2f3a0e0818e392751a45632d1313de6f4cb..3ff0bed4a5c6c43462f8a4ca2df0e04fdae8d3b2 100644 (file)
 
 #include "obj.hxx"
 
-#include <simgear/compiler.h>
-
-#include <osg/Fog>
-#include <osg/Geode>
-#include <osg/Geometry>
-#include <osg/Group>
-#include <osg/LOD>
-#include <osg/MatrixTransform>
-#include <osg/Point>
-#include <osg/Referenced>
-#include <osg/StateSet>
-#include <osg/Switch>
-#include <osgDB/ReaderWriter>
-
-#include <osgUtil/Simplifier>
-
-#include <boost/foreach.hpp>
-
-#include <algorithm>
-
 #include <simgear/debug/logstream.hxx>
 #include <simgear/io/sg_binobj.hxx>
-#include <simgear/math/sg_geodesy.hxx>
-#include <simgear/math/sg_random.h>
-#include <simgear/math/SGMisc.hxx>
-#include <simgear/scene/material/Effect.hxx>
-#include <simgear/scene/material/EffectGeode.hxx>
-#include <simgear/scene/material/mat.hxx>
-#include <simgear/scene/material/matmodel.hxx>
-#include <simgear/scene/material/matlib.hxx>
-#include <simgear/scene/model/SGOffsetTransform.hxx>
-#include <simgear/scene/util/SGUpdateVisitor.hxx>
-#include <simgear/scene/util/SGNodeMasks.hxx>
-#include <simgear/scene/util/QuadTreeBuilder.hxx>
-#include <simgear/scene/util/SGReaderWriterOptions.hxx>
-#include <simgear/scene/util/OptionsReadFileCallback.hxx>
-
-#include "SGTexturedTriangleBin.hxx"
-#include "SGLightBin.hxx"
-#include "SGModelBin.hxx"
-#include "SGBuildingBin.hxx"
-#include "TreeBin.hxx"
-#include "SGDirectionalLightBin.hxx"
-#include "GroundLightManager.hxx"
 
-#include "pt_lights.hxx"
+#include "SGTileGeometryBin.hxx"        // for original tile loading
+#include "SGTileDetailsCallback.hxx"    // for tile details ( random objects, and lighting )
 
-#define SG_SIMPLIFIER_RATIO 0.001
-#define SG_SIMPLIFIER_MAX_LENGTH 1000.0
-#define SG_SIMPLIFIER_MAX_ERROR 2000.0
-#define SG_OBJECT_RANGE 9000.0
-#define SG_TILE_RADIUS 14000.0
-#define SG_TILE_MIN_EXPIRY 180.0
 
 using namespace simgear;
 
-typedef std::map<std::string,SGTexturedTriangleBin> SGMaterialTriangleMap;
-typedef std::list<SGLightBin> SGLightListBin;
-typedef std::list<SGDirectionalLightBin> SGDirectionalLightListBin;
-
-class SGTileGeometryBin : public osg::Referenced {
-public:
-  SGMaterialTriangleMap materialTriangleMap;
-  SGLightBin tileLights;
-  SGLightBin randomTileLights;
-  SGTreeBinList randomForest;
-  SGDirectionalLightBin runwayLights;
-  SGDirectionalLightBin taxiLights;
-  SGDirectionalLightListBin vasiLights;
-  SGDirectionalLightListBin rabitLights;
-  SGLightListBin odalLights;
-  SGDirectionalLightListBin holdshortLights;
-  SGDirectionalLightListBin guardLights;
-  SGDirectionalLightListBin reilLights;
-  SGMatModelBin randomModels;
-  SGBuildingBinList randomBuildings;
-  bool tileRandomSurfaceLightsComputed;
-  bool tileRandomObjectsComputed;
-
-  SGTileGeometryBin() {
-         tileRandomSurfaceLightsComputed = false;
-         tileRandomObjectsComputed = false;
-  }
-
-  static SGVec4f
-  getMaterialLightColor(const SGMaterial* material)
-  {
-    if (!material)
-      return SGVec4f(1, 1, 1, 0.8);
-    return material->get_light_color();
-  }
-
-  static void
-  addPointGeometry(SGLightBin& lights,
-                   const std::vector<SGVec3d>& vertices,
-                   const SGVec4f& color,
-                   const int_list& pts_v)
-  {
-    for (unsigned i = 0; i < pts_v.size(); ++i)
-      lights.insert(toVec3f(vertices[pts_v[i]]), color);
-  }
-
-  static void
-  addPointGeometry(SGDirectionalLightBin& lights,
-                   const std::vector<SGVec3d>& vertices,
-                   const std::vector<SGVec3f>& normals,
-                   const SGVec4f& color,
-                   const int_list& pts_v,
-                   const int_list& pts_n)
-  {
-    // If the normal indices match the vertex indices, use seperate
-    // normal indices. Else reuse the vertex indices for the normals.
-    if (pts_v.size() == pts_n.size()) {
-      for (unsigned i = 0; i < pts_v.size(); ++i)
-        lights.insert(toVec3f(vertices[pts_v[i]]), normals[pts_n[i]], color);
-    } else {
-      for (unsigned i = 0; i < pts_v.size(); ++i)
-        lights.insert(toVec3f(vertices[pts_v[i]]), normals[pts_v[i]], color);
-    }
-  }
-
-  bool
-  insertPtGeometry(const SGBinObject& obj, SGMaterialCache* matcache)
-  {
-    if (obj.get_pts_v().size() != obj.get_pts_n().size()) {
-      SG_LOG(SG_TERRAIN, SG_ALERT,
-             "Group list sizes for points do not match!");
-      return false;
-    }
-
-    for (unsigned grp = 0; grp < obj.get_pts_v().size(); ++grp) {
-      std::string materialName = obj.get_pt_materials()[grp];
-      SGMaterial* material = matcache->find(materialName);
-      SGVec4f color = getMaterialLightColor(material);
-
-      if (3 <= materialName.size() && materialName.substr(0, 3) != "RWY") {
-        // Just plain lights. Not something for the runway.
-        addPointGeometry(tileLights, obj.get_wgs84_nodes(), color,
-                         obj.get_pts_v()[grp]);
-      } else if (materialName == "RWY_BLUE_TAXIWAY_LIGHTS"
-                 || materialName == "RWY_GREEN_TAXIWAY_LIGHTS") {
-        addPointGeometry(taxiLights, obj.get_wgs84_nodes(), obj.get_normals(),
-                         color, obj.get_pts_v()[grp], obj.get_pts_n()[grp]);
-      } else if (materialName == "RWY_VASI_LIGHTS") {
-        vasiLights.push_back(SGDirectionalLightBin());
-        addPointGeometry(vasiLights.back(), obj.get_wgs84_nodes(),
-                         obj.get_normals(), color, obj.get_pts_v()[grp],
-                         obj.get_pts_n()[grp]);
-      } else if (materialName == "RWY_SEQUENCED_LIGHTS") {
-        rabitLights.push_back(SGDirectionalLightBin());
-        addPointGeometry(rabitLights.back(), obj.get_wgs84_nodes(),
-                         obj.get_normals(), color, obj.get_pts_v()[grp],
-                         obj.get_pts_n()[grp]);
-      } else if (materialName == "RWY_ODALS_LIGHTS") {
-        odalLights.push_back(SGLightBin());
-        addPointGeometry(odalLights.back(), obj.get_wgs84_nodes(),
-                         color, obj.get_pts_v()[grp]);
-      } else if (materialName == "RWY_YELLOW_PULSE_LIGHTS") {
-        holdshortLights.push_back(SGDirectionalLightBin());
-        addPointGeometry(holdshortLights.back(), obj.get_wgs84_nodes(),
-                         obj.get_normals(), color, obj.get_pts_v()[grp],
-                         obj.get_pts_n()[grp]);
-      } else if (materialName == "RWY_GUARD_LIGHTS") {
-        guardLights.push_back(SGDirectionalLightBin());
-        addPointGeometry(guardLights.back(), obj.get_wgs84_nodes(),
-                         obj.get_normals(), color, obj.get_pts_v()[grp],
-                         obj.get_pts_n()[grp]);
-      } else if (materialName == "RWY_REIL_LIGHTS") {
-        reilLights.push_back(SGDirectionalLightBin());
-        addPointGeometry(reilLights.back(), obj.get_wgs84_nodes(),
-                         obj.get_normals(), color, obj.get_pts_v()[grp],
-                         obj.get_pts_n()[grp]);
-      } else {
-        // what is left must be runway lights
-        addPointGeometry(runwayLights, obj.get_wgs84_nodes(),
-                         obj.get_normals(), color, obj.get_pts_v()[grp],
-                         obj.get_pts_n()[grp]);
-      }
-    }
-
-    return true;
-  }
-
-
-  static SGVec2f
-  getTexCoord(const std::vector<SGVec2f>& texCoords, const int_list& tc,
-              const SGVec2f& tcScale, unsigned i)
-  {
-    if (tc.empty())
-      return tcScale;
-    else if (tc.size() == 1)
-      return mult(texCoords[tc[0]], tcScale);
-    else
-      return mult(texCoords[tc[i]], tcScale);
-  }
-
-  static void
-  addTriangleGeometry(SGTexturedTriangleBin& triangles,
-                      const SGBinObject& obj, unsigned grp,
-                      const SGVec2f& tc0Scale, 
-                      const SGVec2f& tc1Scale)
-  {
-    const std::vector<SGVec3d>& vertices(obj.get_wgs84_nodes());
-    const std::vector<SGVec3f>& normals(obj.get_normals());
-    const std::vector<SGVec2f>& texCoords(obj.get_texcoords());
-    const int_list& tris_v(obj.get_tris_v()[grp]);
-    const int_list& tris_n(obj.get_tris_n()[grp]);
-    const tci_list& tris_tc(obj.get_tris_tcs()[grp]);
-    bool  num_norms_is_num_verts = true;  
-    
-    if (tris_v.size() != tris_n.size()) {
-        // If the normal indices do not match, they should be inmplicitly
-        // the same than the vertex indices. 
-        num_norms_is_num_verts = false;
-    }
-
-    if ( !tris_tc[1].empty() ) {
-        triangles.hasSecondaryTexCoord(true);
-    }
-    
-    for (unsigned i = 2; i < tris_v.size(); i += 3) {
-        SGVertNormTex v0;
-        v0.SetVertex( toVec3f(vertices[tris_v[i-2]]) );
-        v0.SetNormal( num_norms_is_num_verts ? normals[tris_n[i-2]] : 
-                                               normals[tris_v[i-2]] );
-        v0.SetTexCoord( 0, getTexCoord(texCoords, tris_tc[0], tc0Scale, i-2) );
-        if (!tris_tc[1].empty()) {
-            v0.SetTexCoord( 1, getTexCoord(texCoords, tris_tc[1], tc1Scale, i-2) );
-        }
-        SGVertNormTex v1;
-        v1.SetVertex( toVec3f(vertices[tris_v[i-1]]) );
-        v1.SetNormal( num_norms_is_num_verts ? normals[tris_n[i-1]] : 
-                                               normals[tris_v[i-1]] );
-        v1.SetTexCoord( 0, getTexCoord(texCoords, tris_tc[0], tc0Scale, i-1) );
-        if (!tris_tc[1].empty()) {
-            v1.SetTexCoord( 1, getTexCoord(texCoords, tris_tc[1], tc1Scale, i-1) );
-        }
-        SGVertNormTex v2;
-        v2.SetVertex( toVec3f(vertices[tris_v[i]]) );
-        v2.SetNormal( num_norms_is_num_verts ? normals[tris_n[i]] : 
-                                               normals[tris_v[i]] );
-        v2.SetTexCoord( 0, getTexCoord(texCoords, tris_tc[0], tc0Scale, i) );
-        if (!tris_tc[1].empty()) {
-            v2.SetTexCoord( 1, getTexCoord(texCoords, tris_tc[1], tc1Scale, i) );
-        }
-        
-        triangles.insert(v0, v1, v2);
-    }
-  }
-
-  static void
-  addStripGeometry(SGTexturedTriangleBin& triangles,
-                   const SGBinObject& obj, unsigned grp,
-                   const SGVec2f& tc0Scale, 
-                   const SGVec2f& tc1Scale)
-  {
-      const std::vector<SGVec3d>& vertices(obj.get_wgs84_nodes());
-      const std::vector<SGVec3f>& normals(obj.get_normals());
-      const std::vector<SGVec2f>& texCoords(obj.get_texcoords());
-      const int_list& strips_v(obj.get_strips_v()[grp]);
-      const int_list& strips_n(obj.get_strips_n()[grp]);
-      const tci_list& strips_tc(obj.get_strips_tcs()[grp]);
-      bool  num_norms_is_num_verts = true;  
-      
-      if (strips_v.size() != strips_n.size()) {
-          // If the normal indices do not match, they should be inmplicitly
-          // the same than the vertex indices. 
-          num_norms_is_num_verts = false;
-      }
-      
-      if ( !strips_tc[1].empty() ) {
-          triangles.hasSecondaryTexCoord(true);
-      }
-      
-    for (unsigned i = 2; i < strips_v.size(); ++i) {
-      SGVertNormTex v0;
-      v0.SetVertex( toVec3f(vertices[strips_v[i-2]]) );
-      v0.SetNormal( num_norms_is_num_verts ? normals[strips_n[i-2]] : 
-                                             normals[strips_v[i-2]] );
-      v0.SetTexCoord( 0, getTexCoord(texCoords, strips_tc[0], tc0Scale, i-2) );
-      if (!strips_tc[1].empty()) {
-          v0.SetTexCoord( 1, getTexCoord(texCoords, strips_tc[1], tc1Scale, i-2) );
-      }
-      SGVertNormTex v1;
-      v1.SetVertex( toVec3f(vertices[strips_v[i-1]]) );
-      v1.SetNormal( num_norms_is_num_verts ? normals[strips_n[i-1]] : 
-                                             normals[strips_v[i-1]] );
-      v1.SetTexCoord( 0, getTexCoord(texCoords, strips_tc[1], tc0Scale, i-1) );
-      if (!strips_tc[1].empty()) {
-          v1.SetTexCoord( 1, getTexCoord(texCoords, strips_tc[1], tc1Scale, i-1) );
-      }
-      SGVertNormTex v2;
-      v2.SetVertex( toVec3f(vertices[strips_v[i]]) );
-      v2.SetNormal( num_norms_is_num_verts ? normals[strips_n[i]] : 
-                                             normals[strips_v[i]] );
-      v2.SetTexCoord( 0, getTexCoord(texCoords, strips_tc[0], tc0Scale, i) );
-      if (!strips_tc[1].empty()) {
-          v2.SetTexCoord( 1, getTexCoord(texCoords, strips_tc[1], tc1Scale, i) );
-      }
-      if (i%2)
-        triangles.insert(v1, v0, v2);
-      else
-        triangles.insert(v0, v1, v2);
-    }
-  }
-
-  static void
-  addFanGeometry(SGTexturedTriangleBin& triangles,
-                 const SGBinObject& obj, unsigned grp,
-                 const SGVec2f& tc0Scale, 
-                 const SGVec2f& tc1Scale)
-  {
-      const std::vector<SGVec3d>& vertices(obj.get_wgs84_nodes());
-      const std::vector<SGVec3f>& normals(obj.get_normals());
-      const std::vector<SGVec2f>& texCoords(obj.get_texcoords());
-      const int_list& fans_v(obj.get_fans_v()[grp]);
-      const int_list& fans_n(obj.get_fans_n()[grp]);
-      const tci_list& fans_tc(obj.get_fans_tcs()[grp]);
-      bool  num_norms_is_num_verts = true;  
-      
-      if (fans_v.size() != fans_n.size()) {
-          // If the normal indices do not match, they should be inmplicitly
-          // the same than the vertex indices. 
-          num_norms_is_num_verts = false;
-      }
-      
-      if ( !fans_tc[1].empty() ) {
-          triangles.hasSecondaryTexCoord(true);
-      }
-      
-    SGVertNormTex v0;
-    v0.SetVertex( toVec3f(vertices[fans_v[0]]) );
-    v0.SetNormal( num_norms_is_num_verts ? normals[fans_n[0]] : 
-                                           normals[fans_v[0]] );
-    v0.SetTexCoord( 0, getTexCoord(texCoords, fans_tc[0], tc0Scale, 0) );
-    if (!fans_tc[1].empty()) {
-        v0.SetTexCoord( 1, getTexCoord(texCoords, fans_tc[1], tc1Scale, 0) );
-    }
-    SGVertNormTex v1;
-    v1.SetVertex( toVec3f(vertices[fans_v[1]]) );
-    v1.SetNormal( num_norms_is_num_verts ? normals[fans_n[1]] : 
-                                           normals[fans_v[1]] );
-    v1.SetTexCoord( 0, getTexCoord(texCoords, fans_tc[0], tc0Scale, 1) );
-    if (!fans_tc[1].empty()) {
-        v1.SetTexCoord( 1, getTexCoord(texCoords, fans_tc[1], tc1Scale, 1) );
-    }
-    for (unsigned i = 2; i < fans_v.size(); ++i) {
-      SGVertNormTex v2;
-      v2.SetVertex( toVec3f(vertices[fans_v[i]]) );
-      v2.SetNormal( num_norms_is_num_verts ? normals[fans_n[i]] : 
-                                             normals[fans_v[i]] );
-      v2.SetTexCoord( 0, getTexCoord(texCoords, fans_tc[0], tc0Scale, i) );
-      if (!fans_tc[1].empty()) {
-          v2.SetTexCoord( 1, getTexCoord(texCoords, fans_tc[1], tc1Scale, i) );
-      }
-      triangles.insert(v0, v1, v2);
-      v1 = v2;
-    }
-  }
-
-  SGVec2f getTexCoordScale(const std::string& name, SGMaterialCache* matcache)
-  {
-    if (!matcache)
-      return SGVec2f(1, 1);
-    SGMaterial* material = matcache->find(name);
-    if (!material)
-      return SGVec2f(1, 1);
-
-    return material->get_tex_coord_scale();
-  }
-
-  bool
-  insertSurfaceGeometry(const SGBinObject& obj, SGMaterialCache* matcache)
-  {
-    if (obj.get_tris_n().size() < obj.get_tris_v().size() ||
-        obj.get_tris_tcs().size() < obj.get_tris_v().size()) {
-      SG_LOG(SG_TERRAIN, SG_ALERT,
-             "Group list sizes for triangles do not match!");
-      return false;
-    }
-
-    for (unsigned grp = 0; grp < obj.get_tris_v().size(); ++grp) {
-      std::string materialName = obj.get_tri_materials()[grp];
-      SGVec2f tc0Scale = getTexCoordScale(materialName, matcache);
-      SGVec2f tc1Scale(1.0, 1.0);
-      addTriangleGeometry(materialTriangleMap[materialName],
-                          obj, grp, tc0Scale, tc1Scale );
-    }
-
-    if (obj.get_strips_n().size() < obj.get_strips_v().size() ||
-        obj.get_strips_tcs().size() < obj.get_strips_v().size()) {
-      SG_LOG(SG_TERRAIN, SG_ALERT,
-             "Group list sizes for strips do not match!");
-      return false;
-    }
-    for (unsigned grp = 0; grp < obj.get_strips_v().size(); ++grp) {
-      std::string materialName = obj.get_strip_materials()[grp];
-      SGVec2f tc0Scale = getTexCoordScale(materialName, matcache);
-      SGVec2f tc1Scale(1.0, 1.0);
-      addStripGeometry(materialTriangleMap[materialName],
-                          obj, grp, tc0Scale, tc1Scale);
-    }
-
-    if (obj.get_fans_n().size() < obj.get_fans_v().size() ||
-        obj.get_fans_tcs().size() < obj.get_fans_v().size()) {
-      SG_LOG(SG_TERRAIN, SG_ALERT,
-             "Group list sizes for fans do not match!");
-      return false;
-    }
-    for (unsigned grp = 0; grp < obj.get_fans_v().size(); ++grp) {
-      std::string materialName = obj.get_fan_materials()[grp];
-      SGVec2f tc0Scale = getTexCoordScale(materialName, matcache);
-      SGVec2f tc1Scale(1.0, 1.0);
-      addFanGeometry(materialTriangleMap[materialName],
-                       obj, grp, tc0Scale, tc1Scale );
-    }
-    return true;
-  }
-
-  osg::Node* getSurfaceGeometry(SGMaterialCache* matcache, bool useVBOs) const
-  {
-    if (materialTriangleMap.empty())
-      return 0;
-
-    EffectGeode* eg = NULL;
-    osg::Group* group = (materialTriangleMap.size() > 1 ? new osg::Group : NULL);
-    if (group) {
-        group->setName("surfaceGeometryGroup");
-    }
-    
-    //osg::Geode* geode = new osg::Geode;
-    SGMaterialTriangleMap::const_iterator i;
-    for (i = materialTriangleMap.begin(); i != materialTriangleMap.end(); ++i) {
-      osg::Geometry* geometry = i->second.buildGeometry(useVBOs);
-      SGMaterial *mat = NULL;
-      if (matcache) {
-        mat = matcache->find(i->first);
-      }
-      eg = new EffectGeode;
-      eg->setName("EffectGeode");
-      if (mat) {
-        eg->setEffect(mat->get_effect(i->second));
-      }
-      eg->addDrawable(geometry);
-      eg->runGenerators(geometry);  // Generate extra data needed by effect
-      if (group) {
-        group->addChild(eg);
-      }
-    }
-    
-    if (group) {
-        return group;
-    } else {
-        return eg;
-    }
-  }
-
-  void computeRandomSurfaceLights(SGMaterialCache* matcache)
-  {
-    SGMaterialTriangleMap::iterator i;
-
-    // Only compute the lights if we haven't already done so.
-    // For example, the light data will still exist if the
-    // PagedLOD expires.
-    if (tileRandomSurfaceLightsComputed) return;
-    tileRandomSurfaceLightsComputed = true;
-
-    // generate a repeatable random seed
-    mt seed;
-    mt_init(&seed, unsigned(123));
-
-    for (i = materialTriangleMap.begin(); i != materialTriangleMap.end(); ++i) {
-      SGMaterial *mat = matcache->find(i->first);
-      if (!mat)
-        continue;
-
-      float coverage = mat->get_light_coverage();
-      if (coverage <= 0)
-        continue;
-      if (coverage < 10000.0) {
-        SG_LOG(SG_INPUT, SG_ALERT, "Light coverage is "
-               << coverage << ", pushing up to 10000");
-        coverage = 10000;
-      }
-
-      std::vector<SGVec3f> randomPoints;
-      i->second.addRandomSurfacePoints(coverage, 3, mat->get_object_mask(i->second), randomPoints);
-      std::vector<SGVec3f>::iterator j;
-      for (j = randomPoints.begin(); j != randomPoints.end(); ++j) {
-        float zombie = mt_rand(&seed);
-        // factor = sg_random() ^ 2, range = 0 .. 1 concentrated towards 0
-        float factor = mt_rand(&seed);
-        factor *= factor;
-
-        float bright = 1;
-        SGVec4f color;
-        if ( zombie > 0.5 ) {
-          // 50% chance of yellowish
-          color = SGVec4f(0.9f, 0.9f, 0.3f, bright - factor * 0.2f);
-        } else if (zombie > 0.15f) {
-          // 35% chance of whitish
-          color = SGVec4f(0.9, 0.9f, 0.8f, bright - factor * 0.2f);
-        } else if (zombie > 0.05f) {
-          // 10% chance of orangish
-          color = SGVec4f(0.9f, 0.6f, 0.2f, bright - factor * 0.2f);
-        } else {
-          // 5% chance of redish
-          color = SGVec4f(0.9f, 0.2f, 0.2f, bright - factor * 0.2f);
-        }
-        randomTileLights.insert(*j, color);
-      }
-    }
-  }
-
-  void computeRandomObjectsAndBuildings(
-    SGMaterialCache* matcache,
-    float building_density,
-    bool use_random_objects,
-    bool use_random_buildings,
-    bool useVBOs)
-  {
-    SGMaterialTriangleMap::iterator i;
-
-    // Only compute the random objects if we haven't already done so
-    if (tileRandomObjectsComputed) return;
-    tileRandomObjectsComputed = true;
-
-    // generate a repeatable random seed
-    mt seed;
-    mt_init(&seed, unsigned(123));
-
-    for (i = materialTriangleMap.begin(); i != materialTriangleMap.end(); ++i) {
-      SGMaterial *mat = matcache->find(i->first);
-      SGTexturedTriangleBin triangleBin = i->second;
-
-      if (!mat)
-        continue;
-
-      osg::Texture2D* object_mask  = mat->get_object_mask(triangleBin);
-
-      int   group_count            = mat->get_object_group_count();
-      float building_coverage      = mat->get_building_coverage();
-      float cos_zero_density_angle = mat->get_cos_object_zero_density_slope_angle();
-      float cos_max_density_angle  = mat->get_cos_object_max_density_slope_angle();
-
-      if (building_coverage == 0)
-         continue;
-
-      SGBuildingBin* bin = NULL;
-
-      if (building_coverage > 0) {
-        bin = new SGBuildingBin(mat, useVBOs);
-        randomBuildings.push_back(bin);
-      }
-
-      unsigned num = i->second.getNumTriangles();
-      int random_dropped = 0;
-      int mask_dropped = 0;
-      int building_dropped = 0;
-      int triangle_dropped = 0;
-
-      for (unsigned i = 0; i < num; ++i) {
-        SGTexturedTriangleBin::triangle_ref triangleRef = triangleBin.getTriangleRef(i);
-
-        SGVec3f vorigin = triangleBin.getVertex(triangleRef[0]).GetVertex();
-        SGVec3f v0 = triangleBin.getVertex(triangleRef[1]).GetVertex() - vorigin;
-        SGVec3f v1 = triangleBin.getVertex(triangleRef[2]).GetVertex() - vorigin;
-        SGVec2f torigin = triangleBin.getVertex(triangleRef[0]).GetTexCoord(0);
-        SGVec2f t0 = triangleBin.getVertex(triangleRef[1]).GetTexCoord(0) - torigin;
-        SGVec2f t1 = triangleBin.getVertex(triangleRef[2]).GetTexCoord(0) - torigin;
-        SGVec3f normal = cross(v0, v1);
-
-        // Ensure the slope isn't too steep by checking the
-        // cos of the angle between the slope normal and the
-        // vertical (conveniently the z-component of the normalized
-        // normal) and values passed in.
-        float cos = normalize(normal).z();
-        float slope_density = 1.0;
-        if (cos < cos_zero_density_angle) continue; // Too steep for any objects
-        if (cos < cos_max_density_angle) {
-          slope_density =
-            (cos - cos_zero_density_angle) /
-            (cos_max_density_angle - cos_zero_density_angle);
-        }
-
-        // Containers to hold the random buildings and objects generated
-        // for this triangle for collision detection purposes.
-        std::vector< std::pair< SGVec3f, float> > triangleObjectsList;
-        std::vector< std::pair< SGVec3f, float> > triangleBuildingList;
-
-        // Compute the area
-        float area = 0.5f*length(normal);
-        if (area <= SGLimitsf::min())
-          continue;
-
-        // Generate any random objects
-        if (use_random_objects && (group_count > 0))
-        {
-          for (int j = 0; j < group_count; j++)
-          {
-            SGMatModelGroup *object_group =  mat->get_object_group(j);
-            int nObjects = object_group->get_object_count();
-
-            if (nObjects == 0) continue;
-
-            // For each of the random models in the group, determine an appropriate
-            // number of random placements and insert them.
-            for (int k = 0; k < nObjects; k++) {
-              SGMatModel * object = object_group->get_object(k);
-
-              // Determine the number of objecst to place, taking into account
-              // the slope density factor.
-              double n = slope_density * area / object->get_coverage_m2();
-
-              // Use the zombie door method to determine fractional object placement.
-              n = n + mt_rand(&seed);
-
-              // place an object each unit of area
-              while ( n > 1.0 ) {
-               n -= 1.0;
-
-                float a = mt_rand(&seed);
-                float b = mt_rand(&seed);
-                if ( a + b > 1 ) {
-                  a = 1 - a;
-                  b = 1 - b;
-                }
-
-                SGVec3f randomPoint = vorigin + a*v0 + b*v1;
-                float rotation = static_cast<float>(mt_rand(&seed));
-
-                // Check that the point is sufficiently far from
-                // the edge of the triangle by measuring the distance
-                // from the three lines that make up the triangle.
-                float spacing = object->get_spacing_m();
-
-                SGVec3f p = randomPoint - vorigin;
-                float edges[] = { length(cross(p     , p - v0)) / length(v0),
-                                  length(cross(p - v0, p - v1)) / length(v1 - v0),
-                                  length(cross(p - v1, p     )) / length(v1)      };
-                float edge_dist = *std::min_element(edges, edges + 3);
-
-                if (edge_dist < spacing) {
-                  continue;
-                }
-
-                if (object_mask != NULL) {
-                  SGVec2f texCoord = torigin + a*t0 + b*t1;
-
-                  // Check this random point against the object mask
-                  // blue (for buildings) channel.
-                  osg::Image* img = object_mask->getImage();
-                  unsigned int x = (int) (img->s() * texCoord.x()) % img->s();
-                  unsigned int y = (int) (img->t() * texCoord.y()) % img->t();
-
-                  if (mt_rand(&seed) > img->getColor(x, y).b()) {
-                    // Failed object mask check
-                    continue;
-                  }
-
-                  rotation = img->getColor(x,y).r();
-                }
-
-                bool close = false;
-
-                // Check it isn't too close to any other random objects in the triangle
-                std::vector<std::pair<SGVec3f, float> >::iterator l;
-                for (l = triangleObjectsList.begin(); l != triangleObjectsList.end(); ++l) {
-                  float min_dist2 = (l->second + object->get_spacing_m()) *
-                                    (l->second + object->get_spacing_m());
-
-                  if (distSqr(l->first, randomPoint) < min_dist2) {
-                    close = true;
-                    continue;
-                  }
-                }
-
-                if (!close) {
-                    triangleObjectsList.push_back(std::make_pair(randomPoint, object->get_spacing_m()));
-                    randomModels.insert(randomPoint,
-                                        object,
-                                        (int)object->get_randomized_range_m(&seed),
-                                        rotation);
-                }
-              }
-            }
-          }
-        }
-
-        // Random objects now generated.  Now generate the random buildings (if any);
-        if (use_random_buildings && (building_coverage > 0) && (building_density > 0)) {
-
-          // Calculate the number of buildings, taking into account building density (which is linear)
-          // and the slope density factor.
-          double num = building_density * building_density * slope_density * area / building_coverage;
-
-          // For partial units of area, use a zombie door method to
-          // create the proper random chance of an object being created
-          // for this triangle.
-          num = num + mt_rand(&seed);
-
-          if (num < 1.0f) {
-            continue;
-          }
-
-          // Cosine of the angle between the two vectors.
-          float cosine = (dot(v0, v1) / (length(v0) * length(v1)));
-
-          // Determine a grid spacing in each vector such that the correct
-          // coverage will result.
-          float stepv0 = (sqrtf(building_coverage) / building_density) / length(v0) / sqrtf(1 - cosine * cosine);
-          float stepv1 = (sqrtf(building_coverage) / building_density) / length(v1);
-
-          stepv0 = std::min(stepv0, 1.0f);
-          stepv1 = std::min(stepv1, 1.0f);
-
-          // Start at a random point. a will be immediately incremented below.
-          float a = -mt_rand(&seed) * stepv0;
-          float b = mt_rand(&seed) * stepv1;
-
-          // Place an object each unit of area
-          while (num > 1.0) {
-            num -= 1.0;
-
-            // Set the next location to place a building
-            a += stepv0;
-
-            if ((a + b) > 1.0f) {
-              // Reached the end of the scan-line on v0. Reset and increment
-              // scan-line on v1
-              a = mt_rand(&seed) * stepv0;
-              b += stepv1;
-            }
-
-            if (b > 1.0f) {
-              // In a degenerate case of a single point, we might be outside the
-              // scanline.  Note that we need to still ensure that a+b < 1.
-              b = mt_rand(&seed) * stepv1 * (1.0f - a);
-            }
-
-            if ((a + b) > 1.0f ) {
-              // Truly degenerate case - simply choose a random point guaranteed
-              // to fulfil the constraing of a+b < 1.
-              a = mt_rand(&seed);
-              b = mt_rand(&seed) * (1.0f - a);
-            }
-
-            SGVec3f randomPoint = vorigin + a*v0 + b*v1;
-            float rotation = mt_rand(&seed);
-
-            if (object_mask != NULL) {
-              SGVec2f texCoord = torigin + a*t0 + b*t1;
-              osg::Image* img = object_mask->getImage();
-              int x = (int) (img->s() * texCoord.x()) % img->s();
-              int y = (int) (img->t() * texCoord.y()) % img->t();
-
-              // In some degenerate cases x or y can be < 1, in which case the mod operand fails
-              while (x < 0) x += img->s();
-              while (y < 0) y += img->t();
-
-              if (mt_rand(&seed) < img->getColor(x, y).b()) {
-                // Object passes mask. Rotation is taken from the red channel
-                rotation = img->getColor(x,y).r();
-              } else {
-                // Fails mask test - try again.
-                mask_dropped++;
-                continue;
-              }
-            }
-
-            // Check building isn't too close to the triangle edge.
-            float type_roll = mt_rand(&seed);
-            SGBuildingBin::BuildingType buildingtype = bin->getBuildingType(type_roll);
-            float radius = bin->getBuildingMaxRadius(buildingtype);
-
-            // Determine the actual center of the building, by shifting from the
-            // center of the front face to the true center.
-            osg::Matrix rotationMat = osg::Matrix::rotate(- rotation * M_PI * 2,
-                                                 osg::Vec3f(0.0, 0.0, 1.0));
-            SGVec3f buildingCenter = randomPoint + toSG(osg::Vec3f(-0.5 * bin->getBuildingMaxDepth(buildingtype), 0.0, 0.0) * rotationMat);
-
-            SGVec3f p = buildingCenter - vorigin;
-            float edges[] = { length(cross(p     , p - v0)) / length(v0),
-                              length(cross(p - v0, p - v1)) / length(v1 - v0),
-                              length(cross(p - v1, p     )) / length(v1)      };
-            float edge_dist = *std::min_element(edges, edges + 3);
-
-            if (edge_dist < radius) {
-              triangle_dropped++;
-              continue;
-            }
-
-            // Check building isn't too close to random objects and other buildings.
-            bool close = false;
-            std::vector<std::pair<SGVec3f, float> >::iterator iter;
-
-            for (iter = triangleBuildingList.begin(); iter != triangleBuildingList.end(); ++iter) {
-              float min_dist = iter->second + radius;
-              if (distSqr(iter->first, buildingCenter) < min_dist * min_dist) {
-                close = true;
-                continue;
-              }
-            }
-
-            if (close) {
-              building_dropped++;
-              continue;
-            }
-
-            for (iter = triangleObjectsList.begin(); iter != triangleObjectsList.end(); ++iter) {
-              float min_dist = iter->second + radius;
-              if (distSqr(iter->first, buildingCenter) < min_dist * min_dist) {
-                close = true;
-                continue;
-              }
-            }
-
-            if (close) {
-              random_dropped++;
-              continue;
-            }
-
-            std::pair<SGVec3f, float> pt = std::make_pair(buildingCenter, radius);
-            triangleBuildingList.push_back(pt);
-            bin->insert(randomPoint, rotation, buildingtype);
-          }
-        }
-
-        triangleObjectsList.clear();
-        triangleBuildingList.clear();
-      }
-
-      SG_LOG(SG_TERRAIN, SG_DEBUG, "Random Buildings: " << ((bin) ? bin->getNumBuildings() : 0));
-      SG_LOG(SG_TERRAIN, SG_DEBUG, "  Dropped due to mask: " << mask_dropped);
-      SG_LOG(SG_TERRAIN, SG_DEBUG, "  Dropped due to random object: " << random_dropped);
-      SG_LOG(SG_TERRAIN, SG_DEBUG, "  Dropped due to other buildings: " << building_dropped);
-    }
-  }
-
-  void computeRandomForest(SGMaterialCache* matcache, float vegetation_density)
-  {
-    SGMaterialTriangleMap::iterator i;
-
-    // generate a repeatable random seed
-    mt seed;
-
-    mt_init(&seed, unsigned(586));
-
-    for (i = materialTriangleMap.begin(); i != materialTriangleMap.end(); ++i) {
-      SGMaterial *mat = matcache->find(i->first);
-      if (!mat)
-        continue;
-
-      float wood_coverage = mat->get_wood_coverage();
-      if ((wood_coverage <= 0) || (vegetation_density <= 0))
-        continue;
-
-      // Attributes that don't vary by tree but do vary by material
-      bool found = false;
-      TreeBin* bin = NULL;
-
-      BOOST_FOREACH(bin, randomForest)
-      {
-        if ((bin->texture           == mat->get_tree_texture()  ) &&
-            (bin->texture_varieties == mat->get_tree_varieties()) &&
-            (bin->range             == mat->get_tree_range()    ) &&
-            (bin->width             == mat->get_tree_width()    ) &&
-            (bin->height            == mat->get_tree_height()   )   ) {
-            found = true;
-            break;
-        }
-      }
-
-      if (!found) {
-        bin = new TreeBin();
-        bin->texture = mat->get_tree_texture();
-          SG_LOG(SG_INPUT, SG_DEBUG, "Tree texture " << bin->texture);
-        bin->range   = mat->get_tree_range();
-        bin->width   = mat->get_tree_width();
-        bin->height  = mat->get_tree_height();
-        bin->texture_varieties = mat->get_tree_varieties();
-        randomForest.push_back(bin);
-      }
-
-      std::vector<SGVec3f> randomPoints;
-      i->second.addRandomTreePoints(wood_coverage,
-                                    mat->get_object_mask(i->second),
-                                    vegetation_density,
-                                    mat->get_cos_tree_max_density_slope_angle(),
-                                    mat->get_cos_tree_zero_density_slope_angle(),
-                                    randomPoints);
-
-      std::vector<SGVec3f>::iterator k;
-      for (k = randomPoints.begin(); k != randomPoints.end(); ++k) {
-        bin->insert(*k);
-      }
-    }
-  }
-
-  bool insertBinObj(const SGBinObject& obj, SGMaterialCache* matcache)
-  {
-    if (!insertPtGeometry(obj, matcache))
-      return false;
-    if (!insertSurfaceGeometry(obj, matcache))
-      return false;
-    return true;
-  }
-};
-
-typedef std::pair<osg::Node*, int> ModelLOD;
-struct MakeQuadLeaf {
-    osg::LOD* operator() () const { return new osg::LOD; }
-};
-struct AddModelLOD {
-    void operator() (osg::LOD* leaf, ModelLOD& mlod) const
-    {
-        leaf->addChild(mlod.first, 0, mlod.second);
-    }
-};
-struct GetModelLODCoord {
-    GetModelLODCoord() {}
-    GetModelLODCoord(const GetModelLODCoord& rhs)
-    {}
-    osg::Vec3 operator() (const ModelLOD& mlod) const
-    {
-        return mlod.first->getBound().center();
-    }
-};
-
-typedef QuadTreeBuilder<osg::LOD*, ModelLOD, MakeQuadLeaf, AddModelLOD,
-                        GetModelLODCoord>  RandomObjectsQuadtree;
-
-class RandomObjectCallback : public OptionsReadFileCallback {
-public:
-    virtual osgDB::ReaderWriter::ReadResult
-    readNode(const std::string&, const osgDB::Options*)
-    {
-        osg::ref_ptr<osg::Group> group = new osg::Group;
-        group->setName("Random Object and Lighting Group");
-        group->setDataVariance(osg::Object::STATIC);
-
-        osg::Node* node = loadTerrain();
-        if (node)
-          group->addChild(node);
-
-        osg::LOD* lightLOD = generateLightingTileObjects();
-        if (lightLOD)
-          group->addChild(lightLOD);
-
-        osg::LOD* objectLOD = generateRandomTileObjects();
-        if (objectLOD)
-          group->addChild(objectLOD);
-
-        return group.release();
-    }
-
-    // Load terrain if required
-    osg::Node* loadTerrain()
-    {
-      if (! _loadterrain)
-        return NULL;
-
-      SGBinObject tile;
-      if (!tile.read_bin(_path))
-        return NULL;
-
-      SGMaterialLibPtr matlib;
-      SGMaterialCache* matcache = 0;
-      bool useVBOs = false;
-      bool simplifyNear    = false;
-      double ratio       = SG_SIMPLIFIER_RATIO;
-      double maxLength   = SG_SIMPLIFIER_MAX_LENGTH;
-      double maxError    = SG_SIMPLIFIER_MAX_ERROR;
-
-      if (_options) {
-        matlib = _options->getMaterialLib();
-        useVBOs = (_options->getPluginStringData("SimGear::USE_VBOS") == "ON");
-        SGPropertyNode* propertyNode = _options->getPropertyNode().get();
-        simplifyNear = propertyNode->getBoolValue("/sim/rendering/terrain/simplifier/enabled-near", simplifyNear);
-        ratio = propertyNode->getDoubleValue("/sim/rendering/terrain/simplifier/ratio", ratio);
-        maxLength = propertyNode->getDoubleValue("/sim/rendering/terrain/simplifier/max-length", maxLength);
-        maxError = propertyNode->getDoubleValue("/sim/rendering/terrain/simplifier/max-error", maxError);
-      }
-
-      // PSADRO TODO : we can do this in terragear 
-      // - why not add a bitmask of flags to the btg so we can precompute this?
-      // and only do it if it hasn't been done already
-      SGVec3d center = tile.get_gbs_center();
-      SGGeod geodPos = SGGeod::fromCart(center);
-      SGQuatd hlOr = SGQuatd::fromLonLat(geodPos)*SGQuatd::fromEulerDeg(0, 0, 180);
-
-      // Generate a materials cache
-      if (matlib) matcache = matlib->generateMatCache(geodPos);
-
-      // rotate the tiles so that the bounding boxes get nearly axis aligned.
-      // this will help the collision tree's bounding boxes a bit ...
-      std::vector<SGVec3d> nodes = tile.get_wgs84_nodes();
-      for (unsigned i = 0; i < nodes.size(); ++i)
-        nodes[i] = hlOr.transform(nodes[i]);
-      tile.set_wgs84_nodes(nodes);
-
-      SGQuatf hlOrf(hlOr[0], hlOr[1], hlOr[2], hlOr[3]);
-      std::vector<SGVec3f> normals = tile.get_normals();
-      for (unsigned i = 0; i < normals.size(); ++i)
-        normals[i] = hlOrf.transform(normals[i]);
-      tile.set_normals(normals);
-
-      osg::ref_ptr<SGTileGeometryBin> tileGeometryBin = new SGTileGeometryBin;
-
-      if (!tileGeometryBin->insertBinObj(tile, matcache))
-        return NULL;
-
-      osg::Node* node = tileGeometryBin->getSurfaceGeometry(matcache, useVBOs);
-      if (node && simplifyNear) {
-        osgUtil::Simplifier simplifier(ratio, maxError, maxLength);
-        node->accept(simplifier);
-      }
-
-      return node;
-    }
-
-    // Generate all the lighting objects for the tile.
-    osg::LOD* generateLightingTileObjects()
-    {
-      if (_matcache)
-        _tileGeometryBin->computeRandomSurfaceLights(_matcache);
-
-      GroundLightManager* lightManager = GroundLightManager::instance();
-      osg::ref_ptr<osg::Group> lightGroup = new SGOffsetTransform(0.94);
-      SGVec3f up(0, 0, 1);
-
-      if (_tileGeometryBin->tileLights.getNumLights() > 0
-          || _tileGeometryBin->randomTileLights.getNumLights() > 0) {
-        osg::Group* groundLights0 = new osg::Group;
-        groundLights0->setStateSet(lightManager->getGroundLightStateSet());
-        groundLights0->setNodeMask(GROUNDLIGHTS0_BIT);
-
-        EffectGeode* geode = new EffectGeode;
-        Effect*  lightEffect = getLightEffect(24, osg::Vec3(1, 0.001, 0.00001), 1, 8, false, _options);
-
-        geode->setEffect(lightEffect);
-        geode->addDrawable(SGLightFactory::getLights(_tileGeometryBin->tileLights));
-        geode->addDrawable(SGLightFactory::getLights(_tileGeometryBin->randomTileLights, 4, -0.3f));
-        groundLights0->addChild(geode);
-        lightGroup->addChild(groundLights0);
-      }
-
-      if (_tileGeometryBin->randomTileLights.getNumLights() > 0) {
-        osg::Group* groundLights1 = new osg::Group;
-        groundLights1->setStateSet(lightManager->getGroundLightStateSet());
-        groundLights1->setNodeMask(GROUNDLIGHTS1_BIT);
-        osg::Group* groundLights2 = new osg::Group;
-        groundLights2->setStateSet(lightManager->getGroundLightStateSet());
-        groundLights2->setNodeMask(GROUNDLIGHTS2_BIT);
-        EffectGeode* geode = new EffectGeode;
-        Effect*  lightEffect = getLightEffect(24, osg::Vec3(1, 0.001, 0.00001), 1, 8, false, _options);
-        geode->setEffect(lightEffect);
-        geode->addDrawable(SGLightFactory::getLights(_tileGeometryBin->randomTileLights, 2, -0.15f));
-        groundLights1->addChild(geode);
-        lightGroup->addChild(groundLights1);
-        geode = new EffectGeode;
-        geode->setEffect(lightEffect);
-        geode->addDrawable(SGLightFactory::getLights(_tileGeometryBin->randomTileLights));
-        groundLights2->addChild(geode);
-        lightGroup->addChild(groundLights2);
-      }
-
-      if (!_tileGeometryBin->vasiLights.empty()) {
-        EffectGeode* vasiGeode = new EffectGeode;
-        Effect* vasiEffect
-            = getLightEffect(24, osg::Vec3(1, 0.0001, 0.000001), 1, 24, true, _options);
-        vasiGeode->setEffect(vasiEffect);
-        SGVec4f red(1, 0, 0, 1);
-        SGMaterial* mat = 0;
-        if (_matcache)
-          mat = _matcache->find("RWY_RED_LIGHTS");
-        if (mat)
-          red = mat->get_light_color();
-        SGVec4f white(1, 1, 1, 1);
-        mat = 0;
-        if (_matcache)
-          mat = _matcache->find("RWY_WHITE_LIGHTS");
-        if (mat)
-          white = mat->get_light_color();
-        SGDirectionalLightListBin::const_iterator i;
-        for (i = _tileGeometryBin->vasiLights.begin();
-             i != _tileGeometryBin->vasiLights.end(); ++i) {
-          vasiGeode->addDrawable(SGLightFactory::getVasi(up, *i, red, white));
-        }
-        vasiGeode->setStateSet(lightManager->getRunwayLightStateSet());
-        lightGroup->addChild(vasiGeode);
-      }
-
-      Effect* runwayEffect = 0;
-      if (_tileGeometryBin->runwayLights.getNumLights() > 0
-          || !_tileGeometryBin->rabitLights.empty()
-          || !_tileGeometryBin->reilLights.empty()
-          || !_tileGeometryBin->odalLights.empty()
-          || _tileGeometryBin->taxiLights.getNumLights() > 0)
-          runwayEffect = getLightEffect(16, osg::Vec3(1, 0.001, 0.0002), 1, 16, true, _options);
-      if (_tileGeometryBin->runwayLights.getNumLights() > 0
-          || !_tileGeometryBin->rabitLights.empty()
-          || !_tileGeometryBin->reilLights.empty()
-          || !_tileGeometryBin->odalLights.empty()
-          || !_tileGeometryBin->holdshortLights.empty()
-          || !_tileGeometryBin->guardLights.empty()) {
-        osg::Group* rwyLights = new osg::Group;
-        rwyLights->setStateSet(lightManager->getRunwayLightStateSet());
-        rwyLights->setNodeMask(RUNWAYLIGHTS_BIT);
-        if (_tileGeometryBin->runwayLights.getNumLights() != 0) {
-          EffectGeode* geode = new EffectGeode;
-          geode->setEffect(runwayEffect);
-          geode->addDrawable(SGLightFactory::getLights(_tileGeometryBin->runwayLights));
-          rwyLights->addChild(geode);
-        }
-        SGDirectionalLightListBin::const_iterator i;
-        for (i = _tileGeometryBin->rabitLights.begin();
-             i != _tileGeometryBin->rabitLights.end(); ++i) {
-          rwyLights->addChild(SGLightFactory::getSequenced(*i, _options));
-        }
-        for (i = _tileGeometryBin->reilLights.begin();
-             i != _tileGeometryBin->reilLights.end(); ++i) {
-          rwyLights->addChild(SGLightFactory::getSequenced(*i, _options));
-        }
-        for (i = _tileGeometryBin->holdshortLights.begin();
-             i != _tileGeometryBin->holdshortLights.end(); ++i) {
-          rwyLights->addChild(SGLightFactory::getHoldShort(*i, _options));
-        }
-        for (i = _tileGeometryBin->guardLights.begin();
-             i != _tileGeometryBin->guardLights.end(); ++i) {
-          rwyLights->addChild(SGLightFactory::getGuard(*i, _options));
-        }
-        SGLightListBin::const_iterator j;
-        for (j = _tileGeometryBin->odalLights.begin();
-             j != _tileGeometryBin->odalLights.end(); ++j) {
-          rwyLights->addChild(SGLightFactory::getOdal(*j, _options));
-        }
-        lightGroup->addChild(rwyLights);
-      }
-
-      if (_tileGeometryBin->taxiLights.getNumLights() > 0) {
-        osg::Group* taxiLights = new osg::Group;
-        taxiLights->setStateSet(lightManager->getTaxiLightStateSet());
-        taxiLights->setNodeMask(RUNWAYLIGHTS_BIT);
-        EffectGeode* geode = new EffectGeode;
-        geode->setEffect(runwayEffect);
-        geode->addDrawable(SGLightFactory::getLights(_tileGeometryBin->taxiLights));
-        taxiLights->addChild(geode);
-        lightGroup->addChild(taxiLights);
-      }
-
-      osg::LOD* lightLOD = NULL;
-
-      if (lightGroup->getNumChildren() > 0) {
-        lightLOD = new osg::LOD;
-        lightLOD->addChild(lightGroup.get(), 0, 60000);
-        // VASI is always on, so doesn't use light bits.
-        lightLOD->setNodeMask(LIGHTS_BITS | MODEL_BIT | PERMANENTLIGHT_BIT);
-      }
-
-      return lightLOD;
-    }
-
-    // Generate all the random forest, objects and buildings for the tile
-    osg::LOD* generateRandomTileObjects()
-    {
-      SGMaterialLibPtr matlib;
-      bool use_random_objects = false;
-      bool use_random_vegetation = false;
-      bool use_random_buildings = false;
-      float vegetation_density = 1.0f;
-      float building_density = 1.0f;
-      bool useVBOs = false;
-      
-      osg::ref_ptr<osg::Group> randomObjects;
-      osg::ref_ptr<osg::Group> forestNode;
-      osg::ref_ptr<osg::Group> buildingNode;
-
-      if (_options) {
-        matlib = _options->getMaterialLib();
-        SGPropertyNode* propertyNode = _options->getPropertyNode().get();
-        if (propertyNode) {
-            use_random_objects
-                = propertyNode->getBoolValue("/sim/rendering/random-objects",
-                                             use_random_objects);
-            use_random_vegetation
-                = propertyNode->getBoolValue("/sim/rendering/random-vegetation",
-                                             use_random_vegetation);
-            vegetation_density
-                = propertyNode->getFloatValue("/sim/rendering/vegetation-density",
-                                              vegetation_density);
-            use_random_buildings
-                = propertyNode->getBoolValue("/sim/rendering/random-buildings",
-                                             use_random_buildings);
-            building_density
-                = propertyNode->getFloatValue("/sim/rendering/building-density",
-                                              building_density);
-        }
-        
-        useVBOs = (_options->getPluginStringData("SimGear::USE_VBOS") == "ON");
-      }
-
-
-
-      if (matlib && (use_random_objects || use_random_buildings)) {
-        _tileGeometryBin->computeRandomObjectsAndBuildings(_matcache,
-                                                         building_density,
-                                                         use_random_objects,
-                                                         use_random_buildings,
-                                                         useVBOs);
-      }
-
-
-      if (_tileGeometryBin->randomModels.getNumModels() > 0) {
-        // Generate a repeatable random seed
-        mt seed;
-        mt_init(&seed, unsigned(123));
-
-        std::vector<ModelLOD> models;
-        for (unsigned int i = 0;
-             i < _tileGeometryBin->randomModels.getNumModels(); i++) {
-          SGMatModelBin::MatModel obj
-            = _tileGeometryBin->randomModels.getMatModel(i);
-
-          SGPropertyNode* root = _options->getPropertyNode()->getRootNode();
-          osg::Node* node = obj.model->get_random_model(root, &seed);
-
-          // Create a matrix to place the object in the correct
-          // location, and then apply the rotation matrix created
-          // above, with an additional random (or taken from
-          // the object mask) heading rotation if appropriate.
-          osg::Matrix transformMat;
-          transformMat = osg::Matrix::translate(toOsg(obj.position));
-          if (obj.model->get_heading_type() == SGMatModel::HEADING_RANDOM) {
-            // Rotate the object around the z axis.
-            double hdg = mt_rand(&seed) * M_PI * 2;
-            transformMat.preMult(osg::Matrix::rotate(hdg,
-                                                     osg::Vec3d(0.0, 0.0, 1.0)));
-          }
-
-          if (obj.model->get_heading_type() == SGMatModel::HEADING_MASK) {
-            // Rotate the object around the z axis.
-            double hdg =  - obj.rotation * M_PI * 2;
-            transformMat.preMult(osg::Matrix::rotate(hdg,
-                                                     osg::Vec3d(0.0, 0.0, 1.0)));
-          }
-
-          osg::MatrixTransform* position =
-            new osg::MatrixTransform(transformMat);
-          position->setName("positionRandomModel");
-          position->addChild(node);
-          models.push_back(ModelLOD(position, obj.lod));
-        }
-        RandomObjectsQuadtree quadtree((GetModelLODCoord()), (AddModelLOD()));
-        quadtree.buildQuadTree(models.begin(), models.end());
-        randomObjects = quadtree.getRoot();
-        randomObjects->setName("Random objects");
-      }
-
-      if (! _tileGeometryBin->randomBuildings.empty()) {
-        buildingNode = createRandomBuildings(_tileGeometryBin->randomBuildings, osg::Matrix::identity(),
-                                    _options);
-        buildingNode->setName("Random buildings");
-        _tileGeometryBin->randomBuildings.clear();
-      }
-
-      if (use_random_vegetation && matlib) {
-        // Now add some random forest.
-        _tileGeometryBin->computeRandomForest(_matcache, vegetation_density);
-
-        if (! _tileGeometryBin->randomForest.empty()) {
-          forestNode = createForest(_tileGeometryBin->randomForest, osg::Matrix::identity(),
-                                    _options);
-          forestNode->setName("Random trees");
-        }
-      }
-
-      osg::LOD* objectLOD = NULL;
-
-      if (randomObjects.valid() ||  forestNode.valid() || buildingNode.valid()) {
-        objectLOD = new osg::LOD;
-
-        if (randomObjects.valid()) objectLOD->addChild(randomObjects.get(), 0, 20000);
-        if (forestNode.valid())  objectLOD->addChild(forestNode.get(), 0, 20000);
-        if (buildingNode.valid()) objectLOD->addChild(buildingNode.get(), 0, 20000);
-
-        unsigned nodeMask = SG_NODEMASK_CASTSHADOW_BIT | SG_NODEMASK_RECEIVESHADOW_BIT | SG_NODEMASK_TERRAIN_BIT;
-        objectLOD->setNodeMask(nodeMask);
-      }
-
-      return objectLOD;
-    }
-
-    /// The original options to use for this bunch of models
-    osg::ref_ptr<SGReaderWriterOptions> _options;
-    osg::ref_ptr<SGMaterialCache> _matcache;
-    osg::ref_ptr<SGTileGeometryBin> _tileGeometryBin;
-    string _path;
-    bool _loadterrain;
-};
-
 osg::Node*
 SGLoadBTG(const std::string& path, const simgear::SGReaderWriterOptions* options)
 {
@@ -1382,9 +90,10 @@ SGLoadBTG(const std::string& path, const simgear::SGReaderWriterOptions* options
       normals[i] = hlOrf.transform(normals[i]);
     tile.set_normals(normals);
 
+    // tile surface    
     osg::ref_ptr<SGTileGeometryBin> tileGeometryBin = new SGTileGeometryBin();
 
-    if (!tileGeometryBin->insertBinObj(tile, matcache))
+    if (!tileGeometryBin->insertSurfaceGeometry(tile, matcache))
       return NULL;
 
     osg::Node* node = tileGeometryBin->getSurfaceGeometry(matcache, useVBOs);
@@ -1399,13 +108,17 @@ SGLoadBTG(const std::string& path, const simgear::SGReaderWriterOptions* options
     transform->setMatrix(osg::Matrix::rotate(toOsg(hlOr))*
                          osg::Matrix::translate(toOsg(center)));
 
-    // PagedLOD for the random objects so we don't need to generate
-    // them all on tile loading.
-    osg::PagedLOD* pagedLOD = new osg::PagedLOD;
-    pagedLOD->setCenterMode(osg::PagedLOD::USE_BOUNDING_SPHERE_CENTER);
-    pagedLOD->setName("pagedObjectLOD");
-
     if (node) {
+      // tile points
+      SGTileDetailsCallback* tileDetailsCallback = new SGTileDetailsCallback;
+      tileDetailsCallback->insertPtGeometry( tile, matcache );
+    
+      // PagedLOD for the random objects so we don't need to generate
+      // them all on tile loading.
+      osg::PagedLOD* pagedLOD = new osg::PagedLOD;
+      pagedLOD->setCenterMode(osg::PagedLOD::USE_BOUNDING_SPHERE_CENTER);
+      pagedLOD->setName("pagedObjectLOD");
+
       if (simplifyNear == simplifyDistant) {
         // Same terrain type is used for both near and far distances,
         // so add it to the main group.
@@ -1418,30 +131,31 @@ SGLoadBTG(const std::string& path, const simgear::SGReaderWriterOptions* options
         // call-back below will re-generate the closer version
         pagedLOD->addChild(node, object_range + SG_TILE_RADIUS, FLT_MAX);
       }
-    }
 
-    osg::ref_ptr<SGReaderWriterOptions> opt;
-    opt = SGReaderWriterOptions::copyOrCreate(options);
+      osg::ref_ptr<SGReaderWriterOptions> opt;
+      opt = SGReaderWriterOptions::copyOrCreate(options);
 
-    // we just need to know about the read file callback that itself holds the data
-    RandomObjectCallback* randomObjectCallback = new RandomObjectCallback;
-    randomObjectCallback->_options = opt;
-    randomObjectCallback->_tileGeometryBin = tileGeometryBin;
-    randomObjectCallback->_path = std::string(path);
-    randomObjectCallback->_loadterrain = ! (simplifyNear == simplifyDistant);
-    randomObjectCallback->_matcache = matcache;
+      // we just need to know about the read file callback that itself holds the data
+      tileDetailsCallback->_options = opt;
+      tileDetailsCallback->_path = std::string(path);
+      tileDetailsCallback->_loadterrain = ! (simplifyNear == simplifyDistant);
+      tileDetailsCallback->_gbs_center = center;
+      tileDetailsCallback->_rootNode = node;
+      tileDetailsCallback->_randomSurfaceLightsComputed = false;
+      tileDetailsCallback->_tileRandomObjectsComputed = false;
+    
+      osg::ref_ptr<osgDB::Options> callbackOptions = new osgDB::Options;
+      callbackOptions->setObjectCacheHint(osgDB::Options::CACHE_ALL);
+      callbackOptions->setReadFileCallback(tileDetailsCallback);
+      pagedLOD->setDatabaseOptions(callbackOptions.get());
 
-    osg::ref_ptr<osgDB::Options> callbackOptions = new osgDB::Options;
-    callbackOptions->setObjectCacheHint(osgDB::Options::CACHE_ALL);
-    callbackOptions->setReadFileCallback(randomObjectCallback);
-    pagedLOD->setDatabaseOptions(callbackOptions.get());
+      // Ensure that the random objects aren't expired too quickly
+      pagedLOD->setMinimumExpiryTime(pagedLOD->getNumChildren(), tile_min_expiry);
+      pagedLOD->setFileName(pagedLOD->getNumChildren(), "Dummy filename for random objects callback");
+      pagedLOD->setRange(pagedLOD->getNumChildren(), 0, object_range + SG_TILE_RADIUS);
+      transform->addChild(pagedLOD);
+    }
 
-    // Ensure that the random objects aren't expired too quickly
-    pagedLOD->setMinimumExpiryTime(pagedLOD->getNumChildren(), tile_min_expiry);
-    pagedLOD->setFileName(pagedLOD->getNumChildren(), "Dummy filename for random objects callback");
-    pagedLOD->setRange(pagedLOD->getNumChildren(), 0, object_range + SG_TILE_RADIUS);
-    transform->addChild(pagedLOD);
     transform->setNodeMask( ~simgear::MODELLIGHT_BIT );
-
     return transform;
 }