]> git.mxchange.org Git - flightgear.git/blob - src/Scenery/tilemgr.cxx
a2498db97a74547111fed77551f2da26ffb992f3
[flightgear.git] / src / Scenery / tilemgr.cxx
1 // tilemgr.cxx -- routines to handle dynamic management of scenery tiles
2 //
3 // Written by Curtis Olson, started January 1998.
4 //
5 // Copyright (C) 1997  Curtis L. Olson  - http://www.flightgear.org/~curt
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22
23
24 #ifdef HAVE_CONFIG_H
25 #  include <config.h>
26 #endif
27
28 #include <algorithm>
29 #include <functional>
30
31 #include <boost/lexical_cast.hpp>
32
33 #include <osgViewer/Viewer>
34 #include <osgDB/Registry>
35
36 #include <simgear/constants.h>
37 #include <simgear/debug/logstream.hxx>
38 #include <simgear/structure/exception.hxx>
39 #include <simgear/scene/model/modellib.hxx>
40 #include <simgear/scene/util/SGReaderWriterOptions.hxx>
41 #include <simgear/scene/tsync/terrasync.hxx>
42 #include <simgear/misc/strutils.hxx>
43 #include <simgear/scene/material/matlib.hxx>
44
45 #include <Main/globals.hxx>
46 #include <Main/fg_props.hxx>
47 #include <Viewer/renderer.hxx>
48 #include <Viewer/splash.hxx>
49 #include <Scripting/NasalSys.hxx>
50 #include <Scripting/NasalModelData.hxx>
51
52 #include "scenery.hxx"
53 #include "SceneryPager.hxx"
54 #include "tilemgr.hxx"
55
56 using flightgear::SceneryPager;
57
58 class FGTileMgr::TileManagerListener : public SGPropertyChangeListener
59 {
60 public:
61     TileManagerListener(FGTileMgr* manager) :
62         _manager(manager),
63         _useVBOsProp(fgGetNode("/sim/rendering/use-vbos", true)),
64         _enableCacheProp(fgGetNode("/sim/tile-cache/enable", true)),
65         _pagedLODMaximumProp(fgGetNode("/sim/rendering/max-paged-lod", true))
66     {
67         _useVBOsProp->addChangeListener(this, true);
68       
69         _enableCacheProp->addChangeListener(this, true);
70         if (_enableCacheProp->getType() == simgear::props::NONE) {
71             _enableCacheProp->setBoolValue(true);
72         }
73       
74         if (_pagedLODMaximumProp->getType() == simgear::props::NONE) {
75             // not set, use OSG default / environment value variable
76             osg::ref_ptr<osgViewer::Viewer> viewer(globals->get_renderer()->getViewer());
77             int current = viewer->getDatabasePager()->getTargetMaximumNumberOfPageLOD();
78             _pagedLODMaximumProp->setIntValue(current);
79         }
80         _pagedLODMaximumProp->addChangeListener(this, true);
81     }
82     
83     ~TileManagerListener()
84     {
85         _useVBOsProp->removeChangeListener(this);
86         _enableCacheProp->removeChangeListener(this);
87         _pagedLODMaximumProp->removeChangeListener(this);
88     }
89     
90     virtual void valueChanged(SGPropertyNode* prop)
91     {
92         if (prop == _useVBOsProp) {
93             bool useVBOs = prop->getBoolValue();
94             _manager->_options->setPluginStringData("SimGear::USE_VBOS",
95                                                 useVBOs ? "ON" : "OFF");
96         } else if (prop == _enableCacheProp) {
97             _manager->_enableCache = prop->getBoolValue();
98         } else if (prop == _pagedLODMaximumProp) {
99           int v = prop->getIntValue();
100           osg::ref_ptr<osgViewer::Viewer> viewer(globals->get_renderer()->getViewer());
101           viewer->getDatabasePager()->setTargetMaximumNumberOfPageLOD(v);
102         }
103     }
104     
105 private:
106     FGTileMgr* _manager;
107     SGPropertyNode_ptr _useVBOsProp,
108       _enableCacheProp,
109       _pagedLODMaximumProp;
110 };
111
112 FGTileMgr::FGTileMgr():
113     state( Start ),
114     last_state( Running ),
115     scheduled_visibility(100.0),
116     _terra_sync(NULL),
117     _listener(NULL),
118     _visibilityMeters(fgGetNode("/environment/visibility-m", true)),
119     _maxTileRangeM(fgGetNode("/sim/rendering/static-lod/bare", true)),
120     _disableNasalHooks(fgGetNode("/sim/temp/disable-scenery-nasal", true)),
121     _scenery_loaded(fgGetNode("/sim/sceneryloaded", true)),
122     _scenery_override(fgGetNode("/sim/sceneryloaded-override", true)),
123     _pager(FGScenery::getPagerSingleton()),
124     _enableCache(true)
125 {
126 }
127
128
129 FGTileMgr::~FGTileMgr()
130 {
131    }
132
133
134 // Initialize the Tile Manager subsystem
135 void FGTileMgr::init()
136 {
137     reinit();
138 }
139
140 void FGTileMgr::shutdown()
141 {
142     delete _listener;
143     _listener = NULL;
144
145     FGScenery* scenery = globals->get_scenery();
146     if (scenery && scenery->get_terrain_branch()) {
147         osg::Group* group = scenery->get_terrain_branch();
148         group->removeChildren(0, group->getNumChildren());
149     }
150     // clear OSG cache
151     osgDB::Registry::instance()->clearObjectCache();
152     state = Start; // need to init again
153 }
154
155 void FGTileMgr::reinit()
156 {
157     SG_LOG( SG_TERRAIN, SG_INFO, "Initializing Tile Manager subsystem." );
158     _terra_sync = static_cast<simgear::SGTerraSync*> (globals->get_subsystem("terrasync"));
159
160   // drops the previous options reference
161     _options = new simgear::SGReaderWriterOptions;
162     _listener = new TileManagerListener(this);
163     
164     materialLibChanged();
165     _options->setPropertyNode(globals->get_props());
166     
167     osgDB::FilePathList &fp = _options->getDatabasePathList();
168     const PathList &sc = globals->get_fg_scenery();
169     fp.clear();
170     PathList::const_iterator it;
171     for (it = sc.begin(); it != sc.end(); ++it) {
172         fp.push_back(it->local8BitStr());
173     }
174     _options->setPluginStringData("SimGear::FG_ROOT", globals->get_fg_root().local8BitStr());
175     
176     if (_terra_sync) {
177       _options->setPluginStringData("SimGear::TERRASYNC_ROOT", fgGetString("/sim/terrasync/scenery-dir"));
178     }
179     
180     if (!_disableNasalHooks->getBoolValue())
181       _options->setModelData(new FGNasalModelDataProxy);
182
183     _options->setPluginStringData("SimGear::ROUGH_LOD_RANGE", fgGetString("/sim/rendering/static-lod/rough", boost::lexical_cast<string>(SG_OBJECT_RANGE)));
184   
185     if (state != Start)
186     {
187       // protect against multiple scenery reloads and properly reset flags,
188       // otherwise aircraft fall through the ground while reloading scenery
189       if (_scenery_loaded->getBoolValue() == false) {
190         SG_LOG( SG_TERRAIN, SG_INFO, "/sim/sceneryloaded already false, avoiding duplicate re-init of tile manager" );
191         return;
192       }
193     }
194   
195     _scenery_loaded->setBoolValue(false);
196     fgSetDouble("/sim/startup/splash-alpha", 1.0);
197     
198     materialLibChanged();
199
200     // remove all old scenery nodes from scenegraph and clear cache
201     osg::Group* group = globals->get_scenery()->get_terrain_branch();
202     group->removeChildren(0, group->getNumChildren());
203     tile_cache.init();
204     
205     // clear OSG cache, except on initial start-up
206     if (state != Start)
207     {
208         osgDB::Registry::instance()->clearObjectCache();
209     }
210     
211     state = Inited;
212     
213     previous_bucket.make_bad();
214     current_bucket.make_bad();
215     scheduled_visibility = 100.0;
216
217     // force an update now
218     update(0.0);
219 }
220
221 void FGTileMgr::materialLibChanged()
222 {
223     _options->setMaterialLib(globals->get_matlib());
224 }
225
226 /* schedule a tile for loading, keep request for given amount of time.
227  * Returns true if tile is already loaded. */
228 bool FGTileMgr::sched_tile( const SGBucket& b, double priority, bool current_view, double duration)
229 {
230     // see if tile already exists in the cache
231     TileEntry *t = tile_cache.get_tile( b );
232     if (!t)
233     {
234         // create a new entry
235         t = new TileEntry( b );
236         SG_LOG( SG_TERRAIN, SG_INFO, "sched_tile: new tile entry for:" << b );
237
238
239         // insert the tile into the cache, update will generate load request
240         if ( tile_cache.insert_tile( t ) )
241         {
242             // Attach to scene graph
243
244             t->addToSceneGraph(globals->get_scenery()->get_terrain_branch());
245         } else
246         {
247             // insert failed (cache full with no available entries to
248             // delete.)  Try again later
249             delete t;
250             return false;
251         }
252
253         SG_LOG( SG_TERRAIN, SG_DEBUG, "  New tile cache size " << (int)tile_cache.get_size() );
254     }
255
256     // update tile's properties
257     tile_cache.request_tile(t,priority,current_view,duration);
258
259     return t->is_loaded();
260 }
261
262 /* schedule needed buckets for the current view position for loading,
263  * keep request for given amount of time */
264 void FGTileMgr::schedule_needed(const SGBucket& curr_bucket, double vis)
265 {
266     // sanity check (unfortunately needed!)
267     if (!curr_bucket.isValid() )
268     {
269         SG_LOG( SG_TERRAIN, SG_ALERT,
270                 "Attempting to schedule tiles for invalid bucket" );
271         return;
272     }
273
274     double tile_width = curr_bucket.get_width_m();
275     double tile_height = curr_bucket.get_height_m();
276     SG_LOG( SG_TERRAIN, SG_INFO,
277             "scheduling needed tiles for " << curr_bucket
278            << ", tile-width-m:" << tile_width << ", tile-height-m:" << tile_height);
279
280     
281     // cout << "tile width = " << tile_width << "  tile_height = "
282     //      << tile_height << endl;
283
284     double tileRangeM = std::min(vis,_maxTileRangeM->getDoubleValue());
285     int xrange = (int)(tileRangeM / tile_width) + 1;
286     int yrange = (int)(tileRangeM / tile_height) + 1;
287     if ( xrange < 1 ) { xrange = 1; }
288     if ( yrange < 1 ) { yrange = 1; }
289
290     // make the cache twice as large to avoid losing terrain when switching
291     // between aircraft and tower views
292     tile_cache.set_max_cache_size( (2*xrange + 2) * (2*yrange + 2) * 2 );
293     // cout << "xrange = " << xrange << "  yrange = " << yrange << endl;
294     // cout << "max cache size = " << tile_cache.get_max_cache_size()
295     //      << " current cache size = " << tile_cache.get_size() << endl;
296
297     // clear flags of all tiles belonging to the previous view set 
298     tile_cache.clear_current_view();
299
300     // update timestamps, so all tiles scheduled now are *newer* than any tile previously loaded
301     osg::FrameStamp* framestamp
302             = globals->get_renderer()->getViewer()->getFrameStamp();
303     tile_cache.set_current_time(framestamp->getReferenceTime());
304
305     SGBucket b;
306
307     int x, y;
308
309     /* schedule all tiles, use distance-based loading priority,
310      * so tiles are loaded in innermost-to-outermost sequence. */
311     for ( x = -xrange; x <= xrange; ++x )
312     {
313         for ( y = -yrange; y <= yrange; ++y )
314         {
315             SGBucket b = curr_bucket.sibling(x, y);
316             if (!b.isValid()) {
317                 continue;
318             }
319             
320             float priority = (-1.0) * (x*x+y*y);
321             sched_tile( b, priority, true, 0.0 );
322             
323             if (_terra_sync) {
324                 _terra_sync->scheduleTile(b);
325             }
326         }
327     }
328 }
329
330 /**
331  * Update the various queues maintained by the tilemgr (private
332  * internal function, do not call directly.)
333  */
334 void FGTileMgr::update_queues(bool& isDownloadingScenery)
335 {
336     osg::FrameStamp* framestamp
337         = globals->get_renderer()->getViewer()->getFrameStamp();
338     double current_time = framestamp->getReferenceTime();
339     double vis = _visibilityMeters->getDoubleValue();
340     TileEntry *e;
341     int loading=0;
342     int sz=0;
343     
344     tile_cache.set_current_time( current_time );
345     tile_cache.reset_traversal();
346
347     while ( ! tile_cache.at_end() )
348     {
349         e = tile_cache.get_current();
350         if ( e )
351         {
352             // Prepare the ssg nodes corresponding to each tile.
353             // Set the ssg transform and update it's range selector
354             // based on current visibilty
355             e->prep_ssg_node(vis);
356             
357             if (!e->is_loaded()) {
358                 bool nonExpiredOrCurrent = !e->is_expired(current_time) || e->is_current_view();
359                 bool downloading = isTileDirSyncing(e->tileFileName);
360                 isDownloadingScenery |= downloading;
361                 if ( !downloading && nonExpiredOrCurrent) {
362                     // schedule tile for loading with osg pager
363                     _pager->queueRequest(e->tileFileName,
364                                          e->getNode(),
365                                          e->get_priority(),
366                                          framestamp,
367                                          e->getDatabaseRequest(),
368                                          _options.get());
369                     loading++;
370                 }
371             } // of tile not loaded case
372         } else {
373             SG_LOG(SG_TERRAIN, SG_ALERT, "Warning: empty tile in cache!");
374         }
375         tile_cache.next();
376         sz++;
377     }
378
379     int drop_count = sz - tile_cache.get_max_cache_size();
380     bool dropTiles = false;
381     if (_enableCache) {
382       dropTiles = ( drop_count > 0 ) && ((loading==0)||(drop_count > 10));
383     } else {
384       dropTiles = true;
385       drop_count = sz; // no limit on tiles to drop
386     }
387   
388     if (dropTiles)
389     {
390         long drop_index = _enableCache ? tile_cache.get_drop_tile() :
391                                          tile_cache.get_first_expired_tile();
392         while ( drop_index > -1 )
393         {
394             // schedule tile for deletion with osg pager
395             TileEntry* old = tile_cache.get_tile(drop_index);
396             SG_LOG(SG_TERRAIN, SG_DEBUG, "Dropping:" << old->get_tile_bucket());
397
398             tile_cache.clear_entry(drop_index);
399             
400             osg::ref_ptr<osg::Object> subgraph = old->getNode();
401             old->removeFromSceneGraph();
402             delete old;
403             // zeros out subgraph ref_ptr, so subgraph is owned by
404             // the pager and will be deleted in the pager thread.
405             _pager->queueDeleteRequest(subgraph);
406           
407             if (!_enableCache)
408                 drop_index = tile_cache.get_first_expired_tile();
409             // limit tiles dropped to drop_count
410             else if (--drop_count > 0)
411                 drop_index = tile_cache.get_drop_tile();
412             else
413                drop_index = -1;
414         }
415     } // of dropping tiles loop
416 }
417
418 // given the current lon/lat (in degrees), fill in the array of local
419 // chunks.  If the chunk isn't already in the cache, then read it from
420 // disk.
421 void FGTileMgr::update(double)
422 {
423     double vis = _visibilityMeters->getDoubleValue();
424     schedule_tiles_at(globals->get_view_position(), vis);
425
426     bool waitingOnTerrasync = false;
427     update_queues(waitingOnTerrasync);
428
429     // scenery loading check, triggers after each sim (tile manager) reinit
430     if (!_scenery_loaded->getBoolValue())
431     {
432         bool fdmInited = fgGetBool("sim/fdm-initialized");
433         bool positionFinalized = fgGetBool("sim/position-finalized");
434         bool sceneryOverride = _scenery_override->getBoolValue();
435         
436         
437     // we are done if final position is set and the scenery & FDM are done.
438     // scenery-override can ignore the last two, but not position finalization.
439         if (positionFinalized && (sceneryOverride || (isSceneryLoaded() && fdmInited)))
440         {
441             _scenery_loaded->setBoolValue(true);
442             fgSplashProgress("");
443         }
444         else
445         {
446             if (!positionFinalized) {
447                 fgSplashProgress("finalize-position");
448             } else if (waitingOnTerrasync) {
449                 fgSplashProgress("downloading-scenery");
450             } else {
451                 fgSplashProgress("loading-scenery");
452             }
453             
454             // be nice to loader threads while waiting for initial scenery, reduce to 20fps
455             SGTimeStamp::sleepForMSec(50);
456         }
457     }
458 }
459
460 // schedule tiles for the viewer bucket
461 // (FDM/AI/groundcache/... should use "schedule_scenery" instead)
462 void FGTileMgr::schedule_tiles_at(const SGGeod& location, double range_m)
463 {
464     // SG_LOG( SG_TERRAIN, SG_DEBUG, "FGTileMgr::update() for "
465     //         << longitude << " " << latitude );
466
467     current_bucket = SGBucket( location );
468
469     // schedule more tiles when visibility increased considerably
470     // TODO Calculate tile size - instead of using fixed value (5000m)
471     if (range_m - scheduled_visibility > 5000.0)
472         previous_bucket.make_bad();
473
474     // SG_LOG( SG_TERRAIN, SG_DEBUG, "Updating tile list for "
475     //         << current_bucket );
476     fgSetInt( "/environment/current-tile-id", current_bucket.gen_index() );
477
478     // do tile load scheduling.
479     // Note that we need keep track of both viewer buckets and fdm buckets.
480     if ( state == Running ) {
481         if (last_state != state)
482         {
483             SG_LOG( SG_TERRAIN, SG_DEBUG, "State == Running" );
484         }
485         if (current_bucket != previous_bucket) {
486             // We've moved to a new bucket, we need to schedule any
487             // needed tiles for loading.
488             SG_LOG( SG_TERRAIN, SG_INFO, "FGTileMgr: at " << location << ", scheduling needed for:" << current_bucket
489                    << ", visibility=" << range_m);
490             scheduled_visibility = range_m;
491             schedule_needed(current_bucket, range_m);
492         }
493         
494         // save bucket
495         previous_bucket = current_bucket;
496     } else if ( state == Start || state == Inited ) {
497         SG_LOG( SG_TERRAIN, SG_DEBUG, "State == Start || Inited" );
498         // do not update bucket yet (position not valid in initial loop)
499         state = Running;
500         previous_bucket.make_bad();
501     }
502     last_state = state;
503 }
504
505 /** Schedules scenery for given position. Load request remains valid for given duration
506  * (duration=0.0 => nothing is loaded).
507  * Used for FDM/AI/groundcache/... requests. Viewer uses "schedule_tiles_at" instead.
508  * Returns true when all tiles for the given position are already loaded, false otherwise.
509  */
510 bool FGTileMgr::schedule_scenery(const SGGeod& position, double range_m, double duration)
511 {
512     // sanity check (unfortunately needed!)
513     if (!position.isValid())
514         return false;
515     const float priority = 0.0;
516     bool available = true;
517
518     SGBucket bucket(position);
519     available = sched_tile( bucket, priority, false, duration );
520   
521     if ((!available)&&(duration==0.0)) {
522         SG_LOG( SG_TERRAIN, SG_DEBUG, "schedule_scenery: Scheduling tile at bucket:" << bucket << " return false" );
523         return false;
524     }
525
526     SGVec3d cartPos = SGVec3d::fromGeod(position);
527
528     // Traverse all tiles required to be there for the given visibility.
529     double tile_width = bucket.get_width_m();
530     double tile_height = bucket.get_height_m();
531     double tile_r = 0.5*sqrt(tile_width*tile_width + tile_height*tile_height);
532     double max_dist = tile_r + range_m;
533     double max_dist2 = max_dist*max_dist;
534     
535     int xrange = (int)fabs(range_m / tile_width) + 1;
536     int yrange = (int)fabs(range_m / tile_height) + 1;
537
538     for ( int x = -xrange; x <= xrange; ++x )
539     {
540         for ( int y = -yrange; y <= yrange; ++y )
541         {
542             // We have already checked for the center tile.
543             if ( x != 0 || y != 0 )
544             {
545                 SGBucket b = bucket.sibling(x, y );
546                 if (!b.isValid()) {
547                     continue;
548                 }
549                 
550                 double distance2 = distSqr(cartPos, SGVec3d::fromGeod(b.get_center()));
551                 // Do not ask if it is just the next tile but way out of range.
552                 if (distance2 <= max_dist2)
553                 {
554                     available &= sched_tile( b, priority, false, duration );
555                     if ((!available)&&(duration==0.0))
556                         return false;
557                 }
558             }
559         }
560     }
561
562     return available;
563 }
564
565 // Returns true if tiles around current view position have been loaded
566 bool FGTileMgr::isSceneryLoaded()
567 {
568     double range_m = 100.0;
569     if (scheduled_visibility < range_m)
570         range_m = scheduled_visibility;
571
572     return schedule_scenery(globals->get_view_position(), range_m, 0.0);
573 }
574
575 bool FGTileMgr::isTileDirSyncing(const std::string& tileFileName) const
576 {
577     if (!_terra_sync) {
578         return false;
579     }
580     
581     std::string nameWithoutExtension = tileFileName.substr(0, tileFileName.size() - 4);
582     long int bucketIndex = simgear::strutils::to_int(nameWithoutExtension);
583     SGBucket bucket(bucketIndex);
584     
585     return _terra_sync->isTileDirPending(bucket.gen_base_path());
586 }
587