1
0
Fork 0
flightgear/src/Scenery/tilemgr.cxx

456 lines
12 KiB
C++
Raw Normal View History

// tilemgr.cxx -- routines to handle dynamic management of scenery tiles
//
// Written by Curtis Olson, started January 1998.
//
// Copyright (C) 1997 Curtis L. Olson - curt@infoplane.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// $Id$
1998-01-07 23:50:01 +00:00
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
1998-04-03 22:09:02 +00:00
#ifdef HAVE_WINDOWS_H
1998-01-07 23:50:01 +00:00
# include <windows.h>
#endif
#include <GL/glut.h>
#include <plib/ssg.h>
1998-01-07 23:50:01 +00:00
2000-02-15 03:30:01 +00:00
#include <simgear/constants.h>
2000-02-16 23:01:03 +00:00
#include <simgear/debug/logstream.hxx>
#include <simgear/math/point3d.hxx>
#include <simgear/math/polar3d.hxx>
2000-09-27 20:16:22 +00:00
#include <simgear/math/sg_geodesy.hxx>
2000-02-16 23:01:03 +00:00
#include <simgear/math/vector.hxx>
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <Main/viewer.hxx>
#include <Objects/obj.hxx>
#include "newcache.hxx"
#include "scenery.hxx"
#include "tilemgr.hxx"
#define TEST_LAST_HIT_CACHE
1998-09-08 15:05:10 +00:00
extern ssgRoot *scene;
extern ssgBranch *terrain_branch; // branch that holds world geometry
extern ssgBranch *gnd_lights_branch; // branch that holds ground lighting
extern ssgBranch *rwy_lights_branch; // branch that holds runway lighting
// the tile manager
FGTileMgr global_tile_mgr;
1998-01-24 00:03:27 +00:00
#ifdef ENABLE_THREADS
SGLockedQueue<FGTileEntry *> FGTileMgr::attach_queue;
SGLockedQueue<FGDeferredModel *> FGTileMgr::model_queue;
#else
queue<FGTileEntry *> FGTileMgr::attach_queue;
2001-05-21 20:44:59 +00:00
queue<FGDeferredModel *> FGTileMgr::model_queue;
#endif // ENABLE_THREADS
// Constructor
FGTileMgr::FGTileMgr():
state( Start ),
current_tile( NULL ),
vis( 16000 ),
counter_hack(0)
{
}
1998-01-24 00:03:27 +00:00
// Destructor
FGTileMgr::~FGTileMgr() {
}
1998-01-07 23:50:01 +00:00
// Initialize the Tile Manager subsystem
int FGTileMgr::init() {
2001-03-24 06:03:11 +00:00
SG_LOG( SG_TERRAIN, SG_INFO, "Initializing Tile Manager subsystem." );
2001-05-21 20:44:59 +00:00
tile_cache.init();
#if 0
// instead it's just a lot easier to let any pending work flush
// through, rather than trying to arrest the queue and nuke all
// the various work at all the various stages and get everything
// cleaned up properly.
2001-05-21 20:44:59 +00:00
while ( ! attach_queue.empty() ) {
attach_queue.pop();
}
while ( ! model_queue.empty() ) {
#ifdef ENABLE_THREADS
FGDeferredModel* dm = model_queue.pop();
#else
FGDeferredModel* dm = model_queue.front();
model_queue.pop();
#endif
delete dm;
}
loader.reinit();
#endif
2000-06-20 04:48:12 +00:00
hit_list.clear();
state = Inited;
previous_bucket.make_bad();
current_bucket.make_bad();
longitude = latitude = -1000.0;
last_longitude = last_latitude = -1000.0;
return 1;
1998-01-07 23:50:01 +00:00
}
// schedule a tile for loading
2000-12-13 20:36:04 +00:00
void FGTileMgr::sched_tile( const SGBucket& b ) {
// see if tile already exists in the cache
FGTileEntry *t = tile_cache.get_tile( b );
if ( t == NULL ) {
// create a new entry
FGTileEntry *e = new FGTileEntry( b );
// insert the tile into the cache
if ( tile_cache.insert_tile( e ) ) {
// Schedule tile for loading
loader.add( e );
} else {
// insert failed (cache full with no available entries to
// delete.) Try again later
delete e;
}
}
}
// schedule a needed buckets for loading
void FGTileMgr::schedule_needed() {
// sanity check (unfortunately needed!)
if ( longitude < -180.0 || longitude > 180.0
|| latitude < -90.0 || latitude > 90.0 )
{
SG_LOG( SG_TERRAIN, SG_ALERT,
"Attempting to schedule tiles for bogus latitude and" );
SG_LOG( SG_TERRAIN, SG_ALERT,
"longitude. This is a FATAL error. Exiting!" );
exit(-1);
}
SG_LOG( SG_TERRAIN, SG_INFO,
"scheduling needed tiles for " << longitude << " " << latitude );
vis = fgGetDouble("/environment/visibility-m");
double tile_width = current_bucket.get_width_m();
double tile_height = current_bucket.get_height_m();
// cout << "tile width = " << tile_width << " tile_height = "
// << tile_height !<< endl;
xrange = (int)(vis / tile_width) + 1;
yrange = (int)(vis / tile_height) + 1;
if ( xrange < 1 ) { xrange = 1; }
if ( yrange < 1 ) { yrange = 1; }
// cout << "xrange = " << xrange << " yrange = " << yrange << endl;
tile_cache.set_max_cache_size( (2*xrange + 2) * (2*yrange + 2) );
2001-01-17 23:30:35 +00:00
SGBucket b;
// schedule center tile first so it can be loaded first
b = sgBucketOffset( longitude, latitude, 0, 0 );
sched_tile( b );
2001-03-21 23:10:15 +00:00
int x, y;
2001-01-17 23:30:35 +00:00
// schedule next ring of 8 tiles
2001-03-21 23:10:15 +00:00
for ( x = -1; x <= 1; ++x ) {
for ( y = -1; y <= 1; ++y ) {
2001-01-17 23:30:35 +00:00
if ( x != 0 || y != 0 ) {
b = sgBucketOffset( longitude, latitude, x, y );
sched_tile( b );
2001-01-17 23:30:35 +00:00
}
}
}
// schedule remaining tiles
2001-03-21 23:10:15 +00:00
for ( x = -xrange; x <= xrange; ++x ) {
for ( y = -yrange; y <= yrange; ++y ) {
2001-01-17 23:30:35 +00:00
if ( x < -1 || x > 1 || y < -1 || y > 1 ) {
SGBucket b = sgBucketOffset( longitude, latitude, x, y );
sched_tile( b );
}
}
}
}
void FGTileMgr::initialize_queue()
{
// First time through or we have teleported, initialize the
// system and load all relavant tiles
2001-03-24 06:03:11 +00:00
SG_LOG( SG_TERRAIN, SG_INFO, "Updating Tile list for " << current_bucket );
// cout << "tile cache size = " << tile_cache.get_size() << endl;
// wipe/initialize tile cache
// tile_cache.init();
previous_bucket.make_bad();
// build the local area list and schedule tiles for loading
// start with the center tile and work out in concentric
// "rings"
schedule_needed();
// do we really want to lose this? CLO
#if 0
// Now force a load of the center tile and inner ring so we
// have something to see in our first frame.
int i;
for ( i = 0; i < 9; ++i ) {
if ( load_queue.size() ) {
2001-03-24 06:03:11 +00:00
SG_LOG( SG_TERRAIN, SG_DEBUG,
"Load queue not empty, loading a tile" );
2000-12-13 20:36:04 +00:00
SGBucket pending = load_queue.front();
load_queue.pop_front();
load_tile( pending );
}
}
#endif
}
// given the current lon/lat (in degrees), fill in the array of local
// chunks. If the chunk isn't already in the cache, then read it from
// disk.
int FGTileMgr::update( double lon, double lat ) {
2001-11-20 22:03:58 +00:00
// SG_LOG( SG_TERRAIN, SG_DEBUG, "FGTileMgr::update() for "
// << lon << " " << lat );
1998-01-07 23:50:01 +00:00
longitude = lon;
latitude = lat;
2001-03-24 06:03:11 +00:00
// SG_LOG( SG_TERRAIN, SG_DEBUG, "lon "<< lonlat[LON] <<
// " lat " << lonlat[LAT] );
1998-01-07 23:50:01 +00:00
current_bucket.set_bucket( longitude, latitude );
2001-03-24 06:03:11 +00:00
// SG_LOG( SG_TERRAIN, SG_DEBUG, "Updating Tile list for " << current_bucket );
if ( tile_cache.exists( current_bucket ) ) {
current_tile = tile_cache.get_tile( current_bucket );
scenery.set_next_center( current_tile->center );
} else {
2001-03-24 06:03:11 +00:00
SG_LOG( SG_TERRAIN, SG_WARN, "Tile not found (Ok if initializing)" );
scenery.set_next_center( Point3D(0.0) );
}
if ( state == Running ) {
SG_LOG( SG_TERRAIN, SG_DEBUG, "State == Running" );
if ( !(current_bucket == previous_bucket) ) {
// We've moved to a new bucket, we need to schedule any
// needed tiles for loading.
schedule_needed();
}
} else if ( state == Start || state == Inited ) {
SG_LOG( SG_TERRAIN, SG_INFO, "State == Start || Inited" );
initialize_queue();
state = Running;
// load the next tile in the load queue (or authorize the next
// load in the case of the threaded tile pager)
loader.update();
}
// load the next model in the load queue. Currently this must
// happen in the render thread because model loading can trigger
// texture loading which involves use of the opengl api.
if ( !model_queue.empty() ) {
// cout << "loading next model ..." << endl;
// load the next tile in the queue
#ifdef ENABLE_THREADS
FGDeferredModel* dm = model_queue.pop();
#else
FGDeferredModel* dm = model_queue.front();
model_queue.pop();
#endif
ssgTexturePath( (char *)(dm->get_texture_path().c_str()) );
ssgEntity *obj_model
= ssgLoad( (char *)(dm->get_model_path().c_str()) );
if ( obj_model != NULL ) {
dm->get_obj_trans()->addKid( obj_model );
}
dm->get_tile()->dec_pending_models();
delete dm;
}
// cout << "current elevation (ssg) == " << scenery.get_cur_elev() << endl;
previous_bucket = current_bucket;
last_longitude = longitude;
last_latitude = latitude;
// activate loader thread one out of every 5 frames
if ( counter_hack == 0 ) {
// Notify the tile loader that it can load another tile
loader.update();
}
counter_hack = (counter_hack + 1) % 5;
if ( !attach_queue.empty() ) {
#ifdef ENABLE_THREADS
FGTileEntry* e = attach_queue.pop();
#else
FGTileEntry* e = attach_queue.front();
attach_queue.pop();
#endif
e->add_ssg_nodes( terrain_branch,
gnd_lights_branch,
rwy_lights_branch );
// cout << "Adding ssg nodes for "
}
sgdVec3 sc;
sgdSetVec3( sc,
scenery.get_center()[0],
scenery.get_center()[1],
scenery.get_center()[2] );
#if 0
if ( scenery.center == Point3D(0.0) ) {
// initializing
cout << "initializing scenery current elevation ... " << endl;
sgdVec3 tmp_abs_view_pos;
2001-03-24 04:48:44 +00:00
Point3D geod_pos = Point3D( longitude * SGD_DEGREES_TO_RADIANS,
latitude * SGD_DEGREES_TO_RADIANS,
0.0);
Point3D tmp = sgGeodToCart( geod_pos );
scenery.center = tmp;
sgdSetVec3( tmp_abs_view_pos, tmp.x(), tmp.y(), tmp.z() );
// cout << "abs_view_pos = " << tmp_abs_view_pos << endl;
prep_ssg_nodes();
2000-11-02 00:59:39 +00:00
double tmp_elev;
if ( fgCurrentElev(tmp_abs_view_pos, sc, &hit_list,
&tmp_elev, &scenery.cur_radius, scenery.cur_normal) )
{
scenery.set_cur_elev( tmp_elev );
2000-11-02 00:59:39 +00:00
} else {
scenery.set_cur_elev( 0.0 );
2000-11-02 00:59:39 +00:00
}
// cout << "result = " << scenery.get_cur_elev() << endl;
} else {
#endif
/*
cout << "abs view pos = "
<< globals->get_current_view()->get_abs_view_pos()[0] << ","
<< globals->get_current_view()->get_abs_view_pos()[1] << ","
<< globals->get_current_view()->get_abs_view_pos()[2]
<< " view pos = "
<< globals->get_current_view()->get_view_pos()[0] << ","
<< globals->get_current_view()->get_view_pos()[1] << ","
<< globals->get_current_view()->get_view_pos()[2]
<< endl;
cout << "current_tile = " << current_tile << endl;
cout << "Scenery center = " << sc[0] << "," << sc[1] << "," << sc[2]
<< endl;
*/
// overridden with actual values if a terrain intersection is
// found
double hit_elev = -9999.0;
double hit_radius = 0.0;
sgdVec3 hit_normal = { 0.0, 0.0, 0.0 };
bool hit = false;
if ( fabs(sc[0]) > 1.0 || fabs(sc[1]) > 1.0 || fabs(sc[2]) > 1.0 ) {
// scenery center has been properly defined so any hit
// should be valid (and not just luck)
sgdSetVec3( sc,
scenery.get_center()[0],
scenery.get_center()[1],
scenery.get_center()[2] );
Major viewer-code overhaul from Jim Wilson: Description: This update includes the new viewer interface as proposed by David M. and a first pass at cleaning up the viewer/view manager code by Jim W. Note that I have dropped Main/viewer_lookat.?xx and Main/viewer_rph.?xx and modified the Makefile.am accordingly. Detail of work: Overall: The code reads a little easier. There are still some unnecessary bits in there and I'd like to supplement the comments in the viewer.hxx with a tiny bit on each interface group and what the groupings mean (similar but briefer than what you emailed me the other day). I tried not to mess up the style, but there is an occasional inconsistency. In general I wouldn't call it done (especially since there's no tower yet! :)), but I'd like to get this out there so others can comment, and test. In Viewer: The interface as you suggested has been implemented. Basically everything seems to work as it did visually. There is no difference that I can see in performance, although some things might be a tiny bit faster. I've merged the lookat and rph (pilot view) code into the recalc for the viewer. There is still some redundancy between the two, but a lot has been removed. In some cases I've taken some code that we'd likely want to inline anyway and left it in there in duplicate. You'll see that the code for both looks a little cleaner. I need to take a closer look at the rotations in particular. I've cleaned up a little there, but I suspect more can be done to streamline this. The external declaration to the Quat_mat in mouse.cxx has been removed. IMHO the quat doesn't serve any intrinsic purpose in mouse.cxx, but I'm not about to rip it out. It would seem that there more conventional ways to get spherical data that are just as fast. In any case all the viewer was pulling from the quat matrix was the pitch value so I modified mouse.cxx to output to our pitchOffset input and that works fine. I've changed the native values to degrees from radians where appropriate. This required a conversion from degrees to radians in a couple modules that access the interface. Perhaps we should add interface calls that do the conversion, e.g. a getHeadingOffset_rad() to go along with the getHeadingOffset_deg(). On the view_offset (now headingOffset) thing there are two entry points because of the ability to instantly switch views or to scroll to a new view angle (by hitting the numeric keys for example). This leaves an anomaly in the interface which should be resolved by adding "goal" settings to the interface, e.g. a setGoalHeadingOffset_deg(), setGoalPitchOffset_deg(), etc. Other than these two issues, the next step here will be to look at some further optimizations, and to write support code for a tower view. That should be fairly simple at this point. I was considering creating a "simulated tower view" or "pedestrian view" that defaulted to a position off to the right of whereever the plane is at the moment you switch to the tower view. This could be a fall back when we don't have an actual tower location at hand (as would be the case with rural airports). ViewManager: Basically all I did here was neaten things up by ripping out excess crap and made it compatible as is with the new interface. The result is that viewmanager is now ready to be developed. The two preexisting views are still hardcoded into the view manager. The next step would be to design configuration xml (eg /sim/view[x]/config/blahblah) that could be used to set up as many views as we want. If we want to take the easy way out, we might want to insist that view[0] be a pilot-view and have viewmanager check for that.
2002-03-20 17:43:28 +00:00
hit = fgCurrentElev(globals->get_current_view()->get_absolute_view_pos(),
sc,
current_tile->get_terra_transform(),
&hit_list,
&hit_elev,
&hit_radius,
hit_normal);
}
if ( hit ) {
scenery.set_cur_elev( hit_elev );
scenery.set_cur_radius( hit_radius );
scenery.set_cur_normal( hit_normal );
} else {
scenery.set_cur_elev( -9999.0 );
scenery.set_cur_radius( 0.0 );
scenery.set_cur_normal( hit_normal );
}
// cout << "Current elevation = " << scenery.get_cur_elev() << endl;
#if 0
}
#endif
1998-09-08 15:05:10 +00:00
return 1;
1998-09-08 15:05:10 +00:00
}
void FGTileMgr::prep_ssg_nodes() {
float vis = 0.0;
vis = fgGetDouble("/environment/visibility-m");
// traverse the potentially viewable tile list and update range
// selector and transform
FGTileEntry *e;
tile_cache.reset_traversal();
while ( ! tile_cache.at_end() ) {
// cout << "processing a tile" << endl;
if ( (e = tile_cache.get_current()) ) {
e->prep_ssg_node( scenery.get_center(), vis);
} else {
SG_LOG(SG_INPUT, SG_ALERT, "warning ... empty tile in cache");
}
tile_cache.next();
}
}
Major viewer-code overhaul from Jim Wilson: Description: This update includes the new viewer interface as proposed by David M. and a first pass at cleaning up the viewer/view manager code by Jim W. Note that I have dropped Main/viewer_lookat.?xx and Main/viewer_rph.?xx and modified the Makefile.am accordingly. Detail of work: Overall: The code reads a little easier. There are still some unnecessary bits in there and I'd like to supplement the comments in the viewer.hxx with a tiny bit on each interface group and what the groupings mean (similar but briefer than what you emailed me the other day). I tried not to mess up the style, but there is an occasional inconsistency. In general I wouldn't call it done (especially since there's no tower yet! :)), but I'd like to get this out there so others can comment, and test. In Viewer: The interface as you suggested has been implemented. Basically everything seems to work as it did visually. There is no difference that I can see in performance, although some things might be a tiny bit faster. I've merged the lookat and rph (pilot view) code into the recalc for the viewer. There is still some redundancy between the two, but a lot has been removed. In some cases I've taken some code that we'd likely want to inline anyway and left it in there in duplicate. You'll see that the code for both looks a little cleaner. I need to take a closer look at the rotations in particular. I've cleaned up a little there, but I suspect more can be done to streamline this. The external declaration to the Quat_mat in mouse.cxx has been removed. IMHO the quat doesn't serve any intrinsic purpose in mouse.cxx, but I'm not about to rip it out. It would seem that there more conventional ways to get spherical data that are just as fast. In any case all the viewer was pulling from the quat matrix was the pitch value so I modified mouse.cxx to output to our pitchOffset input and that works fine. I've changed the native values to degrees from radians where appropriate. This required a conversion from degrees to radians in a couple modules that access the interface. Perhaps we should add interface calls that do the conversion, e.g. a getHeadingOffset_rad() to go along with the getHeadingOffset_deg(). On the view_offset (now headingOffset) thing there are two entry points because of the ability to instantly switch views or to scroll to a new view angle (by hitting the numeric keys for example). This leaves an anomaly in the interface which should be resolved by adding "goal" settings to the interface, e.g. a setGoalHeadingOffset_deg(), setGoalPitchOffset_deg(), etc. Other than these two issues, the next step here will be to look at some further optimizations, and to write support code for a tower view. That should be fairly simple at this point. I was considering creating a "simulated tower view" or "pedestrian view" that defaulted to a position off to the right of whereever the plane is at the moment you switch to the tower view. This could be a fall back when we don't have an actual tower location at hand (as would be the case with rural airports). ViewManager: Basically all I did here was neaten things up by ripping out excess crap and made it compatible as is with the new interface. The result is that viewmanager is now ready to be developed. The two preexisting views are still hardcoded into the view manager. The next step would be to design configuration xml (eg /sim/view[x]/config/blahblah) that could be used to set up as many views as we want. If we want to take the easy way out, we might want to insist that view[0] be a pilot-view and have viewmanager check for that.
2002-03-20 17:43:28 +00:00