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, const bool is_cache_locked ) {
107 // see if tile already exists in the cache
108 TileEntry *t = tile_cache.get_tile( b );
110 t->set_timestamp(tile_cache.get_current_time());
111 t->set_inner_ring( is_inner_ring );
112 t->set_cache_lock( is_cache_locked );
116 // make space in the cache
117 SceneryPager* pager = FGScenery::getPagerSingleton();
118 while ( (int)tile_cache.get_size() >= tile_cache.get_max_cache_size() ) {
119 long index = tile_cache.get_oldest_tile();
121 TileEntry *old = tile_cache.get_tile( index );
122 tile_cache.clear_entry( index );
123 osg::ref_ptr<osg::Object> subgraph = old->getNode();
124 old->removeFromSceneGraph();
126 // zeros out subgraph ref_ptr, so subgraph is owned by
127 // the pager and will be deleted in the pager thread.
128 pager->queueDeleteRequest(subgraph);
130 // nothing to free ?!? forge ahead
135 // create a new entry
136 TileEntry *e = new TileEntry( b );
138 // insert the tile into the cache
139 if ( tile_cache.insert_tile( e ) ) {
140 e->set_inner_ring( is_inner_ring );
141 e->set_cache_lock( is_cache_locked );
142 // update_queues will generate load request
143 // Attach to scene graph
144 e->addToSceneGraph(globals->get_scenery()->get_terrain_branch());
146 // insert failed (cache full with no available entries to
147 // delete.) Try again later
152 // schedule a needed buckets for loading
153 void FGTileMgr::schedule_needed(const SGBucket& curr_bucket, double vis) {
155 // sanity check (unfortunately needed!)
156 if ( longitude < -180.0 || longitude > 180.0
157 || latitude < -90.0 || latitude > 90.0 )
159 SG_LOG( SG_TERRAIN, SG_ALERT,
160 "Attempting to schedule tiles for bogus lon and lat = ("
161 << longitude << "," << latitude << ")" );
163 SG_LOG( SG_TERRAIN, SG_ALERT,
164 "This is a FATAL error. Exiting!" );
168 SG_LOG( SG_TERRAIN, SG_INFO,
169 "scheduling needed tiles for " << longitude << " " << latitude );
171 double tile_width = curr_bucket.get_width_m();
172 double tile_height = curr_bucket.get_height_m();
173 // cout << "tile width = " << tile_width << " tile_height = "
174 // << tile_height << endl;
176 xrange = (int)(vis / tile_width) + 1;
177 yrange = (int)(vis / tile_height) + 1;
178 if ( xrange < 1 ) { xrange = 1; }
179 if ( yrange < 1 ) { yrange = 1; }
181 // make the cache twice as large to avoid losing terrain when switching
182 // between aircraft and tower views
183 tile_cache.set_max_cache_size( (2*xrange + 2) * (2*yrange + 2) * 2 );
184 // cout << "xrange = " << xrange << " yrange = " << yrange << endl;
185 // cout << "max cache size = " << tile_cache.get_max_cache_size()
186 // << " current cache size = " << tile_cache.get_size() << endl;
188 // clear the inner ring flags so we can set them below. This
189 // prevents us from having "true" entries we aren't able to find
190 // to get rid of if we teleport a long ways away from the current
192 tile_cache.clear_inner_ring_flags();
194 // clear the cache lock flags which prevented tiles of the previous position to be dropped
196 tile_cache.clear_cache_lock_flags();
198 // update timestamps, so all tiles scheduled now are *newer* than any tile previously loaded
199 osg::FrameStamp* framestamp
200 = globals->get_renderer()->getViewer()->getFrameStamp();
201 tile_cache.set_current_time(framestamp->getReferenceTime());
205 // schedule center tile first so it can be loaded first
206 b = sgBucketOffset( longitude, latitude, 0, 0 );
207 sched_tile( b, true, true );
211 // schedule next ring of 8 tiles
212 for ( x = -1; x <= 1; ++x ) {
213 for ( y = -1; y <= 1; ++y ) {
214 if ( x != 0 || y != 0 ) {
215 b = sgBucketOffset( longitude, latitude, x, y );
216 sched_tile( b, true, true);
221 // schedule remaining tiles
222 for ( x = -xrange; x <= xrange; ++x ) {
223 for ( y = -yrange; y <= yrange; ++y ) {
224 if ( x < -1 || x > 1 || y < -1 || y > 1 ) {
225 SGBucket b = sgBucketOffset( longitude, latitude, x, y );
226 sched_tile( b, false, true);
233 FGTileMgr::loadTileModel(const string& modelPath, bool cacheModel)
236 if (fgGetBool("/sim/paths/use-custom-scenery-data") == true) {
237 string_list sc = globals->get_fg_scenery();
239 for (string_list_iterator it = sc.begin(); it != sc.end(); ++it) {
241 tmpPath.append(modelPath);
242 if (tmpPath.exists()) {
248 fullPath.append(modelPath);
250 osg::Node* result = 0;
254 SGModelLib::loadModel(fullPath.str(), globals->get_props(),
255 new FGNasalModelData);
258 SGModelLib::loadPagedModel(modelPath, globals->get_props(),
259 new FGNasalModelData);
260 } catch (const sg_io_exception& exc) {
261 string m(exc.getMessage());
263 m += exc.getLocation().asString();
264 SG_LOG( SG_ALL, SG_ALERT, m );
265 } catch (const sg_exception& exc) { // XXX may be redundant
266 SG_LOG( SG_ALL, SG_ALERT, exc.getMessage());
271 // Helper class for STL fun
272 class TileLoad : public std::unary_function<TileCache::tile_map::value_type,
276 TileLoad(SceneryPager *pager, osg::FrameStamp* framestamp,
277 osg::Group* terrainBranch, osgDB::ReaderWriter::Options* options) :
278 _pager(pager), _framestamp(framestamp), _options(options) {}
280 TileLoad(const TileLoad& rhs) :
281 _pager(rhs._pager), _framestamp(rhs._framestamp),
282 _options(rhs._options) {}
284 void operator()(TileCache::tile_map::value_type& tilePair)
286 TileEntry* entry = tilePair.second;
287 if (entry->getNode()->getNumChildren() == 0) {
288 _pager->queueRequest(entry->tileFileName,
290 entry->get_inner_ring() ? 10.0f : 1.0f,
292 entry->getDatabaseRequest(),
297 SceneryPager* _pager;
298 osg::FrameStamp* _framestamp;
299 osgDB::ReaderWriter::Options* _options;
303 * Update the various queues maintained by the tilemagr (private
304 * internal function, do not call directly.)
306 void FGTileMgr::update_queues()
308 SceneryPager* pager = FGScenery::getPagerSingleton();
309 osg::FrameStamp* framestamp
310 = globals->get_renderer()->getViewer()->getFrameStamp();
311 tile_cache.set_current_time(framestamp->getReferenceTime());
312 for_each(tile_cache.begin(), tile_cache.end(),
315 globals->get_scenery()->get_terrain_branch(), _options.get()));
319 // given the current lon/lat (in degrees), fill in the array of local
320 // chunks. If the chunk isn't already in the cache, then read it from
322 void FGTileMgr::update(double)
324 SG_LOG( SG_TERRAIN, SG_DEBUG, "FGTileMgr::update()" );
325 SGVec3d viewPos = globals->get_current_view()->get_view_pos();
327 double vis = _visibilityMeters->getDoubleValue();
328 schedule_tiles_at(SGGeod::fromCart(viewPos), vis);
331 int FGTileMgr::schedule_tiles_at(const SGGeod& location, double rangeM)
333 longitude = location.getLongitudeDeg();
334 latitude = location.getLatitudeDeg();
336 // SG_LOG( SG_TERRAIN, SG_DEBUG, "FGTileMgr::update() for "
337 // << longitude << " " << latatitude );
339 current_bucket.set_bucket( location );
340 // SG_LOG( SG_TERRAIN, SG_DEBUG, "Updating tile list for "
341 // << current_bucket );
342 fgSetInt( "/environment/current-tile-id", current_bucket.gen_index() );
344 // do tile load scheduling.
345 // Note that we need keep track of both viewer buckets and fdm buckets.
346 if ( state == Running ) {
347 SG_LOG( SG_TERRAIN, SG_DEBUG, "State == Running" );
348 if (current_bucket != previous_bucket) {
349 // We've moved to a new bucket, we need to schedule any
350 // needed tiles for loading.
351 SG_LOG( SG_TERRAIN, SG_INFO, "FGTileMgr::update()" );
352 schedule_needed(current_bucket, rangeM);
354 } else if ( state == Start || state == Inited ) {
355 SG_LOG( SG_TERRAIN, SG_INFO, "State == Start || Inited" );
358 if (current_bucket != previous_bucket
359 && current_bucket.get_chunk_lon() != -1000) {
360 SG_LOG( SG_TERRAIN, SG_INFO, "FGTileMgr::update()" );
361 schedule_needed(current_bucket, rangeM);
368 previous_bucket = current_bucket;
373 void FGTileMgr::prep_ssg_nodes() {
375 // traverse the potentially viewable tile list and update range
376 // selector and transform
378 double vis = _visibilityMeters->getDoubleValue();
380 tile_cache.reset_traversal();
382 while ( ! tile_cache.at_end() ) {
383 // cout << "processing a tile" << endl;
384 if ( (e = tile_cache.get_current()) ) {
385 e->prep_ssg_node(vis);
387 SG_LOG(SG_INPUT, SG_ALERT, "warning ... empty tile in cache");
393 bool FGTileMgr::scenery_available(const SGGeod& position, double range_m)
395 // sanity check (unfortunately needed!)
396 if (position.getLongitudeDeg() < -180 || position.getLongitudeDeg() > 180 ||
397 position.getLatitudeDeg() < -90 || position.getLatitudeDeg() > 90)
400 SGBucket bucket(position);
401 TileEntry *te = tile_cache.get_tile(bucket);
402 if (!te || !te->is_loaded())
405 SGVec3d cartPos = SGVec3d::fromGeod(position);
407 // Traverse all tiles required to be there for the given visibility.
408 // This uses exactly the same algorithm like the tile scheduler.
409 double tile_width = bucket.get_width_m();
410 double tile_height = bucket.get_height_m();
411 double tile_r = 0.5*sqrt(tile_width*tile_width + tile_height*tile_height);
412 double max_dist = tile_r + range_m;
413 double max_dist2 = max_dist*max_dist;
415 int xrange = (int)fabs(range_m / tile_width) + 1;
416 int yrange = (int)fabs(range_m / tile_height) + 1;
418 for ( int x = -xrange; x <= xrange; ++x ) {
419 for ( int y = -yrange; y <= yrange; ++y ) {
420 // We have already checked for the center tile.
421 if ( x != 0 || y != 0 ) {
422 SGBucket b = sgBucketOffset( position.getLongitudeDeg(),
423 position.getLatitudeDeg(), x, y );
424 // Do not ask if it is just the next tile but way out of range.
425 if (max_dist2 < distSqr(cartPos, SGVec3d::fromGeod(b.get_center())))
427 TileEntry *te = tile_cache.get_tile(b);
428 if (!te || !te->is_loaded())
434 // Survived all tests.
440 struct IsTileLoaded :
441 public std::unary_function<TileCache::tile_map::value_type, bool>
443 bool operator()(const TileCache::tile_map::value_type& tilePair) const
445 return tilePair.second->is_loaded();
450 bool FGTileMgr::isSceneryLoaded()
452 return (std::find_if(tile_cache.begin(), tile_cache.end(),
453 std::not1(IsTileLoaded()))
454 == tile_cache.end());