// AIMgr.cxx - implementation of FGAIMgr // - a global management class for FlightGear generated AI traffic // // Written by David Luff, started March 2002. // // Copyright (C) 2002 David C Luff - david.luff@nottingham.ac.uk // // 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #ifdef HAVE_CONFIG_H # include #endif #include
#include
#include #include #include #ifdef _MSC_VER # include #else # include // for directory reading # include // for directory reading #endif #include #include #include "AIMgr.hxx" #include "AILocalTraffic.hxx" #include "AIGAVFRTraffic.hxx" #include "ATCutils.hxx" #include "commlist.hxx" SG_USING_STD(list); SG_USING_STD(cout); FGAIMgr::FGAIMgr() { ATC = globals->get_ATC_mgr(); initDone = false; ai_callsigns_used["GFS"] = 1; // so we don't inadvertently use this // TODO - use the proper user callsign when it becomes user settable. removalList.clear(); activated.clear(); _havePiperModel = true; } FGAIMgr::~FGAIMgr() { } void FGAIMgr::init() { //cout << "AIMgr::init called..." << endl; // Pointers to user's position lon_node = fgGetNode("/position/longitude-deg", true); lat_node = fgGetNode("/position/latitude-deg", true); elev_node = fgGetNode("/position/altitude-ft", true); lon = lon_node->getDoubleValue(); lat = lat_node->getDoubleValue(); elev = elev_node->getDoubleValue(); // Load up models at the start to avoid pausing later // Hack alert - Hardwired paths!! string planepath = "Aircraft/c172p/Models/c172p.xml"; bool _loadedDefaultOK = true; try { _defaultModel = sgLoad3DModel( globals->get_fg_root(), planepath.c_str(), globals->get_props(), globals->get_sim_time_sec() ); } catch(sg_exception&) { _loadedDefaultOK = false; } if(!_loadedDefaultOK ) { // Just load the same 3D model as the default user plane - that's *bound* to exist! // TODO - implement robust determination of availability of GA AI aircraft models planepath = "Aircraft/c172p/Models/c172p.ac"; _defaultModel = sgLoad3DModel( globals->get_fg_root(), planepath.c_str(), globals->get_props(), globals->get_sim_time_sec() ); } planepath = "Aircraft/pa28-161/Models/pa28-161.ac"; try { _piperModel = sgLoad3DModel( globals->get_fg_root(), planepath.c_str(), globals->get_props(), globals->get_sim_time_sec() ); } catch(sg_exception&) { _havePiperModel = false; } // go through the $FG_ROOT/ATC directory and find all *.taxi files SGPath path(globals->get_fg_root()); path.append("ATC/"); string dir = path.dir(); string ext; string file, f_ident; int pos; ulDir *d; struct ulDirEnt *de; if ( (d = ulOpenDir( dir.c_str() )) == NULL ) { SG_LOG(SG_ATC, SG_WARN, "cannot open directory " << dir); } else { // load all .taxi files while ( (de = ulReadDir(d)) != NULL ) { file = de->d_name; pos = file.find("."); ext = file.substr(pos + 1); if(ext == "taxi") { f_ident = file.substr(0, pos); const FGAirport *a = fgFindAirportID( f_ident); if(a){ SGBucket sgb(a->getLongitude(), a->getLatitude()); int idx = sgb.gen_index(); if(facilities.find(idx) != facilities.end()) { facilities[idx]->push_back(f_ident); } else { ID_list_type* apts = new ID_list_type; apts->push_back(f_ident); facilities[idx] = apts; } SG_LOG(SG_ATC, SG_BULK, "Mapping " << f_ident << " to bucket " << idx); } } } ulCloseDir(d); } // See if are in range at startup and activate if necessary SearchByPos(15.0); initDone = true; //cout << "AIMgr::init done..." << endl; /* // TESTING FGATCAlignedProjection ortho; ortho.Init(fgGetAirportPos("KEMT"), 205.0); // Guess of rwy19 heading //Point3D ip = ortho.ConvertFromLocal(Point3D(6000, 1000, 1000)); // 90 deg entry //Point3D ip = ortho.ConvertFromLocal(Point3D(-7000, 3000, 1000)); // 45 deg entry Point3D ip = ortho.ConvertFromLocal(Point3D(1000, -7000, 1000)); // straight-in ATC->AIRegisterAirport("KEMT"); FGAIGAVFRTraffic* p = new FGAIGAVFRTraffic(); p->SetModel(_defaultModel); p->Init(ip, "KEMT", GenerateShortForm(GenerateUniqueCallsign())); ai_list.push_back(p); traffic[ident].push_back(p); activated["KEMT"] = 1; */ } void FGAIMgr::bind() { } void FGAIMgr::unbind() { } void FGAIMgr::update(double dt) { if(!initDone) { init(); SG_LOG(SG_ATC, SG_WARN, "Warning - AIMgr::update(...) called before AIMgr::init()"); } //cout << activated.size() << '\n'; Point3D userPos = Point3D(lon_node->getDoubleValue(), lat_node->getDoubleValue(), elev_node->getDoubleValue()); // TODO - make these class variables!! static int i = 0; static int j = 0; // Don't update any planes for first 50 runs through - this avoids some possible initialisation anomalies // Might not need it now we have fade-in though? if(i < 50) { ++i; return; } if(j == 215) { SearchByPos(25.0); j = 0; } else if(j == 200) { // Go through the list of activated airports and remove those out of range //cout << "The following airports have been activated by the AI system:\n"; ai_activated_map_iterator apt_itr = activated.begin(); while(apt_itr != activated.end()) { //cout << "FIRST IS " << (*apt_itr).first << '\n'; if(dclGetHorizontalSeparation(userPos, fgGetAirportPos((*apt_itr).first)) > (35.0 * 1600.0)) { // Then get rid of it and make sure the iterator is left pointing to the next one! string s = (*apt_itr).first; if(traffic.find(s) != traffic.end()) { //cout << "s = " << s << ", traffic[s].size() = " << traffic[s].size() << '\n'; if(!traffic[s].empty()) { apt_itr++; } else { //cout << "Erasing " << (*apt_itr).first << " and traffic" << '\n'; activated.erase(apt_itr); apt_itr = activated.upper_bound(s); traffic.erase(s); } } else { //cout << "Erasing " << (*apt_itr).first << ' ' << (*apt_itr).second << '\n'; activated.erase(apt_itr); apt_itr = activated.upper_bound(s); } } else { apt_itr++; } } } else if(j == 180) { // Go through the list of activated airports and do the random airplane generation ai_traffic_map_iterator it = traffic.begin(); while(it != traffic.end()) { string s = (*it).first; //cout << "s = " << s << " size = " << (*it).second.size() << '\n'; // Only generate extra traffic if within a certain distance of the user, // TODO - maybe take users's tuned freq into account as well. double d = dclGetHorizontalSeparation(userPos, fgGetAirportPos(s)); if(d < (15.0 * 1600.0)) { double cd = 0.0; bool gen = false; //cout << "Size of list is " << (*it).second.size() << " at " << s << '\n'; if((*it).second.size()) { FGAIEntity* e = *((*it).second.rbegin()); // Get the last airplane currently scheduled to arrive at this airport. cd = dclGetHorizontalSeparation(e->GetPos(), fgGetAirportPos(s)); if(cd < (d < 5000 ? 10000 : d + 5000)) { gen = true; } } else { gen = true; cd = 0.0; } if(gen) { //cout << "Generating extra traffic at airport " << s << ", at least " << cd << " meters out\n"; //GenerateSimpleAirportTraffic(s, cd); GenerateSimpleAirportTraffic(s, cd + 3000.0); // The random seems a bit wierd - traffic could get far too bunched without the +3000. // TODO - make the anti-random constant variable depending on the ai-traffic level. } } ++it; } } ++j; //cout << "Size of AI list is " << ai_list.size() << '\n'; // TODO - need to add a check of if any activated airports have gone out of range string rs; // plane to be removed, if one. if(removalList.size()) { rs = *(removalList.begin()); removalList.pop_front(); } else { rs = ""; } // Traverse the list of active planes and run all their update methods // TODO - spread the load - not all planes should need updating every frame. // Note that this will require dt to be calculated for each plane though // since they rely on it to calculate distance travelled. ai_list_itr = ai_list.begin(); while(ai_list_itr != ai_list.end()) { FGAIEntity *e = *ai_list_itr; if(rs.size() && e->GetCallsign() == rs) { //cout << "Removing " << rs << " from ai_list\n"; ai_list_itr = ai_list.erase(ai_list_itr); delete e; // This is a hack - we should deref this plane from the airport count! } else { e->Update(dt); ++ai_list_itr; } } //cout << "Size of AI list is " << ai_list.size() << '\n'; } void FGAIMgr::ScheduleRemoval(const string& s) { //cout << "Scheduling removal of plane " << s << " from AIMgr\n"; removalList.push_back(s); } // Activate AI traffic at an airport void FGAIMgr::ActivateAirport(const string& ident) { ATC->AIRegisterAirport(ident); // TODO - need to start the traffic more randomly FGAILocalTraffic* local_traffic = new FGAILocalTraffic; local_traffic->SetModel(_defaultModel.get()); // currently hardwired to cessna. //local_traffic->Init(ident, IN_PATTERN, TAKEOFF_ROLL); local_traffic->Init(GenerateShortForm(GenerateUniqueCallsign()), ident); local_traffic->FlyCircuits(1, true); // Fly 2 circuits with touch & go in between ai_list.push_back(local_traffic); traffic[ident].push_back(local_traffic); //cout << "******** ACTIVATING AIRPORT, ident = " << ident << '\n'; activated[ident] = 1; } // Hack - Generate AI traffic at an airport with no facilities file void FGAIMgr::GenerateSimpleAirportTraffic(const string& ident, double min_dist) { // Ugly hack - don't let VFR Cessnas operate at a hardwired list of major airports // This will go eventually once airport .xml files specify the traffic profile if(ident == "KSFO" || ident == "KDFW" || ident == "EGLL" || ident == "KORD" || ident == "KJFK" || ident == "KMSP" || ident == "KLAX" || ident == "KBOS" || ident == "KEDW" || ident == "KSEA" || ident == "EHAM") { return; } /* // TODO - check for military airports - this should be in the current data. // UGGH - there's no point at the moment - everything is labelled civil in basic.dat! FGAirport a = fgFindAirportID(ident, &a); if(a) { cout << "CODE IS " << a.code << '\n'; } else { // UG - can't find the airport! return; } */ Point3D aptpos = fgGetAirportPos(ident); // TODO - check for elev of -9999 //cout << "ident = " << ident << ", elev = " << aptpos.elev() << '\n'; // Operate from airports at 3000ft and below only to avoid the default cloud layers and since we don't degrade AI performance with altitude. if(aptpos.elev() > 3000) { //cout << "High alt airports not yet supported - returning\n"; return; } // Rough hack for plane type - make 70% of the planes cessnas, the rest pipers. bool cessna = true; // Get the time and only operate VFR in the (approximate) daytime. struct tm *t = globals->get_time_params()->getGmt(); int loc_time = t->tm_hour + int(aptpos.lon() / (360.0 / 24.0)); while (loc_time < 0) loc_time += 24; while (loc_time >= 24) loc_time -= 24; //cout << "loc_time = " << loc_time << '\n'; if(loc_time < 7 || loc_time > 19) return; // Check that the visibility is OK for IFR operation. double visibility; FGEnvironment stationweather = ((FGEnvironmentMgr *)globals->get_subsystem("environment")) ->getEnvironment(aptpos.lat(), aptpos.lon(), aptpos.elev()); // TODO - check whether this should take ft or m for elev. visibility = stationweather.get_visibility_m(); // Technically we can do VFR down to 1 mile (1600m) but that's pretty murky! //cout << "vis = " << visibility << '\n'; if(visibility < 3000) return; ATC->AIRegisterAirport(ident); // Next - get the distance from user to the airport. Point3D userpos = Point3D(lon_node->getDoubleValue(), lat_node->getDoubleValue(), elev_node->getDoubleValue()); double d = dclGetHorizontalSeparation(userpos, aptpos); // in meters int lev = fgGetInt("/sim/ai-traffic/level"); if(lev < 1) return; if (lev > 3) lev = 3; if(visibility < 6000) lev = 1; //cout << "level = " << lev << '\n'; // Next - generate any local / circuit traffic /* // --------------------------- THIS BLOCK IS JUST FOR TESTING - COMMENT OUT BEFORE RELEASE --------------- // Finally - generate VFR approaching traffic //if(d > 2000) { if(ident == "KPOC") { double ad = 2000.0; double avd = 3000.0; // average spacing of arriving traffic in meters - relate to airport business and AI density setting one day! //while(ad < (d < 10000 ? 12000 : d + 2000)) { for(int i=0; i<8; ++i) { double dd = sg_random() * avd; // put a minimum spacing in for now since I don't think tower will cope otherwise! if(dd < 1500) dd = 1500; //ad += dd; ad += dd; double dir = int(sg_random() * 36); if(dir == 36) dir--; dir *= 10; //dir = 180; if(sg_random() < 0.3) cessna = false; else cessna = true; string s = GenerateShortForm(GenerateUniqueCallsign(), (cessna ? "Cessna-" : "Piper-")); FGAIGAVFRTraffic* t = new FGAIGAVFRTraffic(); t->SetModel(cessna ? _defaultModel : _piperModel); //cout << "Generating VFR traffic " << s << " inbound to " << ident << " " << ad << " meters out from " << dir << " degrees\n"; Point3D tpos = dclUpdatePosition(aptpos, dir, 6.0, ad); if(tpos.elev() > (aptpos.elev() + 3000.0)) tpos.setelev(aptpos.elev() + 3000.0); t->Init(tpos, ident, s); ai_list.push_back(t); } } activated[ident] = 1; return; //--------------------------------------------------------------------------------------------------- */ double ad; // Minimum distance out of first arriving plane in meters. double mind; // Minimum spacing of traffic in meters double avd; // average spacing of arriving traffic in meters - relate to airport business and AI density setting one day! // Finally - generate VFR approaching traffic //if(d > 2000) { if(1) { if(lev == 3) { ad = 5000.0; mind = 2000.0; avd = 6000.0; } else if(lev == 2) { ad = 8000.0; mind = 4000.0; avd = 10000.0; } else { ad = 9000.0; // Start the first aircraft at least 9K out for now. mind = 6000.0; avd = 15000.0; } /* // Check if there is already arriving traffic at this airport cout << "BING A " << ident << '\n'; if(traffic.find(ident) != traffic.end()) { cout << "BING B " << ident << '\n'; ai_list_type lst = traffic[ident]; cout << "BING C " << ident << '\n'; if(lst.size()) { cout << "BING D " << ident << '\n'; double cd = dclGetHorizontalSeparation(aptpos, (*lst.rbegin())->GetPos()); cout << "ident = " << ident << ", cd = " << cd << '\n'; if(cd > ad) ad = cd; } } */ if(min_dist != 0) ad = min_dist; //cout << "ident = " << ident << ", ad = " << ad << '\n'; while(ad < (d < 5000 ? 15000 : d + 10000)) { double dd = mind + (sg_random() * (avd - mind)); ad += dd; double dir = int(sg_random() * 36); if(dir == 36) dir--; dir *= 10; if(sg_random() < 0.3) cessna = false; else cessna = true; string s = GenerateShortForm(GenerateUniqueCallsign(), (cessna ? "Cessna-" : "Piper-")); FGAIGAVFRTraffic* t = new FGAIGAVFRTraffic(); t->SetModel(cessna ? _defaultModel.get() : (_havePiperModel ? _piperModel.get() : _defaultModel.get())); //cout << "Generating VFR traffic " << s << " inbound to " << ident << " " << ad << " meters out from " << dir << " degrees\n"; Point3D tpos = dclUpdatePosition(aptpos, dir, 6.0, ad); if(tpos.elev() > (aptpos.elev() + 3000.0)) tpos.setelev(aptpos.elev() + 3000.0); // FEET yuk :-( t->Init(tpos, ident, s); ai_list.push_back(t); traffic[ident].push_back(t); } } } /* // Generate a VFR arrival at airport apt, at least distance d (meters) out. void FGAIMgr::GenerateVFRArrival(const string& apt, double d) { } */ // Search for valid airports in the vicinity of the user and activate them if necessary void FGAIMgr::SearchByPos(double range) { //cout << "In SearchByPos(...)" << endl; // get bucket number for plane position lon = lon_node->getDoubleValue(); lat = lat_node->getDoubleValue(); elev = elev_node->getDoubleValue() * SG_FEET_TO_METER; SGBucket buck(lon, lat); // get neigboring buckets int bx = (int)( range*SG_NM_TO_METER / buck.get_width_m() / 2); //cout << "bx = " << bx << endl; int by = (int)( range*SG_NM_TO_METER / buck.get_height_m() / 2 ); //cout << "by = " << by << endl; // Search for airports with facitities files -------------------------- // loop over bucket range for ( int i=-bx; i<=bx; i++) { //cout << "i loop\n"; for ( int j=-by; j<=by; j++) { //cout << "j loop\n"; buck = sgBucketOffset(lon, lat, i, j); long int bucket = buck.gen_index(); //cout << "bucket is " << bucket << endl; if(facilities.find(bucket) != facilities.end()) { ID_list_type* apts = facilities[bucket]; ID_list_iterator current = apts->begin(); ID_list_iterator last = apts->end(); //cout << "Size of apts is " << apts->size() << endl; //double rlon = lon * SGD_DEGREES_TO_RADIANS; //double rlat = lat * SGD_DEGREES_TO_RADIANS; //Point3D aircraft = sgGeodToCart( Point3D(rlon, rlat, elev) ); //Point3D airport; for(; current != last; ++current) { //cout << "Found " << *current << endl;; if(activated.find(*current) == activated.end()) { //cout << "Activating " << *current << endl; //FGAirport a; //if(dclFindAirportID(*current, &a)) { // // We can do something here based on distance from the user if we wish. //} //string s = *current; //cout << "s = " << s << '\n'; ActivateAirport(*current); //ActivateSimpleAirport(*current); // TODO - put this back to ActivateAirport when that code is done. //cout << "Activation done" << endl; } else { //cout << *current << " already activated" << endl; } } } } } //------------------------------------------------------------- // Search for any towered airports in the vicinity ------------ comm_list_type towered; comm_list_iterator twd_itr; int num_twd = current_commlist->FindByPos(lon, lat, elev, range, &towered, TOWER); if (num_twd != 0) { double closest = 1000000; string s = ""; for(twd_itr = towered.begin(); twd_itr != towered.end(); twd_itr++) { // Only activate the closest airport not already activated each time. if(activated.find(twd_itr->ident) == activated.end()) { double sep = dclGetHorizontalSeparation(Point3D(lon, lat, elev), fgGetAirportPos(twd_itr->ident)); if(sep < closest) { closest = sep; s = twd_itr->ident; } } } if(s.size()) { // TODO - find out why empty strings come through here when all in-range airports done. GenerateSimpleAirportTraffic(s); //cout << "**************ACTIVATING SIMPLE AIRPORT, ident = " << s << '\n'; activated[s] = 1; } } } string FGAIMgr::GenerateCallsign() { // For now we'll just generate US callsigns until we can regionally identify airports. string s = "N"; // Add 3 to 5 numbers and make up to 5 with letters. //sg_srandom_time(); double d = sg_random(); int n = int(d * 3); if(n == 3) --n; //cout << "First n, n = " << n << '\n'; int j = 3 + n; //cout << "j = " << j << '\n'; for(int i=0; i0; --i) { char c = callsign[callsign.size() - i]; //cout << c << '\n'; string tmp = ""; tmp += c; if(isalpha(c)) s += GetPhoneticIdent(c); else s += ConvertNumToSpokenDigits(tmp); if(i > 1) s += '-'; } return(s); }