]> git.mxchange.org Git - flightgear.git/blobdiff - src/Scenery/tilemgr.cxx
Fix issue reported by heap debugger
[flightgear.git] / src / Scenery / tilemgr.cxx
index ea10fd64699726e4065a926cf0d862211d3c6dce..6eeb706679077d70b36065859b277f8e9ecf7c13 100644 (file)
@@ -16,7 +16,7 @@
 //
 // You should have received a copy of the GNU General Public License
 // along with this program; if not, write to the Free Software
-// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 //
 // $Id$
 
 #  include <config.h>
 #endif
 
-#include <plib/ssg.h>
+#include <algorithm>
+#include <functional>
+
+#include <osgViewer/Viewer>
 
 #include <simgear/constants.h>
 #include <simgear/debug/logstream.hxx>
-#include <simgear/math/point3d.hxx>
-#include <simgear/math/polar3d.hxx>
-#include <simgear/math/sg_geodesy.hxx>
-#include <simgear/math/vector.hxx>
 #include <simgear/structure/exception.hxx>
 #include <simgear/scene/model/modellib.hxx>
+#include <simgear/scene/tgdb/SGReaderWriterBTGOptions.hxx>
+#include <simgear/scene/tsync/terrasync.hxx>
 
 #include <Main/globals.hxx>
 #include <Main/fg_props.hxx>
+#include <Main/renderer.hxx>
 #include <Main/viewer.hxx>
+#include <Scripting/NasalSys.hxx>
 
-#include "newcache.hxx"
 #include "scenery.hxx"
+#include "SceneryPager.hxx"
 #include "tilemgr.hxx"
 
-#define TEST_LAST_HIT_CACHE
+using std::for_each;
+using flightgear::SceneryPager;
+using simgear::SGModelLib;
+using simgear::TileEntry;
+using simgear::TileCache;
+
+
+// helper: listen to property changes affecting tile loading
+class LoaderPropertyWatcher : public SGPropertyChangeListener
+{
+public:
+    LoaderPropertyWatcher(FGTileMgr* pTileMgr) :
+        _pTileMgr(pTileMgr)
+    {
+    }
+
+    virtual void valueChanged(SGPropertyNode*)
+    {
+        _pTileMgr->configChanged();
+    }
 
-#if defined(ENABLE_THREADS) && ENABLE_THREADS
-SGLockedQueue<FGTileEntry *> FGTileMgr::attach_queue;
-SGLockedQueue<FGDeferredModel *> FGTileMgr::model_queue;
-#else
-queue<FGTileEntry *> FGTileMgr::attach_queue;
-queue<FGDeferredModel *> FGTileMgr::model_queue;
-#endif // ENABLE_THREADS
-queue<FGTileEntry *> FGTileMgr::delete_queue;
+private:
+    FGTileMgr* _pTileMgr;
+};
 
-bool FGTileMgr::tile_filter = true;
 
-// Constructor
 FGTileMgr::FGTileMgr():
     state( Start ),
-    current_tile( NULL ),
-    vis( 16000 )
+    vis( 16000 ),
+    _terra_sync(NULL),
+    _propListener(new LoaderPropertyWatcher(this))
 {
+    _randomObjects = fgGetNode("/sim/rendering/random-objects", true);
+    _randomVegetation = fgGetNode("/sim/rendering/random-vegetation", true);
+    _maxTileRangeM = fgGetNode("/sim/rendering/static-lod/bare", true);
 }
 
 
-// Destructor
-FGTileMgr::~FGTileMgr() {
+FGTileMgr::~FGTileMgr()
+{
+    // remove all nodes we might have left behind
+    osg::Group* group = globals->get_scenery()->get_terrain_branch();
+    group->removeChildren(0, group->getNumChildren());
+    delete _propListener;
+    _propListener = NULL;
+    // clear OSG cache
+    osgDB::Registry::instance()->clearObjectCache();
 }
 
 
 // Initialize the Tile Manager subsystem
-int FGTileMgr::init() {
+void FGTileMgr::init() {
     SG_LOG( SG_TERRAIN, SG_INFO, "Initializing Tile Manager subsystem." );
 
-    tile_cache.init();
+    _options = new SGReaderWriterBTGOptions;
+    _options->setMatlib(globals->get_matlib());
 
-#if 0
+    _randomObjects.get()->addChangeListener(_propListener, false);
+    _randomVegetation.get()->addChangeListener(_propListener, false);
+    configChanged();
 
-    // instead it's just a lot easier to let any pending work flush
-    // through, rather than trying to arrest the queue and nuke all
-    // the various work at all the various stages and get everything
-    // cleaned up properly.
+    osgDB::FilePathList &fp = _options->getDatabasePathList();
+    const string_list &sc = globals->get_fg_scenery();
+    fp.clear();
+    std::copy(sc.begin(), sc.end(), back_inserter(fp));
 
-    while ( ! attach_queue.empty() ) {
-        attach_queue.pop();
-    }
+    TileEntry::setModelLoadHelper(this);
+    
+    _visibilityMeters = fgGetNode("/environment/visibility-m", true);
 
-    while ( ! model_queue.empty() ) {
-#if defined(ENABLE_THREADS) && ENABLE_THREADS
-        FGDeferredModel* dm = model_queue.pop();
-#else
-        FGDeferredModel* dm = model_queue.front();
-        model_queue.pop();
-#endif
-        delete dm;
-    }
-    loader.reinit();
-#endif
+    reinit();
+}
 
-    hit_list.clear();
 
+void FGTileMgr::reinit()
+{
+    // remove all old scenery nodes from scenegraph and clear cache
+    osg::Group* group = globals->get_scenery()->get_terrain_branch();
+    group->removeChildren(0, group->getNumChildren());
+    tile_cache.init();
+    
+    // clear OSG cache, except on initial start-up
+    if (state != Start)
+    {
+        osgDB::Registry::instance()->clearObjectCache();
+    }
+    
     state = Inited;
-
+    
     previous_bucket.make_bad();
     current_bucket.make_bad();
-
     longitude = latitude = -1000.0;
 
-    return 1;
+    _terra_sync = (simgear::SGTerraSync*) globals->get_subsystem("terrasync");
+    if (_terra_sync)
+        _terra_sync->setTileCache(&tile_cache);
+
+    // force an update now
+    update(0.0);
 }
 
+void FGTileMgr::configChanged()
+{
+    _options->setUseRandomObjects(_randomObjects.get()->getBoolValue());
+    _options->setUseRandomVegetation(_randomVegetation.get()->getBoolValue());
+}
 
-// schedule a tile for loading
-void FGTileMgr::sched_tile( const SGBucket& b, const bool is_inner_ring ) {
+/* schedule a tile for loading, keep request for given amount of time.
+ * Returns true if tile is already loaded. */
+bool FGTileMgr::sched_tile( const SGBucket& b, double priority, bool current_view, double duration)
+{
     // see if tile already exists in the cache
-    FGTileEntry *t = tile_cache.get_tile( b );
-
-    if ( t == NULL ) {
-        // make space in the cache
-        while ( (int)tile_cache.get_size() > tile_cache.get_max_cache_size() ) {
-            long index = tile_cache.get_oldest_tile();
-            if ( index >= 0 ) {
-                FGTileEntry *old = tile_cache.get_tile( index );
-                old->disconnect_ssg_nodes();
-                delete_queue.push( old );
-                tile_cache.clear_entry( index );
-            } else {
-                // nothing to free ?!? forge ahead
-                break;
-            }
-        }
-
+    TileEntry *t = tile_cache.get_tile( b );
+    if (!t)
+    {
         // create a new entry
-        FGTileEntry *e = new FGTileEntry( b );
-
-        // insert the tile into the cache
-        if ( tile_cache.insert_tile( e ) ) {
-            // Schedule tile for loading
-            loader.add( e );
-        } else {
+        t = new TileEntry( b );
+        // insert the tile into the cache, update will generate load request
+        if ( tile_cache.insert_tile( t ) )
+        {
+            // Attach to scene graph
+            t->addToSceneGraph(globals->get_scenery()->get_terrain_branch());
+        } else
+        {
             // insert failed (cache full with no available entries to
             // delete.)  Try again later
-            delete e;
+            delete t;
+            return false;
         }
-    } else {
-        t->set_inner_ring( is_inner_ring );
+
+        SG_LOG( SG_TERRAIN, SG_DEBUG, "  New tile cache size " << (int)tile_cache.get_size() );
     }
-}
 
+    // update tile's properties
+    tile_cache.request_tile(t,priority,current_view,duration);
 
-// schedule a needed buckets for loading
-void FGTileMgr::schedule_needed( double vis, SGBucket curr_bucket) {
+    return t->is_loaded();
+}
+
+/* schedule needed buckets for the current view position for loading,
+ * keep request for given amount of time */
+void FGTileMgr::schedule_needed(const SGBucket& curr_bucket, double vis)
+{
     // sanity check (unfortunately needed!)
     if ( longitude < -180.0 || longitude > 180.0 
          || latitude < -90.0 || latitude > 90.0 )
@@ -160,384 +197,294 @@ void FGTileMgr::schedule_needed( double vis, SGBucket curr_bucket) {
         SG_LOG( SG_TERRAIN, SG_ALERT,
                 "Attempting to schedule tiles for bogus lon and lat  = ("
                 << longitude << "," << latitude << ")" );
-        SG_LOG( SG_TERRAIN, SG_ALERT,
-                "This is a FATAL error.  Exiting!" );
-        exit(-1);        
+        return;
     }
 
     SG_LOG( SG_TERRAIN, SG_INFO,
             "scheduling needed tiles for " << longitude << " " << latitude );
 
-    // vis = fgGetDouble("/environment/visibility-m");
-
     double tile_width = curr_bucket.get_width_m();
     double tile_height = curr_bucket.get_height_m();
     // cout << "tile width = " << tile_width << "  tile_height = "
     //      << tile_height << endl;
 
-    xrange = (int)(vis / tile_width) + 1;
-    yrange = (int)(vis / tile_height) + 1;
+    double tileRangeM = min(vis,_maxTileRangeM->getDoubleValue());
+    xrange = (int)(tileRangeM / tile_width) + 1;
+    yrange = (int)(tileRangeM / tile_height) + 1;
     if ( xrange < 1 ) { xrange = 1; }
     if ( yrange < 1 ) { yrange = 1; }
 
-    // note * 2 at end doubles cache size (for fdm and viewer)
+    // make the cache twice as large to avoid losing terrain when switching
+    // between aircraft and tower views
     tile_cache.set_max_cache_size( (2*xrange + 2) * (2*yrange + 2) * 2 );
     // cout << "xrange = " << xrange << "  yrange = " << yrange << endl;
     // cout << "max cache size = " << tile_cache.get_max_cache_size()
     //      << " current cache size = " << tile_cache.get_size() << endl;
 
-    // clear the inner ring flags so we can set them below.  This
-    // prevents us from having "true" entries we aren't able to find
-    // to get rid of if we teleport a long ways away from the current
-    // location.
-    tile_cache.clear_inner_ring_flags();
+    // clear flags of all tiles belonging to the previous view set 
+    tile_cache.clear_current_view();
 
-    SGBucket b;
+    // update timestamps, so all tiles scheduled now are *newer* than any tile previously loaded
+    osg::FrameStamp* framestamp
+            = globals->get_renderer()->getViewer()->getFrameStamp();
+    tile_cache.set_current_time(framestamp->getReferenceTime());
 
-    // schedule center tile first so it can be loaded first
-    b = sgBucketOffset( longitude, latitude, 0, 0 );
-    sched_tile( b, true );
+    SGBucket b;
 
     int x, y;
 
-    // schedule next ring of 8 tiles
-    for ( x = -1; x <= 1; ++x ) {
-        for ( y = -1; y <= 1; ++y ) {
-            if ( x != 0 || y != 0 ) {
-                b = sgBucketOffset( longitude, latitude, x, y );
-                sched_tile( b, true );
-            }
-        }
-    }
-
-    // schedule remaining tiles
-    for ( x = -xrange; x <= xrange; ++x ) {
-        for ( y = -yrange; y <= yrange; ++y ) {
-            if ( x < -1 || x > 1 || y < -1 || y > 1 ) {
-                SGBucket b = sgBucketOffset( longitude, latitude, x, y );
-                sched_tile( b, false );
-            }
+    /* schedule all tiles, use distance-based loading priority,
+     * so tiles are loaded in innermost-to-outermost sequence. */
+    for ( x = -xrange; x <= xrange; ++x )
+    {
+        for ( y = -yrange; y <= yrange; ++y )
+        {
+            SGBucket b = sgBucketOffset( longitude, latitude, x, y );
+            float priority = (-1.0) * (x*x+y*y);
+            sched_tile( b, priority, true, 0.0 );
         }
     }
 }
 
-
-void FGTileMgr::initialize_queue()
+osg::Node*
+FGTileMgr::loadTileModel(const string& modelPath, bool cacheModel)
 {
-    // First time through or we have teleported, initialize the
-    // system and load all relavant tiles
-
-    SG_LOG( SG_TERRAIN, SG_INFO, "Initialize_queue(): Updating Tile list for "
-            << current_bucket );
-    // cout << "tile cache size = " << tile_cache.get_size() << endl;
-
-    // wipe/initialize tile cache
-    // tile_cache.init();
-    previous_bucket.make_bad();
-
-    // build the local area list and schedule tiles for loading
-
-    // start with the center tile and work out in concentric
-    // "rings"
-
-    double visibility_meters = fgGetDouble("/environment/visibility-m");
-    schedule_needed(visibility_meters, current_bucket);
-
-    // do we really want to lose this? CLO
-#if 0
-    // Now force a load of the center tile and inner ring so we
-    // have something to see in our first frame.
-    int i;
-    for ( i = 0; i < 9; ++i ) {
-        if ( load_queue.size() ) {
-            SG_LOG( SG_TERRAIN, SG_DEBUG, 
-                    "Load queue not empty, loading a tile" );
-
-            SGBucket pending = load_queue.front();
-            load_queue.pop_front();
-            load_tile( pending );
+    SGPath fullPath;
+    if (fgGetBool("/sim/paths/use-custom-scenery-data") == true) {
+        string_list sc = globals->get_fg_scenery();
+
+        for (string_list_iterator it = sc.begin(); it != sc.end(); ++it) {
+            SGPath tmpPath(*it);
+            tmpPath.append(modelPath);
+            if (tmpPath.exists()) {
+                fullPath = tmpPath;
+                break;
+            } 
         }
+    } else {
+         fullPath.append(modelPath);
     }
-#endif
-}
-
-/**
- * return current status of queues
- *
- */
-
-bool FGTileMgr::all_queues_empty() {
-       return attach_queue.empty() && model_queue.empty();
+    osg::Node* result = 0;
+    try {
+        if(cacheModel)
+            result =
+                SGModelLib::loadModel(fullPath.str(), globals->get_props(),
+                                      new FGNasalModelData);
+        else
+            result=
+                SGModelLib::loadPagedModel(modelPath, globals->get_props(),
+                                           new FGNasalModelData);
+    } catch (const sg_io_exception& exc) {
+        string m(exc.getMessage());
+        m += " ";
+        m += exc.getLocation().asString();
+        SG_LOG( SG_ALL, SG_ALERT, m );
+    } catch (const sg_exception& exc) { // XXX may be redundant
+        SG_LOG( SG_ALL, SG_ALERT, exc.getMessage());
+    }
+    return result;
 }
 
-
 /**
  * Update the various queues maintained by the tilemagr (private
  * internal function, do not call directly.)
  */
 void FGTileMgr::update_queues()
 {
-    // load the next model in the load queue.  Currently this must
-    // happen in the render thread because model loading can trigger
-    // texture loading which involves use of the opengl api.  Skip any
-    // models belonging to not loaded tiles (i.e. the tile was removed
-    // before we were able to load some of the associated models.)
-    if ( !model_queue.empty() ) {
-        bool processed_one = false;
-
-        while ( model_queue.size() > 200 || processed_one == false ) {
-            processed_one = true;
-
-            if ( model_queue.size() > 200 ) {
-                SG_LOG( SG_TERRAIN, SG_INFO,
-                        "Alert: catching up on model load queue" );
-            }
-
-            // cout << "loading next model ..." << endl;
-            // load the next tile in the queue
-#if defined(ENABLE_THREADS) && ENABLE_THREADS
-            FGDeferredModel* dm = model_queue.pop();
-#else
-            FGDeferredModel* dm = model_queue.front();
-            model_queue.pop();
-#endif
+    SceneryPager* pager = FGScenery::getPagerSingleton();
+    osg::FrameStamp* framestamp
+        = globals->get_renderer()->getViewer()->getFrameStamp();
+    double current_time = framestamp->getReferenceTime();
+    double vis = _visibilityMeters->getDoubleValue();
+    TileEntry *e;
+    int loading=0;
+    int sz=0;
+
+    tile_cache.set_current_time( current_time );
+    tile_cache.reset_traversal();
 
-            // only load the model if the tile still exists in the
-            // tile cache
-            FGTileEntry *t = tile_cache.get_tile( dm->get_bucket() );
-            if ( t != NULL ) {
-                ssgTexturePath( (char *)(dm->get_texture_path().c_str()) );
-                try {
-                    ssgEntity *obj_model =
-                        globals->get_model_lib()->load_model( ".",
-                                                  dm->get_model_path(),
-                                                  globals->get_props(),
-                                                  globals->get_sim_time_sec() );
-                    if ( obj_model != NULL ) {
-                        dm->get_obj_trans()->addKid( obj_model );
-                    }
-                } catch (const sg_exception& exc) {
-                    SG_LOG( SG_ALL, SG_ALERT, exc.getMessage() );
-                }
-                
-                dm->get_tile()->dec_pending_models();
+    while ( ! tile_cache.at_end() )
+    {
+        e = tile_cache.get_current();
+        // cout << "processing a tile" << endl;
+        if ( e )
+        {
+            // Prepare the ssg nodes corresponding to each tile.
+            // Set the ssg transform and update it's range selector
+            // based on current visibilty
+            e->prep_ssg_node(vis);
+
+            if (( !e->is_loaded() )&&
+                ((!e->is_expired(current_time))||
+                  e->is_current_view() ))
+            {
+                // schedule tile for loading with osg pager
+                pager->queueRequest(e->tileFileName,
+                                    e->getNode(),
+                                    e->get_priority(),
+                                    framestamp,
+                                    e->getDatabaseRequest(),
+                                    _options.get());
+                loading++;
             }
-            delete dm;
+        } else
+        {
+            SG_LOG(SG_INPUT, SG_ALERT, "Warning: empty tile in cache!");
         }
+        tile_cache.next();
+        sz++;
     }
-    
-    // cout << "current elevation (ssg) == " << scenery.get_cur_elev() << endl;
-
-    // Notify the tile loader that it can load another tile
-    loader.update();
-
-    if ( !attach_queue.empty() ) {
-#if defined(ENABLE_THREADS) && ENABLE_THREADS
-        FGTileEntry* e = attach_queue.pop();
-#else
-        FGTileEntry* e = attach_queue.front();
-        attach_queue.pop();
-#endif
-        e->add_ssg_nodes( globals->get_scenery()->get_terrain_branch(),
-                          globals->get_scenery()->get_gnd_lights_root(),
-                          globals->get_scenery()->get_vasi_lights_root(),
-                          globals->get_scenery()->get_rwy_lights_root(),
-                          globals->get_scenery()->get_taxi_lights_root() );
-        // cout << "Adding ssg nodes for "
-    }
-
-    if ( !delete_queue.empty() ) {
-        // cout << "delete queue = " << delete_queue.size() << endl;
-        bool processed_one = false;
-
-        while ( delete_queue.size() > 30 || processed_one == false ) {
-            processed_one = true;
-
-            if ( delete_queue.size() > 30 ) {
-                // uh oh, delete queue is blowing up, we aren't clearing
-                // it fast enough.  Let's just panic, well not panic, but
-                // get real serious and agressively free up some tiles so
-                // we don't explode our memory usage.
 
-                SG_LOG( SG_TERRAIN, SG_ALERT,
-                        "Alert: catching up on tile delete queue" );
-            }
-
-            FGTileEntry* e = delete_queue.front();
-            if ( e->free_tile() ) {
-                delete_queue.pop();
-                delete e;
-            }
+    int drop_count = sz - tile_cache.get_max_cache_size();
+    if (( drop_count > 0 )&&
+         ((loading==0)||(drop_count > 10)))
+    {
+        long drop_index = tile_cache.get_drop_tile();
+        while ( drop_index > -1 )
+        {
+            // schedule tile for deletion with osg pager
+            TileEntry* old = tile_cache.get_tile(drop_index);
+            tile_cache.clear_entry(drop_index);
+            
+            osg::ref_ptr<osg::Object> subgraph = old->getNode();
+            old->removeFromSceneGraph();
+            delete old;
+            // zeros out subgraph ref_ptr, so subgraph is owned by
+            // the pager and will be deleted in the pager thread.
+            pager->queueDeleteRequest(subgraph);
+            
+            if (--drop_count > 0)
+                drop_index = tile_cache.get_drop_tile();
+            else
+                drop_index = -1;
         }
     }
 }
 
-
 // given the current lon/lat (in degrees), fill in the array of local
 // chunks.  If the chunk isn't already in the cache, then read it from
 // disk.
-int FGTileMgr::update( double visibility_meters )
+void FGTileMgr::update(double)
 {
-    SGLocation *location = globals->get_current_view()->getSGLocation();
-    sgdVec3 abs_pos_vector;
-    sgdCopyVec3( abs_pos_vector,
-                 globals->get_current_view()->get_absolute_view_pos() );
-    return update( location, visibility_meters, abs_pos_vector );
-}
+    SG_LOG( SG_TERRAIN, SG_DEBUG, "FGTileMgr::update()" );
+    SGVec3d viewPos = globals->get_current_view()->get_view_pos();
+    double vis = _visibilityMeters->getDoubleValue();
+    schedule_tiles_at(SGGeod::fromCart(viewPos), vis);
 
+    update_queues();
+}
 
-int FGTileMgr::update( SGLocation *location, double visibility_meters,
-                       sgdVec3 abs_pos_vector )
+// schedule tiles for the viewer bucket (FDM/AI/groundcache/... use
+// "schedule_scenery" instead
+int FGTileMgr::schedule_tiles_at(const SGGeod& location, double range_m)
 {
-    SG_LOG( SG_TERRAIN, SG_DEBUG, "FGTileMgr::update()" );
-
-    longitude = location->getLongitude_deg();
-    latitude = location->getLatitude_deg();
-    // add 1.0m to the max altitude to give a little leeway to the
-    // ground reaction code.
-    altitude_m = location->getAltitudeASL_ft() * SG_FEET_TO_METER + 1.0;
+    longitude = location.getLongitudeDeg();
+    latitude = location.getLatitudeDeg();
 
-    // if current altitude is apparently not initialized, set max
-    // altitude to something big.
-    if ( altitude_m < -1000 ) {
-        altitude_m = 10000;
-    }
     // SG_LOG( SG_TERRAIN, SG_DEBUG, "FGTileMgr::update() for "
     //         << longitude << " " << latatitude );
 
-    current_bucket.set_bucket( longitude, latitude );
+    current_bucket.set_bucket( location );
+
+    // schedule more tiles when visibility increased considerably
+    // TODO Calculate tile size - instead of using fixed value (5000m)
+    if (range_m-scheduled_visibility > 5000.0)
+        previous_bucket.make_bad();
+
     // SG_LOG( SG_TERRAIN, SG_DEBUG, "Updating tile list for "
     //         << current_bucket );
     fgSetInt( "/environment/current-tile-id", current_bucket.gen_index() );
 
-    // set global scenery center from current tile center
-    current_tile = tile_cache.get_tile( current_bucket );
-    if ( current_tile != NULL ) {
-        globals->get_scenery()->set_next_center( current_tile->center );
-    } else {
-        SG_LOG( SG_TERRAIN, SG_WARN, "Tile not found (Ok if initializing)" );
-        globals->get_scenery()->set_next_center( Point3D(0.0) );
-    }
-
-    // do tile load scheduling. 
+    // do tile load scheduling.
     // Note that we need keep track of both viewer buckets and fdm buckets.
     if ( state == Running ) {
         SG_LOG( SG_TERRAIN, SG_DEBUG, "State == Running" );
-        if (!(current_bucket == previous_bucket )) {
+        if (current_bucket != previous_bucket) {
             // We've moved to a new bucket, we need to schedule any
             // needed tiles for loading.
             SG_LOG( SG_TERRAIN, SG_INFO, "FGTileMgr::update()" );
-            schedule_needed(visibility_meters, current_bucket);
+            scheduled_visibility = range_m;
+            schedule_needed(current_bucket, range_m);
+            if (_terra_sync)
+                _terra_sync->schedulePosition(latitude,longitude);
         }
+        // save bucket
+        previous_bucket = current_bucket;
     } else if ( state == Start || state == Inited ) {
         SG_LOG( SG_TERRAIN, SG_INFO, "State == Start || Inited" );
-//        initialize_queue();
+        // do not update bucket yet (position not valid in initial loop)
         state = Running;
-
-        // load the next tile in the load queue (or authorize the next
-        // load in the case of the threaded tile pager)
-        loader.update();
+        previous_bucket.make_bad();
     }
 
-    update_queues();
-
-    // save bucket...
-    previous_bucket = current_bucket;
-
-    updateCurrentElevAtPos( abs_pos_vector, altitude_m,
-                            location->get_tile_center() );
-
     return 1;
 }
 
-
-// timer event driven call to scheduler for the purpose of refreshing the tile timestamps
-void FGTileMgr::refresh_view_timestamps() {
-    SG_LOG( SG_TERRAIN, SG_INFO,
-            "Refreshing timestamps for " << current_bucket.get_center_lon()
-            << " " << current_bucket.get_center_lat() );
-    if ( longitude >= -180.0 && longitude <= 180.0 
-         && latitude >= -90.0 && latitude <= 90.0 )
-    {
-        schedule_needed(fgGetDouble("/environment/visibility-m"), current_bucket);
-    }
-}
-
-
-int FGTileMgr::updateCurrentElevAtPos( sgdVec3 abs_pos_vector,
-                                       double max_alt_m,
-                                       Point3D center)
+/** Schedules scenery for given position. Load request remains valid for given duration
+ * (duration=0.0 => nothing is loaded).
+ * Used for FDM/AI/groundcache/... requests. Viewer uses "schedule_tiles_at" instead.
+ * Returns true when all tiles for the given position are already loaded, false otherwise.
+ */
+bool FGTileMgr::schedule_scenery(const SGGeod& position, double range_m, double duration)
 {
-    sgdVec3 sc;
-
-    sgdSetVec3( sc, center[0], center[1], center[2]);
-
-    // overridden with actual values if a terrain intersection is
-    // found
-    double hit_elev = -9999.0;
-    double hit_radius = 0.0;
-    sgdVec3 hit_normal = { 0.0, 0.0, 0.0 };
+    const float priority = 0.0;
+    double current_longitude = position.getLongitudeDeg();
+    double current_latitude = position.getLatitudeDeg();
+    bool available = true;
     
-    bool hit = false;
-    if ( fabs(sc[0]) > 1.0 || fabs(sc[1]) > 1.0 || fabs(sc[2]) > 1.0 ) {
-        // scenery center has been properly defined so any hit should
-        // be valid (and not just luck)
-        hit = fgCurrentElev(abs_pos_vector,
-                            max_alt_m,
-                            sc,
-               // uncomment next paramater to fly under
-               // bridges and a slightly faster algorithm
-               // but you won't be able to land on aircraft carriers
-                            // current_tile->get_terra_transform(),
-                            &hit_list,
-                            &hit_elev,
-                            &hit_radius,
-                            hit_normal);
-    }
-
-    if ( hit ) {
-        // cout << "elev = " << hit_elev << " " << hit_radius << endl;
-        globals->get_scenery()->set_cur_elev( hit_elev );
-        globals->get_scenery()->set_cur_radius( hit_radius );
-        globals->get_scenery()->set_cur_normal( hit_normal );
-    } else {
-        globals->get_scenery()->set_cur_elev( -9999.0 );
-        globals->get_scenery()->set_cur_radius( 0.0 );
-        globals->get_scenery()->set_cur_normal( hit_normal );
-    }
-    return hit;
-}
-
-
-void FGTileMgr::prep_ssg_nodes( SGLocation *location, float vis ) {
-
-    // traverse the potentially viewable tile list and update range
-    // selector and transform
-
-    Point3D center = location->get_tile_center();
-    float *up = location->get_world_up();
-
-    FGTileEntry *e;
-    tile_cache.reset_traversal();
+    // sanity check (unfortunately needed!)
+    if (current_longitude < -180 || current_longitude > 180 ||
+        current_latitude < -90 || current_latitude > 90)
+        return false;
+  
+    SGBucket bucket(position);
+    available = sched_tile( bucket, priority, false, duration );
+  
+    if ((!available)&&(duration==0.0))
+        return false;
+
+    SGVec3d cartPos = SGVec3d::fromGeod(position);
+
+    // Traverse all tiles required to be there for the given visibility.
+    double tile_width = bucket.get_width_m();
+    double tile_height = bucket.get_height_m();
+    double tile_r = 0.5*sqrt(tile_width*tile_width + tile_height*tile_height);
+    double max_dist = tile_r + range_m;
+    double max_dist2 = max_dist*max_dist;
+    
+    int xrange = (int)fabs(range_m / tile_width) + 1;
+    int yrange = (int)fabs(range_m / tile_height) + 1;
 
-    while ( ! tile_cache.at_end() ) {
-        // cout << "processing a tile" << endl;
-        if ( (e = tile_cache.get_current()) ) {
-            e->prep_ssg_node( center, up, vis);
-        } else {
-            SG_LOG(SG_INPUT, SG_ALERT, "warning ... empty tile in cache");
+    for ( int x = -xrange; x <= xrange; ++x )
+    {
+        for ( int y = -yrange; y <= yrange; ++y )
+        {
+            // We have already checked for the center tile.
+            if ( x != 0 || y != 0 )
+            {
+                SGBucket b = sgBucketOffset( current_longitude,
+                                             current_latitude, x, y );
+                double distance2 = distSqr(cartPos, SGVec3d::fromGeod(b.get_center()));
+                // Do not ask if it is just the next tile but way out of range.
+                if (distance2 <= max_dist2)
+                {
+                    available &= sched_tile( b, priority, false, duration );
+                    if ((!available)&&(duration==0.0))
+                        return false;
+                }
+            }
         }
-        tile_cache.next();
     }
-}
 
-bool FGTileMgr::set_tile_filter( bool f ) {
-    bool old = tile_filter;
-    tile_filter = f;
-    return old;
+    return available;
 }
 
-int FGTileMgr::tile_filter_cb( ssgEntity *, int )
+// Returns true if tiles around current view position have been loaded
+bool FGTileMgr::isSceneryLoaded()
 {
-  return tile_filter ? 1 : 0;
+    double range_m = 100.0;
+    if (scheduled_visibility < range_m)
+        range_m = scheduled_visibility;
+
+    return schedule_scenery(SGGeod::fromDeg(longitude, latitude), range_m, 0.0);
 }