]> git.mxchange.org Git - flightgear.git/blob - src/Scenery/tilemgr.cxx
Continued FGViewer cleanups. FGViewer is now a base class so that we can
[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  - curt@infoplane.com
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., 675 Mass Ave, Cambridge, MA 02139, USA.
20 //
21 // $Id$
22
23
24 #ifdef HAVE_CONFIG_H
25 #  include <config.h>
26 #endif
27
28 #ifdef HAVE_WINDOWS_H
29 #  include <windows.h>
30 #endif
31
32 #include <GL/glut.h>
33 #include <simgear/xgl/xgl.h>
34
35 #include <simgear/constants.h>
36 #include <simgear/debug/logstream.hxx>
37 #include <simgear/math/point3d.hxx>
38 #include <simgear/math/polar3d.hxx>
39 #include <simgear/math/sg_geodesy.hxx>
40 #include <simgear/math/vector.hxx>
41
42 // #include <Aircraft/aircraft.hxx>
43 #include <Main/globals.hxx>
44 #include <Objects/obj.hxx>
45
46 #ifndef FG_OLD_WEATHER
47 #  include <WeatherCM/FGLocalWeatherDatabase.h>
48 #else
49 #  include <Weather/weather.hxx>
50 #endif
51
52 #include "scenery.hxx"
53 #include "tilecache.hxx"
54 #include "tilemgr.hxx"
55
56 #define TEST_LAST_HIT_CACHE
57
58 extern ssgRoot *scene;
59 extern ssgBranch *terrain;
60
61 // the tile manager
62 FGTileMgr global_tile_mgr;
63
64
65 // a temporary hack until we get everything rewritten with sgdVec3
66 static inline Point3D operator + (const Point3D& a, const sgdVec3 b)
67 {
68     return Point3D(a.x()+b[0], a.y()+b[1], a.z()+b[2]);
69 }
70
71
72 // Constructor
73 FGTileMgr::FGTileMgr ( void ):
74     state( Start )
75 {
76 }
77
78
79 // Destructor
80 FGTileMgr::~FGTileMgr ( void ) {
81 }
82
83
84 // Initialize the Tile Manager subsystem
85 int FGTileMgr::init( void ) {
86     FG_LOG( FG_TERRAIN, FG_INFO, "Initializing Tile Manager subsystem." );
87
88     if ( state != Start ) {
89         FG_LOG( FG_TERRAIN, FG_INFO,
90                 "... Reinitializing." );
91         destroy_queue();
92     } else {
93         FG_LOG( FG_TERRAIN, FG_INFO,
94                 "... First time through." );
95     }
96
97     global_tile_cache.init();
98     hit_list.clear();
99
100     state = Inited;
101
102     tile_diameter = globals->get_options()->get_tile_diameter();
103     FG_LOG( FG_TERRAIN, FG_INFO, "Tile Diameter = " << tile_diameter);
104     
105     previous_bucket.make_bad();
106     current_bucket.make_bad();
107
108     scroll_direction = SCROLL_INIT;
109     tile_index = -9999;
110
111     longitude = latitude = -1000.0;
112     last_longitude = last_latitude = -1000.0;
113         
114     return 1;
115 }
116
117
118 // schedule a tile for loading
119 int FGTileMgr::sched_tile( const FGBucket& b ) {
120     // see if tile already exists in the cache
121     int cache_index = global_tile_cache.exists( b );
122
123     if ( cache_index >= 0 ) {
124         // tile exists in cache, reenable it.
125         // cout << "REENABLING DISABLED TILE" << endl;
126         FGTileEntry *t = global_tile_cache.get_tile( cache_index );
127         t->select_ptr->select( 1 );
128         t->mark_loaded();
129     } else {
130         // find the next available cache entry and mark it as
131         // scheduled
132         cache_index = global_tile_cache.next_avail();
133         FGTileEntry *t = global_tile_cache.get_tile( cache_index );
134         t->mark_scheduled_for_use();
135
136         // register a load request
137         FGLoadRec request;
138         request.b = b;
139         request.cache_index = cache_index;
140         load_queue.push_back( request );
141     }
142
143     return cache_index;
144 }
145
146
147 // load a tile
148 void FGTileMgr::load_tile( const FGBucket& b, int cache_index) {
149
150     FG_LOG( FG_TERRAIN, FG_DEBUG, "Loading tile " << b );
151     global_tile_cache.fill_in(cache_index, b);
152     FG_LOG( FG_TERRAIN, FG_DEBUG, "Loaded for cache index: " << cache_index );
153 }
154
155
156 static void CurrentNormalInLocalPlane(sgVec3 dst, sgVec3 src) {
157     sgVec3 tmp;
158     sgSetVec3(tmp, src[0], src[1], src[2] );
159     sgMat4 TMP;
160     sgTransposeNegateMat4 ( TMP, globals->get_current_view()->get_UP() ) ;
161     sgXformVec3(tmp, tmp, TMP);
162     sgSetVec3(dst, tmp[2], tmp[1], tmp[0] );
163 }
164
165
166 // Determine scenery altitude via ssg.  Normally this just happens
167 // when we render the scene, but we'd also like to be able to do this
168 // explicitely.  lat & lon are in radians.  view_pos in current world
169 // coordinate translated near (0,0,0) (in meters.)  Returns result in
170 // meters.
171 bool FGTileMgr::current_elev_ssg( sgdVec3 abs_view_pos, sgVec3 view_pos ) {
172     sgdVec3 orig, dir;
173
174     sgdSetVec3(orig, view_pos );
175     sgdCopyVec3(dir, abs_view_pos );
176
177     hit_list.Intersect( terrain, orig, dir );
178
179     int this_hit=0;
180     Point3D geoc;
181     double result = -9999;
182
183     int hitcount = hit_list.num_hits();
184     for ( int i = 0; i < hitcount; ++i ) {
185         geoc = sgCartToPolar3d( scenery.center + hit_list.get_point(i) );      
186         double lat_geod, alt, sea_level_r;
187         sgGeocToGeod(geoc.lat(), geoc.radius(), &lat_geod, 
188                      &alt, &sea_level_r);
189         if ( alt > result && alt < 10000 ) {
190             result = alt;
191             this_hit = i;
192         }
193     }
194
195     if ( result > -9000 ) {
196         scenery.cur_elev = result;
197         scenery.cur_radius = geoc.radius();
198         sgVec3 tmp;
199         sgSetVec3(tmp, hit_list.get_normal(this_hit));
200         ssgState *IntersectedLeafState =
201             ((ssgLeaf*)hit_list.get_entity(this_hit))->getState();
202         CurrentNormalInLocalPlane(tmp, tmp);
203         sgdSetVec3( scenery.cur_normal, tmp );
204         // cout << "NED: " << tmp[0] << " " << tmp[1] << " " << tmp[2] << endl;
205         return true;
206     } else {
207         FG_LOG( FG_TERRAIN, FG_INFO, "no terrain intersection" );
208         scenery.cur_elev = 0.0;
209         float *up = globals->get_current_view()->get_local_up();
210         sgdSetVec3(scenery.cur_normal, up[0], up[1], up[2]);
211         return false;
212     }
213 }
214
215
216 FGBucket FGTileMgr::BucketOffset( int dx, int dy )
217 {
218     double clat, clon, span;
219     if( scroll_direction == SCROLL_INIT ) {
220         // use current latitude and longitude
221         // walk dy units in the lat direction
222         clat = current_bucket.get_center_lat() + dy * FG_BUCKET_SPAN;
223
224         // find the lon span for the new latitude
225         span = bucket_span( clat );
226         
227         // walk dx units in the lon direction
228         clon = longitude + dx * span;
229     } else      {
230         // use previous latitude and longitude
231         // walk dy units in the lat direction
232         clat = previous_bucket.get_center_lat() + dy * FG_BUCKET_SPAN;
233
234         // find the lon span for the new latitude
235         span = bucket_span( clat );
236
237         // walk dx units in the lon direction
238         clon = last_longitude + dx * span;
239     }    
240         
241     while ( clon < -180.0 ) clon += 360.0;
242     while ( clon >= 180.0 ) clon -= 360.0;
243     pending.set_bucket( clon, clat );
244
245     FG_LOG( FG_TERRAIN, FG_DEBUG, "    fgBucketOffset " << pending );
246     return pending;
247 }
248
249
250 // schedule a tile row(column) for loading
251 void FGTileMgr::scroll( void )
252 {
253     FG_LOG( FG_TERRAIN, FG_DEBUG, "schedule_row: Scrolling" );
254
255     int i, dw, dh;
256         
257     switch( scroll_direction ) {
258     case SCROLL_NORTH:
259         FG_LOG( FG_TERRAIN, FG_DEBUG, 
260                 "  (North) Loading " << tile_diameter << " tiles" );
261         dw = tile_diameter / 2;
262         dh = dw + 1;
263         for ( i = 0; i < tile_diameter; i++ ) {
264             sched_tile( BucketOffset( i - dw, dh ) );
265         }
266         break;
267     case SCROLL_EAST:
268         FG_LOG( FG_TERRAIN, FG_DEBUG, 
269                 "  (East) Loading " << tile_diameter << " tiles" );
270         dh = tile_diameter / 2;
271         dw = dh + 1;
272         for ( i = 0; i < tile_diameter; i++ ) {
273             sched_tile( BucketOffset( dw, i - dh ) );
274         }
275         break;
276     case SCROLL_SOUTH:
277         FG_LOG( FG_TERRAIN, FG_DEBUG, 
278                 "  (South) Loading " << tile_diameter << " tiles" );
279         dw = tile_diameter / 2;
280         dh = -dw - 1;
281         for ( i = 0; i < tile_diameter; i++ ) {
282             sched_tile( BucketOffset( i - dw, dh ) );
283         }
284         break;
285     case SCROLL_WEST:
286         FG_LOG( FG_TERRAIN, FG_DEBUG, 
287                 "  (West) Loading " << tile_diameter << " tiles" );
288         dh = tile_diameter / 2;
289         dw = -dh - 1;
290         for ( i = 0; i < tile_diameter; i++ ) {
291             sched_tile( BucketOffset( dw, i - dh ) );
292         }
293         break;
294     default:
295         FG_LOG( FG_TERRAIN, FG_WARN, "UNKNOWN SCROLL DIRECTION in schedule_row" );
296         ;
297     }
298     FG_LOG( FG_TERRAIN, FG_DEBUG, "\tschedule_row returns" );
299 }
300
301
302 void FGTileMgr::initialize_queue()
303 {
304     // First time through or we have teleported, initialize the
305     // system and load all relavant tiles
306
307     FG_LOG( FG_TERRAIN, FG_INFO, "Updating Tile list for " << current_bucket );
308     FG_LOG( FG_TERRAIN, FG_INFO, "  Updating Tile list for " << current_bucket );
309     FG_LOG( FG_TERRAIN, FG_INFO, "  Loading " 
310             << tile_diameter * tile_diameter << " tiles" );
311
312     int i;
313     scroll_direction = SCROLL_INIT;
314
315     // wipe/initialize tile cache
316     global_tile_cache.init();
317     previous_bucket.make_bad();
318
319     // build the local area list and schedule tiles for loading
320
321     // start with the center tile and work out in concentric
322     // "rings"
323
324     sched_tile( current_bucket );
325
326     for ( i = 3; i <= tile_diameter; i = i + 2 ) {
327         int j;
328         int span = i / 2;
329
330         // bottom row
331         for ( j = -span; j <= span; ++j ) {
332             sched_tile( BucketOffset( j, -span ) );
333         }
334
335         // top row
336         for ( j = -span; j <= span; ++j ) {
337             sched_tile( BucketOffset( j, span ) );
338         }
339
340         // middle rows
341         for ( j = -span + 1; j <= span - 1; ++j ) {
342             sched_tile( BucketOffset( -span, j ) );
343             sched_tile( BucketOffset( span, j ) );
344         }
345
346     }
347
348     // Now force a load of the center tile and inner ring so we
349     // have something to see in our first frame.
350     for ( i = 0; i < 9; ++i ) {
351         if ( load_queue.size() ) {
352             FG_LOG( FG_TERRAIN, FG_DEBUG, 
353                     "Load queue not empty, loading a tile" );
354
355             FGLoadRec pending = load_queue.front();
356             load_queue.pop_front();
357             load_tile( pending.b, pending.cache_index );
358         }
359     }
360 }
361
362
363 // forced emptying of the queue
364 // This is necessay to keep bookeeping straight for the
365 // tile_cache   -- which actually handles all the
366 // (de)allocations  
367 void FGTileMgr::destroy_queue() {
368     while( load_queue.size() ) {
369         FG_LOG( FG_TERRAIN, FG_INFO, 
370                 "Load queue not empty, popping a tile" );
371         FGLoadRec pending = load_queue.front();
372         load_queue.pop_front();
373         FGTileEntry *t = global_tile_cache.get_tile( pending.cache_index );
374         // just t->mark_unused() should be enough
375         // but a little paranoia doesn't hurt us here
376         if(t->is_scheduled_for_use())
377             t->mark_unused();
378         else
379             load_tile( pending.b, pending.cache_index );
380     }
381 }
382
383
384 // given the current lon/lat (in degrees), fill in the array of local
385 // chunks.  If the chunk isn't already in the cache, then read it from
386 // disk.
387 int FGTileMgr::update( double lon, double lat ) {
388     // FG_LOG( FG_TERRAIN, FG_DEBUG, "FGTileMgr::update()" );
389
390     // FGInterface *f = current_aircraft.fdm_state;
391
392     // lonlat for this update 
393     // longitude = f->get_Longitude() * RAD_TO_DEG;
394     // latitude = f->get_Latitude() * RAD_TO_DEG;
395     longitude = lon;
396     latitude = lat;
397     // FG_LOG( FG_TERRAIN, FG_DEBUG, "lon "<< lonlat[LON] <<
398     //      " lat " << lonlat[LAT] );
399
400     current_bucket.set_bucket( longitude, latitude );
401     // FG_LOG( FG_TERRAIN, FG_DEBUG, "Updating Tile list for " << current_bucket );
402
403     tile_index = global_tile_cache.exists(current_bucket);
404     // FG_LOG( FG_TERRAIN, FG_DEBUG, "tile index " << tile_index );
405
406     if ( tile_index >= 0 ) {
407         current_tile = global_tile_cache.get_tile(tile_index);
408         scenery.next_center = current_tile->center;
409     } else {
410         FG_LOG( FG_TERRAIN, FG_WARN, "Tile not found (Ok if initializing)" );
411     }
412
413     if ( state == Running ) {
414         if( current_bucket == previous_bucket) {
415             FG_LOG( FG_TERRAIN, FG_DEBUG, "Same bucket as last time" );
416             scroll_direction = SCROLL_NONE;
417         } else {
418             // We've moved to a new bucket, we need to scroll our
419             // structures, and load in the new tiles
420             // CURRENTLY THIS ASSUMES WE CAN ONLY MOVE TO ADJACENT TILES.
421             // AT ULTRA HIGH SPEEDS THIS ASSUMPTION MAY NOT BE VALID IF
422             // THE AIRCRAFT CAN SKIP A TILE IN A SINGLE ITERATION.
423
424             if ( (current_bucket.get_lon() > previous_bucket.get_lon()) ||
425                  ( (current_bucket.get_lon() == previous_bucket.get_lon()) && 
426                    (current_bucket.get_x() > previous_bucket.get_x()) ) )
427                 {
428                     scroll_direction = SCROLL_EAST;
429                 }
430             else if ( (current_bucket.get_lon() < previous_bucket.get_lon()) ||
431                       ( (current_bucket.get_lon() == previous_bucket.get_lon()) && 
432                         (current_bucket.get_x() < previous_bucket.get_x()) ) )
433                 {   
434                     scroll_direction = SCROLL_WEST;
435                 }   
436
437             if ( (current_bucket.get_lat() > previous_bucket.get_lat()) ||
438                  ( (current_bucket.get_lat() == previous_bucket.get_lat()) && 
439                    (current_bucket.get_y() > previous_bucket.get_y()) ) )
440                 {   
441                     scroll_direction = SCROLL_NORTH;
442                 }
443             else if ( (current_bucket.get_lat() < previous_bucket.get_lat()) ||
444                       ( (current_bucket.get_lat() == previous_bucket.get_lat()) && 
445                         (current_bucket.get_y() < previous_bucket.get_y()) ) )
446                 {
447                     scroll_direction = SCROLL_SOUTH;
448                 }
449
450             scroll();
451         }
452
453     } else if ( state == Start || state == Inited ) {
454         initialize_queue();
455         state = Running;
456     }
457
458     if ( load_queue.size() ) {
459         FG_LOG( FG_TERRAIN, FG_DEBUG, "Load queue not empty, loading a tile" );
460
461         FGLoadRec pending = load_queue.front();
462         load_queue.pop_front();
463         load_tile( pending.b, pending.cache_index );
464     }
465
466     if ( scenery.center == Point3D(0.0) ) {
467         // initializing
468         // cout << "initializing ... " << endl;
469         sgdVec3 tmp_abs_view_pos;
470         sgVec3 tmp_view_pos;
471
472         Point3D geod_pos = Point3D( longitude * DEG_TO_RAD,
473                                     latitude * DEG_TO_RAD,
474                                     0.0);
475         Point3D tmp = sgGeodToCart( geod_pos );
476         scenery.center = tmp;
477         sgdSetVec3( tmp_abs_view_pos, tmp.x(), tmp.y(), tmp.z() );
478
479         // cout << "abs_view_pos = " << tmp_abs_view_pos << endl;
480         prep_ssg_nodes();
481         sgSetVec3( tmp_view_pos, 0.0, 0.0, 0.0 );
482         current_elev_ssg( tmp_abs_view_pos, tmp_view_pos );
483     } else {
484         // cout << "abs view pos = " << current_view.abs_view_pos
485         //      << " view pos = " << current_view.view_pos << endl;
486         current_elev_ssg( globals->get_current_view()->get_abs_view_pos(),
487                           globals->get_current_view()->get_view_pos() );
488     }
489
490     // cout << "current elevation (ssg) == " << scenery.cur_elev << endl;
491
492     previous_bucket = current_bucket;
493     last_longitude = longitude;
494     last_latitude  = latitude;
495
496     return 1;
497 }
498
499 // Prepare the ssg nodes ... for each tile, set it's proper
500 // transform and update it's range selector based on current
501 // visibilty
502 void FGTileMgr::prep_ssg_node( int idx ) {
503 }
504
505 void FGTileMgr::prep_ssg_nodes( void ) {
506     FGTileEntry *t;
507     float ranges[2];
508     ranges[0] = 0.0f;
509     double vis = 0.0;
510
511 #ifndef FG_OLD_WEATHER
512     if ( WeatherDatabase != NULL ) {
513         vis = WeatherDatabase->getWeatherVisibility();
514     } else {
515         vis = 16000;
516     }
517 #else
518     vis = current_weather.get_visibility();
519 #endif
520     // cout << "visibility = " << vis << endl;
521
522     // traverse the potentially viewable tile list and update range
523     // selector and transform
524     for ( int i = 0; i < (int)global_tile_cache.get_size(); i++ ) {
525         t = global_tile_cache.get_tile( i );
526
527         if ( t->is_loaded() ) {
528             // set range selector (LOD trick) to be distance to center
529             // of tile + bounding radius
530
531             ranges[1] = vis + t->bounding_radius;
532             t->range_ptr->setRanges( ranges, 2 );
533
534             // calculate tile offset
535             t->SetOffset( scenery.center );
536
537             // calculate ssg transform
538             sgCoord sgcoord;
539             sgSetCoord( &sgcoord,
540                         t->offset.x(), t->offset.y(), t->offset.z(),
541                         0.0, 0.0, 0.0 );
542             t->transform_ptr->setTransform( &sgcoord );
543         }
544     }
545 }