]> git.mxchange.org Git - flightgear.git/blob - src/Scenery/tilemgr.cxx
20c8d61c280414c532761cb82e268b91363fe925
[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 <osgViewer/Viewer>
32 #include <osgDB/Registry>
33
34 #include <simgear/constants.h>
35 #include <simgear/debug/logstream.hxx>
36 #include <simgear/structure/exception.hxx>
37 #include <simgear/scene/model/modellib.hxx>
38 #include <simgear/scene/util/SGReaderWriterOptions.hxx>
39 #include <simgear/scene/tsync/terrasync.hxx>
40
41 #include <Main/globals.hxx>
42 #include <Main/fg_props.hxx>
43 #include <Main/renderer.hxx>
44 #include <Main/viewer.hxx>
45 #include <Scripting/NasalSys.hxx>
46
47 #include "scenery.hxx"
48 #include "SceneryPager.hxx"
49 #include "tilemgr.hxx"
50
51 using flightgear::SceneryPager;
52
53
54 FGTileMgr::FGTileMgr():
55     state( Start ),
56     last_state( Running ),
57     vis( 16000 ),
58     _terra_sync(NULL),
59     _visibilityMeters(fgGetNode("/environment/visibility-m", true)),
60     _maxTileRangeM(fgGetNode("/sim/rendering/static-lod/bare", true)),
61     _disableNasalHooks(fgGetNode("/sim/temp/disable-scenery-nasal", true))
62 {
63 }
64
65
66 FGTileMgr::~FGTileMgr()
67 {
68     // remove all nodes we might have left behind
69     osg::Group* group = globals->get_scenery()->get_terrain_branch();
70     group->removeChildren(0, group->getNumChildren());
71     // clear OSG cache
72     osgDB::Registry::instance()->clearObjectCache();
73 }
74
75
76 // Initialize the Tile Manager subsystem
77 void FGTileMgr::init() {
78     SG_LOG( SG_TERRAIN, SG_INFO, "Initializing Tile Manager subsystem." );
79
80     _options = new simgear::SGReaderWriterOptions;
81     _options->setMaterialLib(globals->get_matlib());
82     _options->setPropertyNode(globals->get_props());
83
84     osgDB::FilePathList &fp = _options->getDatabasePathList();
85     const string_list &sc = globals->get_fg_scenery();
86     fp.clear();
87     std::copy(sc.begin(), sc.end(), back_inserter(fp));
88     _options->setPluginStringData("SimGear::FG_ROOT", globals->get_fg_root());
89     if (!_disableNasalHooks->getBoolValue())
90         _options->setModelData(new FGNasalModelDataProxy);
91
92     reinit();
93 }
94
95 void FGTileMgr::refresh_tile(void* tileMgr, long tileIndex)
96 {
97     ((FGTileMgr*) tileMgr)->tile_cache.refresh_tile(tileIndex);
98 }
99
100 void FGTileMgr::reinit()
101 {
102     // remove all old scenery nodes from scenegraph and clear cache
103     osg::Group* group = globals->get_scenery()->get_terrain_branch();
104     group->removeChildren(0, group->getNumChildren());
105     tile_cache.init();
106     
107     // clear OSG cache, except on initial start-up
108     if (state != Start)
109     {
110         osgDB::Registry::instance()->clearObjectCache();
111     }
112     
113     state = Inited;
114     
115     previous_bucket.make_bad();
116     current_bucket.make_bad();
117     longitude = latitude = -1000.0;
118
119     _terra_sync = (simgear::SGTerraSync*) globals->get_subsystem("terrasync");
120     if (_terra_sync)
121         _terra_sync->setTileRefreshCb(&refresh_tile, this);
122
123     // force an update now
124     update(0.0);
125 }
126
127 /* schedule a tile for loading, keep request for given amount of time.
128  * Returns true if tile is already loaded. */
129 bool FGTileMgr::sched_tile( const SGBucket& b, double priority, bool current_view, double duration)
130 {
131     // see if tile already exists in the cache
132     TileEntry *t = tile_cache.get_tile( b );
133     if (!t)
134     {
135         // create a new entry
136         t = new TileEntry( b );
137         // insert the tile into the cache, update will generate load request
138         if ( tile_cache.insert_tile( t ) )
139         {
140             // Attach to scene graph
141
142             t->addToSceneGraph(globals->get_scenery()->get_terrain_branch());
143         } else
144         {
145             // insert failed (cache full with no available entries to
146             // delete.)  Try again later
147             delete t;
148             return false;
149         }
150
151         SG_LOG( SG_TERRAIN, SG_DEBUG, "  New tile cache size " << (int)tile_cache.get_size() );
152     }
153
154     // update tile's properties
155     tile_cache.request_tile(t,priority,current_view,duration);
156
157     return t->is_loaded();
158 }
159
160 /* schedule needed buckets for the current view position for loading,
161  * keep request for given amount of time */
162 void FGTileMgr::schedule_needed(const SGBucket& curr_bucket, double vis)
163 {
164     // sanity check (unfortunately needed!)
165     if ( longitude < -180.0 || longitude > 180.0 
166          || latitude < -90.0 || latitude > 90.0 )
167     {
168         SG_LOG( SG_TERRAIN, SG_ALERT,
169                 "Attempting to schedule tiles for bogus lon and lat  = ("
170                 << longitude << "," << latitude << ")" );
171         return;
172     }
173
174     SG_LOG( SG_TERRAIN, SG_INFO,
175             "scheduling needed tiles for " << longitude << " " << latitude );
176
177     double tile_width = curr_bucket.get_width_m();
178     double tile_height = curr_bucket.get_height_m();
179     // cout << "tile width = " << tile_width << "  tile_height = "
180     //      << tile_height << endl;
181
182     double tileRangeM = std::min(vis,_maxTileRangeM->getDoubleValue());
183     xrange = (int)(tileRangeM / tile_width) + 1;
184     yrange = (int)(tileRangeM / tile_height) + 1;
185     if ( xrange < 1 ) { xrange = 1; }
186     if ( yrange < 1 ) { yrange = 1; }
187
188     // make the cache twice as large to avoid losing terrain when switching
189     // between aircraft and tower views
190     tile_cache.set_max_cache_size( (2*xrange + 2) * (2*yrange + 2) * 2 );
191     // cout << "xrange = " << xrange << "  yrange = " << yrange << endl;
192     // cout << "max cache size = " << tile_cache.get_max_cache_size()
193     //      << " current cache size = " << tile_cache.get_size() << endl;
194
195     // clear flags of all tiles belonging to the previous view set 
196     tile_cache.clear_current_view();
197
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());
202
203     SGBucket b;
204
205     int x, y;
206
207     /* schedule all tiles, use distance-based loading priority,
208      * so tiles are loaded in innermost-to-outermost sequence. */
209     for ( x = -xrange; x <= xrange; ++x )
210     {
211         for ( y = -yrange; y <= yrange; ++y )
212         {
213             SGBucket b = sgBucketOffset( longitude, latitude, x, y );
214             float priority = (-1.0) * (x*x+y*y);
215             sched_tile( b, priority, true, 0.0 );
216         }
217     }
218 }
219
220 /**
221  * Update the various queues maintained by the tilemagr (private
222  * internal function, do not call directly.)
223  */
224 void FGTileMgr::update_queues()
225 {
226     SceneryPager* pager = FGScenery::getPagerSingleton();
227     osg::FrameStamp* framestamp
228         = globals->get_renderer()->getViewer()->getFrameStamp();
229     double current_time = framestamp->getReferenceTime();
230     double vis = _visibilityMeters->getDoubleValue();
231     TileEntry *e;
232     int loading=0;
233     int sz=0;
234
235     tile_cache.set_current_time( current_time );
236     tile_cache.reset_traversal();
237
238     while ( ! tile_cache.at_end() )
239     {
240         e = tile_cache.get_current();
241         // cout << "processing a tile" << endl;
242         if ( e )
243         {
244             // Prepare the ssg nodes corresponding to each tile.
245             // Set the ssg transform and update it's range selector
246             // based on current visibilty
247             e->prep_ssg_node(vis);
248
249             if (( !e->is_loaded() )&&
250                 ((!e->is_expired(current_time))||
251                   e->is_current_view() ))
252             {
253                 // schedule tile for loading with osg pager
254                 pager->queueRequest(e->tileFileName,
255                                     e->getNode(),
256                                     e->get_priority(),
257                                     framestamp,
258                                     e->getDatabaseRequest(),
259                                     _options.get());
260                 loading++;
261             }
262         } else
263         {
264             SG_LOG(SG_TERRAIN, SG_ALERT, "Warning: empty tile in cache!");
265         }
266         tile_cache.next();
267         sz++;
268     }
269
270     int drop_count = sz - tile_cache.get_max_cache_size();
271     if (( drop_count > 0 )&&
272          ((loading==0)||(drop_count > 10)))
273     {
274         long drop_index = tile_cache.get_drop_tile();
275         while ( drop_index > -1 )
276         {
277             // schedule tile for deletion with osg pager
278             TileEntry* old = tile_cache.get_tile(drop_index);
279             tile_cache.clear_entry(drop_index);
280             
281             osg::ref_ptr<osg::Object> subgraph = old->getNode();
282             old->removeFromSceneGraph();
283             delete old;
284             // zeros out subgraph ref_ptr, so subgraph is owned by
285             // the pager and will be deleted in the pager thread.
286             pager->queueDeleteRequest(subgraph);
287             
288             if (--drop_count > 0)
289                 drop_index = tile_cache.get_drop_tile();
290             else
291                 drop_index = -1;
292         }
293     }
294 }
295
296 // given the current lon/lat (in degrees), fill in the array of local
297 // chunks.  If the chunk isn't already in the cache, then read it from
298 // disk.
299 void FGTileMgr::update(double)
300 {
301     SGVec3d viewPos = globals->get_current_view()->get_view_pos();
302     double vis = _visibilityMeters->getDoubleValue();
303     schedule_tiles_at(SGGeod::fromCart(viewPos), vis);
304
305     update_queues();
306 }
307
308 // schedule tiles for the viewer bucket (FDM/AI/groundcache/... use
309 // "schedule_scenery" instead
310 int FGTileMgr::schedule_tiles_at(const SGGeod& location, double range_m)
311 {
312     longitude = location.getLongitudeDeg();
313     latitude = location.getLatitudeDeg();
314
315     // SG_LOG( SG_TERRAIN, SG_DEBUG, "FGTileMgr::update() for "
316     //         << longitude << " " << latatitude );
317
318     current_bucket.set_bucket( location );
319
320     // schedule more tiles when visibility increased considerably
321     // TODO Calculate tile size - instead of using fixed value (5000m)
322     if (range_m-scheduled_visibility > 5000.0)
323         previous_bucket.make_bad();
324
325     // SG_LOG( SG_TERRAIN, SG_DEBUG, "Updating tile list for "
326     //         << current_bucket );
327     fgSetInt( "/environment/current-tile-id", current_bucket.gen_index() );
328
329     // do tile load scheduling.
330     // Note that we need keep track of both viewer buckets and fdm buckets.
331     if ( state == Running ) {
332         if (last_state != state)
333         {
334             SG_LOG( SG_TERRAIN, SG_DEBUG, "State == Running" );
335         }
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             scheduled_visibility = range_m;
341             schedule_needed(current_bucket, range_m);
342             if (_terra_sync)
343                 _terra_sync->schedulePosition(latitude,longitude);
344         }
345         // save bucket
346         previous_bucket = current_bucket;
347     } else if ( state == Start || state == Inited ) {
348         SG_LOG( SG_TERRAIN, SG_DEBUG, "State == Start || Inited" );
349         // do not update bucket yet (position not valid in initial loop)
350         state = Running;
351         previous_bucket.make_bad();
352     }
353     last_state = state;
354
355     return 1;
356 }
357
358 /** Schedules scenery for given position. Load request remains valid for given duration
359  * (duration=0.0 => nothing is loaded).
360  * Used for FDM/AI/groundcache/... requests. Viewer uses "schedule_tiles_at" instead.
361  * Returns true when all tiles for the given position are already loaded, false otherwise.
362  */
363 bool FGTileMgr::schedule_scenery(const SGGeod& position, double range_m, double duration)
364 {
365     const float priority = 0.0;
366     double current_longitude = position.getLongitudeDeg();
367     double current_latitude = position.getLatitudeDeg();
368     bool available = true;
369     
370     // sanity check (unfortunately needed!)
371     if (current_longitude < -180 || current_longitude > 180 ||
372         current_latitude < -90 || current_latitude > 90)
373         return false;
374   
375     SGBucket bucket(position);
376     available = sched_tile( bucket, priority, false, duration );
377   
378     if ((!available)&&(duration==0.0))
379         return false;
380
381     SGVec3d cartPos = SGVec3d::fromGeod(position);
382
383     // Traverse all tiles required to be there for the given visibility.
384     double tile_width = bucket.get_width_m();
385     double tile_height = bucket.get_height_m();
386     double tile_r = 0.5*sqrt(tile_width*tile_width + tile_height*tile_height);
387     double max_dist = tile_r + range_m;
388     double max_dist2 = max_dist*max_dist;
389     
390     int xrange = (int)fabs(range_m / tile_width) + 1;
391     int yrange = (int)fabs(range_m / tile_height) + 1;
392
393     for ( int x = -xrange; x <= xrange; ++x )
394     {
395         for ( int y = -yrange; y <= yrange; ++y )
396         {
397             // We have already checked for the center tile.
398             if ( x != 0 || y != 0 )
399             {
400                 SGBucket b = sgBucketOffset( current_longitude,
401                                              current_latitude, x, y );
402                 double distance2 = distSqr(cartPos, SGVec3d::fromGeod(b.get_center()));
403                 // Do not ask if it is just the next tile but way out of range.
404                 if (distance2 <= max_dist2)
405                 {
406                     available &= sched_tile( b, priority, false, duration );
407                     if ((!available)&&(duration==0.0))
408                         return false;
409                 }
410             }
411         }
412     }
413
414     return available;
415 }
416
417 // Returns true if tiles around current view position have been loaded
418 bool FGTileMgr::isSceneryLoaded()
419 {
420     double range_m = 100.0;
421     if (scheduled_visibility < range_m)
422         range_m = scheduled_visibility;
423
424     return schedule_scenery(SGGeod::fromDeg(longitude, latitude), range_m, 0.0);
425 }