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/math/vector.hxx>
36 #include <simgear/structure/exception.hxx>
37 #include <simgear/scene/model/modellib.hxx>
38 #include <simgear/scene/tgdb/SGReaderWriterBTGOptions.hxx>
40 #include <Main/globals.hxx>
41 #include <Main/fg_props.hxx>
42 #include <Main/renderer.hxx>
43 #include <Main/viewer.hxx>
44 #include <Scripting/NasalSys.hxx>
46 #include "scenery.hxx"
47 #include "SceneryPager.hxx"
48 #include "tilemgr.hxx"
51 using flightgear::SceneryPager;
52 using simgear::SGModelLib;
53 using simgear::TileEntry;
54 using simgear::TileCache;
56 FGTileMgr::FGTileMgr():
63 FGTileMgr::~FGTileMgr() {
64 // remove all nodes we might have left behind
65 osg::Group* group = globals->get_scenery()->get_terrain_branch();
66 group->removeChildren(0, group->getNumChildren());
70 // Initialize the Tile Manager subsystem
71 int FGTileMgr::init() {
72 SG_LOG( SG_TERRAIN, SG_INFO, "Initializing Tile Manager subsystem." );
74 _options = new SGReaderWriterBTGOptions;
75 _options->setMatlib(globals->get_matlib());
76 _options->setUseRandomObjects(fgGetBool("/sim/rendering/random-objects", true));
77 _options->setUseRandomVegetation(fgGetBool("/sim/rendering/random-vegetation", true));
78 osgDB::FilePathList &fp = _options->getDatabasePathList();
79 const string_list &sc = globals->get_fg_scenery();
81 std::copy(sc.begin(), sc.end(), back_inserter(fp));
83 TileEntry::setModelLoadHelper(this);
89 previous_bucket.make_bad();
90 current_bucket.make_bad();
92 longitude = latitude = -1000.0;
97 // schedule a tile for loading
98 void FGTileMgr::sched_tile( const SGBucket& b, const bool is_inner_ring ) {
99 // see if tile already exists in the cache
100 TileEntry *t = tile_cache.get_tile( b );
103 // make space in the cache
104 SceneryPager* pager = FGScenery::getPagerSingleton();
105 while ( (int)tile_cache.get_size() > tile_cache.get_max_cache_size() ) {
106 long index = tile_cache.get_oldest_tile();
108 TileEntry *old = tile_cache.get_tile( index );
109 tile_cache.clear_entry( index );
110 osg::ref_ptr<osg::Object> subgraph = old->getNode();
111 old->removeFromSceneGraph();
113 // zeros out subgraph ref_ptr, so subgraph is owned by
114 // the pager and will be deleted in the pager thread.
115 pager->queueDeleteRequest(subgraph);
117 // nothing to free ?!? forge ahead
122 // create a new entry
123 TileEntry *e = new TileEntry( b );
125 // insert the tile into the cache
126 if ( tile_cache.insert_tile( e ) ) {
127 // update_queues will generate load request
129 // insert failed (cache full with no available entries to
130 // delete.) Try again later
133 // Attach to scene graph
134 e->addToSceneGraph(globals->get_scenery()->get_terrain_branch());
136 t->set_inner_ring( is_inner_ring );
141 // schedule a needed buckets for loading
142 void FGTileMgr::schedule_needed( double vis, const SGBucket& curr_bucket) {
143 // sanity check (unfortunately needed!)
144 if ( longitude < -180.0 || longitude > 180.0
145 || latitude < -90.0 || latitude > 90.0 )
147 SG_LOG( SG_TERRAIN, SG_ALERT,
148 "Attempting to schedule tiles for bogus lon and lat = ("
149 << longitude << "," << latitude << ")" );
151 SG_LOG( SG_TERRAIN, SG_ALERT,
152 "This is a FATAL error. Exiting!" );
156 SG_LOG( SG_TERRAIN, SG_INFO,
157 "scheduling needed tiles for " << longitude << " " << latitude );
159 // vis = fgGetDouble("/environment/visibility-m");
161 double tile_width = curr_bucket.get_width_m();
162 double tile_height = curr_bucket.get_height_m();
163 // cout << "tile width = " << tile_width << " tile_height = "
164 // << tile_height << endl;
166 xrange = (int)(vis / tile_width) + 1;
167 yrange = (int)(vis / tile_height) + 1;
168 if ( xrange < 1 ) { xrange = 1; }
169 if ( yrange < 1 ) { yrange = 1; }
171 // make the cache twice as large to avoid losing terrain when switching
172 // between aircraft and tower views
173 tile_cache.set_max_cache_size( (2*xrange + 2) * (2*yrange + 2) * 2 );
174 // cout << "xrange = " << xrange << " yrange = " << yrange << endl;
175 // cout << "max cache size = " << tile_cache.get_max_cache_size()
176 // << " current cache size = " << tile_cache.get_size() << endl;
178 // clear the inner ring flags so we can set them below. This
179 // prevents us from having "true" entries we aren't able to find
180 // to get rid of if we teleport a long ways away from the current
182 tile_cache.clear_inner_ring_flags();
186 // schedule center tile first so it can be loaded first
187 b = sgBucketOffset( longitude, latitude, 0, 0 );
188 sched_tile( b, true );
192 // schedule next ring of 8 tiles
193 for ( x = -1; x <= 1; ++x ) {
194 for ( y = -1; y <= 1; ++y ) {
195 if ( x != 0 || y != 0 ) {
196 b = sgBucketOffset( longitude, latitude, x, y );
197 sched_tile( b, true );
202 // schedule remaining tiles
203 for ( x = -xrange; x <= xrange; ++x ) {
204 for ( y = -yrange; y <= yrange; ++y ) {
205 if ( x < -1 || x > 1 || y < -1 || y > 1 ) {
206 SGBucket b = sgBucketOffset( longitude, latitude, x, y );
207 sched_tile( b, false );
214 void FGTileMgr::initialize_queue()
216 // First time through or we have teleported, initialize the
217 // system and load all relavant tiles
219 SG_LOG( SG_TERRAIN, SG_INFO, "Initialize_queue(): Updating Tile list for "
221 // cout << "tile cache size = " << tile_cache.get_size() << endl;
223 // wipe/initialize tile cache
224 // tile_cache.init();
225 previous_bucket.make_bad();
227 // build the local area list and schedule tiles for loading
229 // start with the center tile and work out in concentric
232 double visibility_meters = fgGetDouble("/environment/visibility-m");
233 schedule_needed(visibility_meters, current_bucket);
237 FGTileMgr::loadTileModel(const string& modelPath, bool cacheModel)
239 osg::Node* result = 0;
243 SGModelLib::loadModel(modelPath, globals->get_props(),
244 new FGNasalModelData);
248 SGModelLib::loadPagedModel(modelPath, globals->get_props(),
249 new FGNasalModelData);
250 } catch (const sg_io_exception& exc) {
251 string m(exc.getMessage());
253 m += exc.getLocation().asString();
254 SG_LOG( SG_ALL, SG_ALERT, m );
255 } catch (const sg_exception& exc) { // XXX may be redundant
256 SG_LOG( SG_ALL, SG_ALERT, exc.getMessage());
261 // Helper class for STL fun
262 class TileLoad : public std::unary_function<TileCache::tile_map::value_type,
266 TileLoad(SceneryPager *pager, osg::FrameStamp* framestamp,
267 osg::Group* terrainBranch, osgDB::ReaderWriter::Options* options) :
268 _pager(pager), _framestamp(framestamp), _options(options) {}
270 TileLoad(const TileLoad& rhs) :
271 _pager(rhs._pager), _framestamp(rhs._framestamp),
272 _options(rhs._options) {}
274 void operator()(TileCache::tile_map::value_type& tilePair)
276 TileEntry* entry = tilePair.second;
277 if (entry->getNode()->getNumChildren() == 0) {
278 _pager->queueRequest(entry->tileFileName,
280 entry->get_inner_ring() ? 10.0f : 1.0f,
282 entry->getDatabaseRequest(),
287 SceneryPager* _pager;
288 osg::FrameStamp* _framestamp;
289 osgDB::ReaderWriter::Options* _options;
293 * Update the various queues maintained by the tilemagr (private
294 * internal function, do not call directly.)
296 void FGTileMgr::update_queues()
298 SceneryPager* pager = FGScenery::getPagerSingleton();
299 osg::FrameStamp* framestamp
300 = globals->get_renderer()->getViewer()->getFrameStamp();
301 tile_cache.set_current_time(framestamp->getReferenceTime());
302 for_each(tile_cache.begin(), tile_cache.end(),
305 globals->get_scenery()->get_terrain_branch(), _options.get()));
309 // given the current lon/lat (in degrees), fill in the array of local
310 // chunks. If the chunk isn't already in the cache, then read it from
312 int FGTileMgr::update( double visibility_meters )
314 SGVec3d viewPos = globals->get_current_view()->get_view_pos();
315 return update(SGGeod::fromCart(viewPos), visibility_meters);
318 int FGTileMgr::update( const SGGeod& location, double visibility_meters)
320 SG_LOG( SG_TERRAIN, SG_DEBUG, "FGTileMgr::update()" );
322 longitude = location.getLongitudeDeg();
323 latitude = location.getLatitudeDeg();
325 // SG_LOG( SG_TERRAIN, SG_DEBUG, "FGTileMgr::update() for "
326 // << longitude << " " << latatitude );
328 current_bucket.set_bucket( location );
329 // SG_LOG( SG_TERRAIN, SG_DEBUG, "Updating tile list for "
330 // << current_bucket );
331 fgSetInt( "/environment/current-tile-id", current_bucket.gen_index() );
333 // do tile load scheduling.
334 // Note that we need keep track of both viewer buckets and fdm buckets.
335 if ( state == Running ) {
336 SG_LOG( SG_TERRAIN, SG_DEBUG, "State == Running" );
337 if (current_bucket != previous_bucket) {
338 // We've moved to a new bucket, we need to schedule any
339 // needed tiles for loading.
340 SG_LOG( SG_TERRAIN, SG_INFO, "FGTileMgr::update()" );
341 schedule_needed(visibility_meters, current_bucket);
343 } else if ( state == Start || state == Inited ) {
344 SG_LOG( SG_TERRAIN, SG_INFO, "State == Start || Inited" );
345 // initialize_queue();
347 if (current_bucket != previous_bucket
348 && current_bucket.get_chunk_lon() != -1000) {
349 SG_LOG( SG_TERRAIN, SG_INFO, "FGTileMgr::update()" );
350 schedule_needed(visibility_meters, current_bucket);
357 previous_bucket = current_bucket;
362 void FGTileMgr::prep_ssg_nodes(float vis) {
364 // traverse the potentially viewable tile list and update range
365 // selector and transform
368 tile_cache.reset_traversal();
370 while ( ! tile_cache.at_end() ) {
371 // cout << "processing a tile" << endl;
372 if ( (e = tile_cache.get_current()) ) {
373 e->prep_ssg_node(vis);
375 SG_LOG(SG_INPUT, SG_ALERT, "warning ... empty tile in cache");
381 bool FGTileMgr::scenery_available(const SGGeod& position, double range_m)
383 // sanity check (unfortunately needed!)
384 if (position.getLongitudeDeg() < -180 || position.getLongitudeDeg() > 180 ||
385 position.getLatitudeDeg() < -90 || position.getLatitudeDeg() > 90)
388 SGBucket bucket(position);
389 TileEntry *te = tile_cache.get_tile(bucket);
390 if (!te || !te->is_loaded())
393 SGVec3d cartPos = SGVec3d::fromGeod(position);
395 // Traverse all tiles required to be there for the given visibility.
396 // This uses exactly the same algorithm like the tile scheduler.
397 double tile_width = bucket.get_width_m();
398 double tile_height = bucket.get_height_m();
399 double tile_r = 0.5*sqrt(tile_width*tile_width + tile_height*tile_height);
400 double max_dist = tile_r + range_m;
401 double max_dist2 = max_dist*max_dist;
403 int xrange = (int)fabs(range_m / tile_width) + 1;
404 int yrange = (int)fabs(range_m / tile_height) + 1;
406 for ( int x = -xrange; x <= xrange; ++x ) {
407 for ( int y = -yrange; y <= yrange; ++y ) {
408 // We have already checked for the center tile.
409 if ( x != 0 || y != 0 ) {
410 SGBucket b = sgBucketOffset( position.getLongitudeDeg(),
411 position.getLatitudeDeg(), x, y );
412 // Do not ask if it is just the next tile but way out of range.
413 if (max_dist2 < distSqr(cartPos, SGVec3d::fromGeod(b.get_center())))
415 TileEntry *te = tile_cache.get_tile(b);
416 if (!te || !te->is_loaded())
422 // Survived all tests.
428 struct IsTileLoaded :
429 public std::unary_function<TileCache::tile_map::value_type, bool>
431 bool operator()(const TileCache::tile_map::value_type& tilePair) const
433 return tilePair.second->is_loaded();
438 bool FGTileMgr::isSceneryLoaded()
440 return (std::find_if(tile_cache.begin(), tile_cache.end(),
441 std::not1(IsTileLoaded()))
442 == tile_cache.end());