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 void 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);
84 _visibilityMeters = fgGetNode("/environment/visibility-m", true);
91 void FGTileMgr::reinit()
97 previous_bucket.make_bad();
98 current_bucket.make_bad();
99 longitude = latitude = -1000.0;
101 // force an update now
105 // schedule a tile for loading
106 void FGTileMgr::sched_tile( const SGBucket& b, const bool is_inner_ring ) {
107 // see if tile already exists in the cache
108 TileEntry *t = tile_cache.get_tile( b );
110 t->set_inner_ring( is_inner_ring );
114 // make space in the cache
115 SceneryPager* pager = FGScenery::getPagerSingleton();
116 while ( (int)tile_cache.get_size() > tile_cache.get_max_cache_size() ) {
117 long index = tile_cache.get_oldest_tile();
119 TileEntry *old = tile_cache.get_tile( index );
120 tile_cache.clear_entry( index );
121 osg::ref_ptr<osg::Object> subgraph = old->getNode();
122 old->removeFromSceneGraph();
124 // zeros out subgraph ref_ptr, so subgraph is owned by
125 // the pager and will be deleted in the pager thread.
126 pager->queueDeleteRequest(subgraph);
128 // nothing to free ?!? forge ahead
133 // create a new entry
134 TileEntry *e = new TileEntry( b );
136 // insert the tile into the cache
137 if ( tile_cache.insert_tile( e ) ) {
138 // update_queues will generate load request
140 // insert failed (cache full with no available entries to
141 // delete.) Try again later
144 // Attach to scene graph
145 e->addToSceneGraph(globals->get_scenery()->get_terrain_branch());
149 // schedule a needed buckets for loading
150 void FGTileMgr::schedule_needed(const SGBucket& curr_bucket, double vis) {
152 // sanity check (unfortunately needed!)
153 if ( longitude < -180.0 || longitude > 180.0
154 || latitude < -90.0 || latitude > 90.0 )
156 SG_LOG( SG_TERRAIN, SG_ALERT,
157 "Attempting to schedule tiles for bogus lon and lat = ("
158 << longitude << "," << latitude << ")" );
160 SG_LOG( SG_TERRAIN, SG_ALERT,
161 "This is a FATAL error. Exiting!" );
165 SG_LOG( SG_TERRAIN, SG_INFO,
166 "scheduling needed tiles for " << longitude << " " << latitude );
168 double tile_width = curr_bucket.get_width_m();
169 double tile_height = curr_bucket.get_height_m();
170 // cout << "tile width = " << tile_width << " tile_height = "
171 // << tile_height << endl;
173 xrange = (int)(vis / tile_width) + 1;
174 yrange = (int)(vis / tile_height) + 1;
175 if ( xrange < 1 ) { xrange = 1; }
176 if ( yrange < 1 ) { yrange = 1; }
178 // make the cache twice as large to avoid losing terrain when switching
179 // between aircraft and tower views
180 tile_cache.set_max_cache_size( (2*xrange + 2) * (2*yrange + 2) * 2 );
181 // cout << "xrange = " << xrange << " yrange = " << yrange << endl;
182 // cout << "max cache size = " << tile_cache.get_max_cache_size()
183 // << " current cache size = " << tile_cache.get_size() << endl;
185 // clear the inner ring flags so we can set them below. This
186 // prevents us from having "true" entries we aren't able to find
187 // to get rid of if we teleport a long ways away from the current
189 tile_cache.clear_inner_ring_flags();
193 // schedule center tile first so it can be loaded first
194 b = sgBucketOffset( longitude, latitude, 0, 0 );
195 sched_tile( b, true );
199 // schedule next ring of 8 tiles
200 for ( x = -1; x <= 1; ++x ) {
201 for ( y = -1; y <= 1; ++y ) {
202 if ( x != 0 || y != 0 ) {
203 b = sgBucketOffset( longitude, latitude, x, y );
204 sched_tile( b, true );
209 // schedule remaining tiles
210 for ( x = -xrange; x <= xrange; ++x ) {
211 for ( y = -yrange; y <= yrange; ++y ) {
212 if ( x < -1 || x > 1 || y < -1 || y > 1 ) {
213 SGBucket b = sgBucketOffset( longitude, latitude, x, y );
214 sched_tile( b, false );
221 FGTileMgr::loadTileModel(const string& modelPath, bool cacheModel)
224 if (fgGetBool("/sim/paths/use-custom-scenery-data") == true) {
225 string_list sc = globals->get_fg_scenery();
227 for (string_list_iterator it = sc.begin(); it != sc.end(); ++it) {
229 tmpPath.append(modelPath);
230 if (tmpPath.exists()) {
236 fullPath.append(modelPath);
238 osg::Node* result = 0;
242 SGModelLib::loadModel(fullPath.str(), globals->get_props(),
243 new FGNasalModelData);
246 SGModelLib::loadPagedModel(modelPath, globals->get_props(),
247 new FGNasalModelData);
248 } catch (const sg_io_exception& exc) {
249 string m(exc.getMessage());
251 m += exc.getLocation().asString();
252 SG_LOG( SG_ALL, SG_ALERT, m );
253 } catch (const sg_exception& exc) { // XXX may be redundant
254 SG_LOG( SG_ALL, SG_ALERT, exc.getMessage());
259 // Helper class for STL fun
260 class TileLoad : public std::unary_function<TileCache::tile_map::value_type,
264 TileLoad(SceneryPager *pager, osg::FrameStamp* framestamp,
265 osg::Group* terrainBranch, osgDB::ReaderWriter::Options* options) :
266 _pager(pager), _framestamp(framestamp), _options(options) {}
268 TileLoad(const TileLoad& rhs) :
269 _pager(rhs._pager), _framestamp(rhs._framestamp),
270 _options(rhs._options) {}
272 void operator()(TileCache::tile_map::value_type& tilePair)
274 TileEntry* entry = tilePair.second;
275 if (entry->getNode()->getNumChildren() == 0) {
276 _pager->queueRequest(entry->tileFileName,
278 entry->get_inner_ring() ? 10.0f : 1.0f,
280 entry->getDatabaseRequest(),
285 SceneryPager* _pager;
286 osg::FrameStamp* _framestamp;
287 osgDB::ReaderWriter::Options* _options;
291 * Update the various queues maintained by the tilemagr (private
292 * internal function, do not call directly.)
294 void FGTileMgr::update_queues()
296 SceneryPager* pager = FGScenery::getPagerSingleton();
297 osg::FrameStamp* framestamp
298 = globals->get_renderer()->getViewer()->getFrameStamp();
299 tile_cache.set_current_time(framestamp->getReferenceTime());
300 for_each(tile_cache.begin(), tile_cache.end(),
303 globals->get_scenery()->get_terrain_branch(), _options.get()));
307 // given the current lon/lat (in degrees), fill in the array of local
308 // chunks. If the chunk isn't already in the cache, then read it from
310 void FGTileMgr::update(double)
312 SG_LOG( SG_TERRAIN, SG_DEBUG, "FGTileMgr::update()" );
313 SGVec3d viewPos = globals->get_current_view()->get_view_pos();
315 double vis = _visibilityMeters->getDoubleValue();
316 schedule_tiles_at(SGGeod::fromCart(viewPos), vis);
319 int FGTileMgr::schedule_tiles_at(const SGGeod& location, double rangeM)
321 longitude = location.getLongitudeDeg();
322 latitude = location.getLatitudeDeg();
324 // SG_LOG( SG_TERRAIN, SG_DEBUG, "FGTileMgr::update() for "
325 // << longitude << " " << latatitude );
327 current_bucket.set_bucket( location );
328 // SG_LOG( SG_TERRAIN, SG_DEBUG, "Updating tile list for "
329 // << current_bucket );
330 fgSetInt( "/environment/current-tile-id", current_bucket.gen_index() );
332 // do tile load scheduling.
333 // Note that we need keep track of both viewer buckets and fdm buckets.
334 if ( state == Running ) {
335 SG_LOG( SG_TERRAIN, SG_DEBUG, "State == Running" );
336 if (current_bucket != previous_bucket) {
337 // We've moved to a new bucket, we need to schedule any
338 // needed tiles for loading.
339 SG_LOG( SG_TERRAIN, SG_INFO, "FGTileMgr::update()" );
340 schedule_needed(current_bucket, rangeM);
342 } else if ( state == Start || state == Inited ) {
343 SG_LOG( SG_TERRAIN, SG_INFO, "State == Start || Inited" );
346 if (current_bucket != previous_bucket
347 && current_bucket.get_chunk_lon() != -1000) {
348 SG_LOG( SG_TERRAIN, SG_INFO, "FGTileMgr::update()" );
349 schedule_needed(current_bucket, rangeM);
356 previous_bucket = current_bucket;
361 void FGTileMgr::prep_ssg_nodes() {
363 // traverse the potentially viewable tile list and update range
364 // selector and transform
366 double vis = _visibilityMeters->getDoubleValue();
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());