1 // tilemgr.cxx -- routines to handle dynamic management of scenery tiles
3 // Written by Curtis Olson, started January 1998.
5 // Copyright (C) 1997 Curtis L. Olson - http://www.flightgear.org/~curt
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.
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.
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.
31 #include <osgViewer/Viewer>
33 #include <simgear/constants.h>
34 #include <simgear/debug/logstream.hxx>
35 #include <simgear/structure/exception.hxx>
36 #include <simgear/scene/model/modellib.hxx>
37 #include <simgear/scene/tgdb/SGReaderWriterBTGOptions.hxx>
39 #include <Main/globals.hxx>
40 #include <Main/fg_props.hxx>
41 #include <Main/renderer.hxx>
42 #include <Main/viewer.hxx>
43 #include <Scripting/NasalSys.hxx>
45 #include "scenery.hxx"
46 #include "SceneryPager.hxx"
47 #include "tilemgr.hxx"
50 using flightgear::SceneryPager;
51 using simgear::SGModelLib;
52 using simgear::TileEntry;
53 using simgear::TileCache;
55 FGTileMgr::FGTileMgr():
62 FGTileMgr::~FGTileMgr() {
63 // remove all nodes we might have left behind
64 osg::Group* group = globals->get_scenery()->get_terrain_branch();
65 group->removeChildren(0, group->getNumChildren());
69 // Initialize the Tile Manager subsystem
70 int FGTileMgr::init() {
71 SG_LOG( SG_TERRAIN, SG_INFO, "Initializing Tile Manager subsystem." );
73 _options = new SGReaderWriterBTGOptions;
74 _options->setMatlib(globals->get_matlib());
75 _options->setUseRandomObjects(fgGetBool("/sim/rendering/random-objects", true));
76 _options->setUseRandomVegetation(fgGetBool("/sim/rendering/random-vegetation", true));
77 osgDB::FilePathList &fp = _options->getDatabasePathList();
78 const string_list &sc = globals->get_fg_scenery();
80 std::copy(sc.begin(), sc.end(), back_inserter(fp));
82 TileEntry::setModelLoadHelper(this);
88 previous_bucket.make_bad();
89 current_bucket.make_bad();
91 longitude = latitude = -1000.0;
96 // schedule a tile for loading
97 void FGTileMgr::sched_tile( const SGBucket& b, const bool is_inner_ring ) {
98 // see if tile already exists in the cache
99 TileEntry *t = tile_cache.get_tile( b );
102 // make space in the cache
103 SceneryPager* pager = FGScenery::getPagerSingleton();
104 while ( (int)tile_cache.get_size() > tile_cache.get_max_cache_size() ) {
105 long index = tile_cache.get_oldest_tile();
107 TileEntry *old = tile_cache.get_tile( index );
108 tile_cache.clear_entry( index );
109 osg::ref_ptr<osg::Object> subgraph = old->getNode();
110 old->removeFromSceneGraph();
112 // zeros out subgraph ref_ptr, so subgraph is owned by
113 // the pager and will be deleted in the pager thread.
114 pager->queueDeleteRequest(subgraph);
116 // nothing to free ?!? forge ahead
121 // create a new entry
122 TileEntry *e = new TileEntry( b );
124 // insert the tile into the cache
125 if ( tile_cache.insert_tile( e ) ) {
126 // update_queues will generate load request
128 // insert failed (cache full with no available entries to
129 // delete.) Try again later
132 // Attach to scene graph
133 e->addToSceneGraph(globals->get_scenery()->get_terrain_branch());
135 t->set_inner_ring( is_inner_ring );
140 // schedule a needed buckets for loading
141 void FGTileMgr::schedule_needed( double vis, const SGBucket& curr_bucket) {
142 // sanity check (unfortunately needed!)
143 if ( longitude < -180.0 || longitude > 180.0
144 || latitude < -90.0 || latitude > 90.0 )
146 SG_LOG( SG_TERRAIN, SG_ALERT,
147 "Attempting to schedule tiles for bogus lon and lat = ("
148 << longitude << "," << latitude << ")" );
150 SG_LOG( SG_TERRAIN, SG_ALERT,
151 "This is a FATAL error. Exiting!" );
155 SG_LOG( SG_TERRAIN, SG_INFO,
156 "scheduling needed tiles for " << longitude << " " << latitude );
158 // vis = fgGetDouble("/environment/visibility-m");
160 double tile_width = curr_bucket.get_width_m();
161 double tile_height = curr_bucket.get_height_m();
162 // cout << "tile width = " << tile_width << " tile_height = "
163 // << tile_height << endl;
165 xrange = (int)(vis / tile_width) + 1;
166 yrange = (int)(vis / tile_height) + 1;
167 if ( xrange < 1 ) { xrange = 1; }
168 if ( yrange < 1 ) { yrange = 1; }
170 // make the cache twice as large to avoid losing terrain when switching
171 // between aircraft and tower views
172 tile_cache.set_max_cache_size( (2*xrange + 2) * (2*yrange + 2) * 2 );
173 // cout << "xrange = " << xrange << " yrange = " << yrange << endl;
174 // cout << "max cache size = " << tile_cache.get_max_cache_size()
175 // << " current cache size = " << tile_cache.get_size() << endl;
177 // clear the inner ring flags so we can set them below. This
178 // prevents us from having "true" entries we aren't able to find
179 // to get rid of if we teleport a long ways away from the current
181 tile_cache.clear_inner_ring_flags();
185 // schedule center tile first so it can be loaded first
186 b = sgBucketOffset( longitude, latitude, 0, 0 );
187 sched_tile( b, true );
191 // schedule next ring of 8 tiles
192 for ( x = -1; x <= 1; ++x ) {
193 for ( y = -1; y <= 1; ++y ) {
194 if ( x != 0 || y != 0 ) {
195 b = sgBucketOffset( longitude, latitude, x, y );
196 sched_tile( b, true );
201 // schedule remaining tiles
202 for ( x = -xrange; x <= xrange; ++x ) {
203 for ( y = -yrange; y <= yrange; ++y ) {
204 if ( x < -1 || x > 1 || y < -1 || y > 1 ) {
205 SGBucket b = sgBucketOffset( longitude, latitude, x, y );
206 sched_tile( b, false );
213 void FGTileMgr::initialize_queue()
215 // First time through or we have teleported, initialize the
216 // system and load all relavant tiles
218 SG_LOG( SG_TERRAIN, SG_INFO, "Initialize_queue(): Updating Tile list for "
220 // cout << "tile cache size = " << tile_cache.get_size() << endl;
222 // wipe/initialize tile cache
223 // tile_cache.init();
224 previous_bucket.make_bad();
226 // build the local area list and schedule tiles for loading
228 // start with the center tile and work out in concentric
231 double visibility_meters = fgGetDouble("/environment/visibility-m");
232 schedule_needed(visibility_meters, current_bucket);
236 FGTileMgr::loadTileModel(const string& modelPath, bool cacheModel)
239 if (fgGetBool("/sim/paths/use-custom-scenery-data") == true) {
240 string_list sc = globals->get_fg_scenery();
242 for (string_list_iterator it = sc.begin(); it != sc.end(); ++it) {
244 tmpPath.append(modelPath);
245 if (tmpPath.exists()) {
251 fullPath.append(modelPath);
253 osg::Node* result = 0;
257 SGModelLib::loadModel(fullPath.str(), globals->get_props(),
258 new FGNasalModelData);
261 SGModelLib::loadPagedModel(modelPath, globals->get_props(),
262 new FGNasalModelData);
263 } catch (const sg_io_exception& exc) {
264 string m(exc.getMessage());
266 m += exc.getLocation().asString();
267 SG_LOG( SG_ALL, SG_ALERT, m );
268 } catch (const sg_exception& exc) { // XXX may be redundant
269 SG_LOG( SG_ALL, SG_ALERT, exc.getMessage());
274 // Helper class for STL fun
275 class TileLoad : public std::unary_function<TileCache::tile_map::value_type,
279 TileLoad(SceneryPager *pager, osg::FrameStamp* framestamp,
280 osg::Group* terrainBranch, osgDB::ReaderWriter::Options* options) :
281 _pager(pager), _framestamp(framestamp), _options(options) {}
283 TileLoad(const TileLoad& rhs) :
284 _pager(rhs._pager), _framestamp(rhs._framestamp),
285 _options(rhs._options) {}
287 void operator()(TileCache::tile_map::value_type& tilePair)
289 TileEntry* entry = tilePair.second;
290 if (entry->getNode()->getNumChildren() == 0) {
291 _pager->queueRequest(entry->tileFileName,
293 entry->get_inner_ring() ? 10.0f : 1.0f,
295 entry->getDatabaseRequest(),
300 SceneryPager* _pager;
301 osg::FrameStamp* _framestamp;
302 osgDB::ReaderWriter::Options* _options;
306 * Update the various queues maintained by the tilemagr (private
307 * internal function, do not call directly.)
309 void FGTileMgr::update_queues()
311 SceneryPager* pager = FGScenery::getPagerSingleton();
312 osg::FrameStamp* framestamp
313 = globals->get_renderer()->getViewer()->getFrameStamp();
314 tile_cache.set_current_time(framestamp->getReferenceTime());
315 for_each(tile_cache.begin(), tile_cache.end(),
318 globals->get_scenery()->get_terrain_branch(), _options.get()));
322 // given the current lon/lat (in degrees), fill in the array of local
323 // chunks. If the chunk isn't already in the cache, then read it from
325 int FGTileMgr::update( double visibility_meters )
327 SGVec3d viewPos = globals->get_current_view()->get_view_pos();
328 return update(SGGeod::fromCart(viewPos), visibility_meters);
331 int FGTileMgr::update( const SGGeod& location, double visibility_meters)
333 SG_LOG( SG_TERRAIN, SG_DEBUG, "FGTileMgr::update()" );
335 longitude = location.getLongitudeDeg();
336 latitude = location.getLatitudeDeg();
338 // SG_LOG( SG_TERRAIN, SG_DEBUG, "FGTileMgr::update() for "
339 // << longitude << " " << latatitude );
341 current_bucket.set_bucket( location );
342 // SG_LOG( SG_TERRAIN, SG_DEBUG, "Updating tile list for "
343 // << current_bucket );
344 fgSetInt( "/environment/current-tile-id", current_bucket.gen_index() );
346 // do tile load scheduling.
347 // Note that we need keep track of both viewer buckets and fdm buckets.
348 if ( state == Running ) {
349 SG_LOG( SG_TERRAIN, SG_DEBUG, "State == Running" );
350 if (current_bucket != previous_bucket) {
351 // We've moved to a new bucket, we need to schedule any
352 // needed tiles for loading.
353 SG_LOG( SG_TERRAIN, SG_INFO, "FGTileMgr::update()" );
354 schedule_needed(visibility_meters, current_bucket);
356 } else if ( state == Start || state == Inited ) {
357 SG_LOG( SG_TERRAIN, SG_INFO, "State == Start || Inited" );
358 // initialize_queue();
360 if (current_bucket != previous_bucket
361 && current_bucket.get_chunk_lon() != -1000) {
362 SG_LOG( SG_TERRAIN, SG_INFO, "FGTileMgr::update()" );
363 schedule_needed(visibility_meters, current_bucket);
370 previous_bucket = current_bucket;
375 void FGTileMgr::prep_ssg_nodes(float vis) {
377 // traverse the potentially viewable tile list and update range
378 // selector and transform
381 tile_cache.reset_traversal();
383 while ( ! tile_cache.at_end() ) {
384 // cout << "processing a tile" << endl;
385 if ( (e = tile_cache.get_current()) ) {
386 e->prep_ssg_node(vis);
388 SG_LOG(SG_INPUT, SG_ALERT, "warning ... empty tile in cache");
394 bool FGTileMgr::scenery_available(const SGGeod& position, double range_m)
396 // sanity check (unfortunately needed!)
397 if (position.getLongitudeDeg() < -180 || position.getLongitudeDeg() > 180 ||
398 position.getLatitudeDeg() < -90 || position.getLatitudeDeg() > 90)
401 SGBucket bucket(position);
402 TileEntry *te = tile_cache.get_tile(bucket);
403 if (!te || !te->is_loaded())
406 SGVec3d cartPos = SGVec3d::fromGeod(position);
408 // Traverse all tiles required to be there for the given visibility.
409 // This uses exactly the same algorithm like the tile scheduler.
410 double tile_width = bucket.get_width_m();
411 double tile_height = bucket.get_height_m();
412 double tile_r = 0.5*sqrt(tile_width*tile_width + tile_height*tile_height);
413 double max_dist = tile_r + range_m;
414 double max_dist2 = max_dist*max_dist;
416 int xrange = (int)fabs(range_m / tile_width) + 1;
417 int yrange = (int)fabs(range_m / tile_height) + 1;
419 for ( int x = -xrange; x <= xrange; ++x ) {
420 for ( int y = -yrange; y <= yrange; ++y ) {
421 // We have already checked for the center tile.
422 if ( x != 0 || y != 0 ) {
423 SGBucket b = sgBucketOffset( position.getLongitudeDeg(),
424 position.getLatitudeDeg(), x, y );
425 // Do not ask if it is just the next tile but way out of range.
426 if (max_dist2 < distSqr(cartPos, SGVec3d::fromGeod(b.get_center())))
428 TileEntry *te = tile_cache.get_tile(b);
429 if (!te || !te->is_loaded())
435 // Survived all tests.
441 struct IsTileLoaded :
442 public std::unary_function<TileCache::tile_map::value_type, bool>
444 bool operator()(const TileCache::tile_map::value_type& tilePair) const
446 return tilePair.second->is_loaded();
451 bool FGTileMgr::isSceneryLoaded()
453 return (std::find_if(tile_cache.begin(), tile_cache.end(),
454 std::not1(IsTileLoaded()))
455 == tile_cache.end());