1 // AIMgr.cxx - implementation of FGAIMgr
2 // - a global management class for FlightGear generated AI traffic
4 // Written by David Luff, started March 2002.
6 // Copyright (C) 2002 David C Luff - david.luff@nottingham.ac.uk
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // General Public License for more details.
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
28 #include <Main/fg_props.hxx>
29 #include <Main/globals.hxx>
30 #include <simgear/misc/sg_path.hxx>
31 #include <simgear/math/sg_random.h>
32 #include <simgear/scene/model/modellib.hxx>
38 # include <sys/types.h> // for directory reading
39 # include <dirent.h> // for directory reading
42 #include <Environment/environment_mgr.hxx>
43 #include <Environment/environment.hxx>
46 #include "AILocalTraffic.hxx"
47 #include "AIGAVFRTraffic.hxx"
48 #include "ATCutils.hxx"
49 #include "commlist.hxx"
54 using namespace simgear;
56 // extern from Airports/simple.cxx
57 extern SGGeod fgGetAirportPos( const std::string& id );
60 ATC = globals->get_ATC_mgr();
62 ai_callsigns_used["GFS"] = 1; // so we don't inadvertently use this
63 // TODO - use the proper user callsign when it becomes user settable.
66 _havePiperModel = true;
70 for (ai_list_itr = ai_list.begin(); ai_list_itr != ai_list.end(); ai_list_itr++) {
71 delete (*ai_list_itr);
75 void FGAIMgr::init() {
76 //cout << "AIMgr::init called..." << endl;
78 // Pointers to user's position
79 lon_node = fgGetNode("/position/longitude-deg", true);
80 lat_node = fgGetNode("/position/latitude-deg", true);
81 elev_node = fgGetNode("/position/altitude-ft", true);
83 // Load up models at the start to avoid pausing later
84 // Hack alert - Hardwired paths!!
85 string planepath = "Aircraft/c172p/Models/c172p.xml";
86 bool _loadedDefaultOK = true;
88 _defaultModel = SGModelLib::loadPagedModel(planepath.c_str(), globals->get_props());
89 } catch(sg_exception&) {
90 _loadedDefaultOK = false;
93 if(!_loadedDefaultOK ) {
94 // Just load the same 3D model as the default user plane - that's *bound* to exist!
95 // TODO - implement robust determination of availability of GA AI aircraft models
96 planepath = "Aircraft/c172p/Models/c172p.ac";
97 _defaultModel = SGModelLib::loadPagedModel(planepath.c_str(), globals->get_props());
100 planepath = "Aircraft/pa28-161/Models/pa28-161.ac";
102 _piperModel = SGModelLib::loadPagedModel(planepath.c_str(), globals->get_props());
103 } catch(sg_exception&) {
104 _havePiperModel = false;
107 // go through the $FG_ROOT/ATC directory and find all *.taxi files
108 SGPath path(globals->get_fg_root());
110 string dir = path.dir();
112 string file, f_ident;
118 if ( (d = ulOpenDir( dir.c_str() )) == NULL ) {
119 SG_LOG(SG_ATC, SG_WARN, "cannot open directory " << dir);
121 // load all .taxi files
122 while ( (de = ulReadDir(d)) != NULL ) {
124 pos = file.find(".");
125 ext = file.substr(pos + 1);
127 f_ident = file.substr(0, pos);
128 const FGAirport *a = fgFindAirportID( f_ident);
130 SGBucket sgb(a->getLongitude(), a->getLatitude());
131 int idx = sgb.gen_index();
132 if(facilities.find(idx) != facilities.end()) {
133 facilities[idx]->push_back(f_ident);
135 ID_list_type* apts = new ID_list_type;
136 apts->push_back(f_ident);
137 facilities[idx] = apts;
139 SG_LOG(SG_ATC, SG_BULK, "Mapping " << f_ident << " to bucket " << idx);
146 // See if are in range at startup and activate if necessary
152 void FGAIMgr::bind() {
155 void FGAIMgr::unbind() {
158 void FGAIMgr::update(double dt) {
161 SG_LOG(SG_ATC, SG_WARN, "Warning - AIMgr::update(...) called before AIMgr::init()");
164 //cout << activated.size() << '\n';
166 SGGeod userPos = SGGeod::fromDegM(lon_node->getDoubleValue(), lat_node->getDoubleValue(), elev_node->getDoubleValue());
168 // TODO - make these class variables!!
172 // Don't update any planes for first 50 runs through - this avoids some possible initialisation anomalies
173 // Might not need it now we have fade-in though?
182 } else if(j == 200) {
183 // Go through the list of activated airports and remove those out of range
184 //cout << "The following airports have been activated by the AI system:\n";
185 ai_activated_map_iterator apt_itr = activated.begin();
186 while(apt_itr != activated.end()) {
187 //cout << "FIRST IS " << (*apt_itr).first << '\n';
188 if(dclGetHorizontalSeparation(userPos, fgGetAirportPos((*apt_itr).first)) > (35.0 * 1600.0)) {
189 // Then get rid of it and make sure the iterator is left pointing to the next one!
190 string s = (*apt_itr).first;
191 if(traffic.find(s) != traffic.end()) {
192 //cout << "s = " << s << ", traffic[s].size() = " << traffic[s].size() << '\n';
193 if(!traffic[s].empty()) {
196 //cout << "Erasing " << (*apt_itr).first << " and traffic" << '\n';
197 activated.erase(apt_itr);
198 apt_itr = activated.upper_bound(s);
202 //cout << "Erasing " << (*apt_itr).first << ' ' << (*apt_itr).second << '\n';
203 activated.erase(apt_itr);
204 apt_itr = activated.upper_bound(s);
210 } else if(j == 180) {
211 // Go through the list of activated airports and do the random airplane generation
212 ai_traffic_map_iterator it = traffic.begin();
213 while(it != traffic.end()) {
214 string s = (*it).first;
215 //cout << "s = " << s << " size = " << (*it).second.size() << '\n';
216 // Only generate extra traffic if within a certain distance of the user,
217 // TODO - maybe take users's tuned freq into account as well.
218 double d = dclGetHorizontalSeparation(userPos, fgGetAirportPos(s));
219 if(d < (15.0 * 1600.0)) {
222 //cout << "Size of list is " << (*it).second.size() << " at " << s << '\n';
223 if((*it).second.size()) {
224 FGAIEntity* e = *((*it).second.rbegin()); // Get the last airplane currently scheduled to arrive at this airport.
225 cd = dclGetHorizontalSeparation(e->getPos(), fgGetAirportPos(s));
226 if(cd < (d < 5000 ? 10000 : d + 5000)) {
234 //cout << "Generating extra traffic at airport " << s << ", at least " << cd << " meters out\n";
235 //GenerateSimpleAirportTraffic(s, cd);
236 GenerateSimpleAirportTraffic(s, cd + 3000.0); // The random seems a bit wierd - traffic could get far too bunched without the +3000.
237 // TODO - make the anti-random constant variable depending on the ai-traffic level.
246 //cout << "Size of AI list is " << ai_list.size() << '\n';
248 // TODO - need to add a check of if any activated airports have gone out of range
250 string rs; // plane to be removed, if one.
251 if(removalList.size()) {
252 rs = *(removalList.begin());
253 removalList.pop_front();
258 // Traverse the list of active planes and run all their update methods
259 // TODO - spread the load - not all planes should need updating every frame.
260 // Note that this will require dt to be calculated for each plane though
261 // since they rely on it to calculate distance travelled.
262 ai_list_itr = ai_list.begin();
263 while(ai_list_itr != ai_list.end()) {
264 FGAIEntity *e = *ai_list_itr;
265 if(rs.size() && e->GetCallsign() == rs) {
266 //cout << "Removing " << rs << " from ai_list\n";
267 ai_list_itr = ai_list.erase(ai_list_itr);
269 // This is a hack - we should deref this plane from the airport count!
276 //cout << "Size of AI list is " << ai_list.size() << '\n';
279 void FGAIMgr::ScheduleRemoval(const string& s) {
280 //cout << "Scheduling removal of plane " << s << " from AIMgr\n";
281 removalList.push_back(s);
284 // Activate AI traffic at an airport
285 void FGAIMgr::ActivateAirport(const string& ident) {
286 ATC->AIRegisterAirport(ident);
287 // TODO - need to start the traffic more randomly
288 FGAILocalTraffic* local_traffic = new FGAILocalTraffic;
289 local_traffic->SetModel(_defaultModel.get()); // currently hardwired to cessna.
290 //local_traffic->Init(ident, IN_PATTERN, TAKEOFF_ROLL);
291 local_traffic->Init(GenerateShortForm(GenerateUniqueCallsign()), ident);
292 local_traffic->FlyCircuits(1, true); // Fly 2 circuits with touch & go in between
293 ai_list.push_back(local_traffic);
294 traffic[ident].push_back(local_traffic);
295 //cout << "******** ACTIVATING AIRPORT, ident = " << ident << '\n';
296 activated[ident] = 1;
299 // Hack - Generate AI traffic at an airport with no facilities file
300 void FGAIMgr::GenerateSimpleAirportTraffic(const string& ident, double min_dist) {
301 // Ugly hack - don't let VFR Cessnas operate at a hardwired list of major airports
302 // This will go eventually once airport .xml files specify the traffic profile
303 if(ident == "KSFO" || ident == "KDFW" || ident == "EGLL" || ident == "KORD" || ident == "KJFK"
304 || ident == "KMSP" || ident == "KLAX" || ident == "KBOS" || ident == "KEDW"
305 || ident == "KSEA" || ident == "EHAM") {
310 // TODO - check for military airports - this should be in the current data.
311 // UGGH - there's no point at the moment - everything is labelled civil in basic.dat!
312 FGAirport a = fgFindAirportID(ident, &a);
314 cout << "CODE IS " << a.code << '\n';
316 // UG - can't find the airport!
321 SGGeod aptpos = fgGetAirportPos(ident); // TODO - check for elev of -9999
322 //cout << "ident = " << ident << ", elev = " << aptpos.getElevationM() << '\n';
324 // Operate from airports at 3000ft and below only to avoid the default cloud layers and since we don't degrade AI performance with altitude.
325 if(aptpos.getElevationM() > 3000) {
326 //cout << "High alt airports not yet supported - returning\n";
330 // Rough hack for plane type - make 70% of the planes cessnas, the rest pipers.
333 // Get the time and only operate VFR in the (approximate) daytime.
334 struct tm *t = globals->get_time_params()->getGmt();
335 int loc_time = t->tm_hour + int(aptpos.getLongitudeDeg() / (360.0 / 24.0));
338 while (loc_time >= 24)
341 //cout << "loc_time = " << loc_time << '\n';
342 if(loc_time < 7 || loc_time > 19) return;
344 // Check that the visibility is OK for IFR operation.
346 FGEnvironment stationweather =
347 ((FGEnvironmentMgr *)globals->get_subsystem("environment"))
348 ->getEnvironment(aptpos.getLatitudeDeg(), aptpos.getLongitudeDeg(), aptpos.getElevationM()); // TODO - check whether this should take ft or m for elev.
349 visibility = stationweather.get_visibility_m();
350 // Technically we can do VFR down to 1 mile (1600m) but that's pretty murky!
351 //cout << "vis = " << visibility << '\n';
352 if(visibility < 3000) return;
354 ATC->AIRegisterAirport(ident);
356 // Next - get the distance from user to the airport.
357 SGGeod userpos = SGGeod::fromDegM(lon_node->getDoubleValue(), lat_node->getDoubleValue(), elev_node->getDoubleValue());
358 double d = dclGetHorizontalSeparation(userpos, aptpos); // in meters
360 int lev = fgGetInt("/sim/ai-traffic/level");
365 if(visibility < 6000) lev = 1;
366 //cout << "level = " << lev << '\n';
368 // Next - generate any local / circuit traffic
371 // --------------------------- THIS BLOCK IS JUST FOR TESTING - COMMENT OUT BEFORE RELEASE ---------------
372 // Finally - generate VFR approaching traffic
374 if(ident == "KPOC") {
376 double avd = 3000.0; // average spacing of arriving traffic in meters - relate to airport business and AI density setting one day!
377 //while(ad < (d < 10000 ? 12000 : d + 2000)) {
378 for(int i=0; i<8; ++i) {
379 double dd = sg_random() * avd;
380 // put a minimum spacing in for now since I don't think tower will cope otherwise!
381 if(dd < 1500) dd = 1500;
384 double dir = int(sg_random() * 36);
388 if(sg_random() < 0.3) cessna = false;
390 string s = GenerateShortForm(GenerateUniqueCallsign(), (cessna ? "Cessna-" : "Piper-"));
391 FGAIGAVFRTraffic* t = new FGAIGAVFRTraffic();
392 t->SetModel(cessna ? _defaultModel : _piperModel);
393 //cout << "Generating VFR traffic " << s << " inbound to " << ident << " " << ad << " meters out from " << dir << " degrees\n";
394 SGGeod tpos = dclUpdatePosition(aptpos, dir, 6.0, ad);
395 if(tpos.getElevationM() > (aptpos.getElevationM() + 3000.0)) tpos.setElevationM(aptpos.getElevationM() + 3000.0);
396 t->Init(tpos, ident, s);
397 ai_list.push_back(t);
400 activated[ident] = 1;
402 //---------------------------------------------------------------------------------------------------
405 double ad; // Minimum distance out of first arriving plane in meters.
406 double mind; // Minimum spacing of traffic in meters
407 double avd; // average spacing of arriving traffic in meters - relate to airport business and AI density setting one day!
408 // Finally - generate VFR approaching traffic
415 } else if(lev == 2) {
420 ad = 9000.0; // Start the first aircraft at least 9K out for now.
425 // Check if there is already arriving traffic at this airport
426 cout << "BING A " << ident << '\n';
427 if(traffic.find(ident) != traffic.end()) {
428 cout << "BING B " << ident << '\n';
429 ai_list_type lst = traffic[ident];
430 cout << "BING C " << ident << '\n';
432 cout << "BING D " << ident << '\n';
433 double cd = dclGetHorizontalSeparation(aptpos, (*lst.rbegin())->GetPos());
434 cout << "ident = " << ident << ", cd = " << cd << '\n';
439 if(min_dist != 0) ad = min_dist;
440 //cout << "ident = " << ident << ", ad = " << ad << '\n';
441 while(ad < (d < 5000 ? 15000 : d + 10000)) {
442 double dd = mind + (sg_random() * (avd - mind));
444 double dir = int(sg_random() * 36);
448 if(sg_random() < 0.3) cessna = false;
450 string s = GenerateShortForm(GenerateUniqueCallsign(), (cessna ? "Cessna-" : "Piper-"));
451 FGAIGAVFRTraffic* t = new FGAIGAVFRTraffic();
452 t->SetModel(cessna ? _defaultModel.get() : (_havePiperModel ? _piperModel.get() : _defaultModel.get()));
453 //cout << "Generating VFR traffic " << s << " inbound to " << ident << " " << ad << " meters out from " << dir << " degrees\n";
454 SGGeod tpos = dclUpdatePosition(aptpos, dir, 6.0, ad);
455 if(tpos.getElevationM() > (aptpos.getElevationM() + 3000.0)) tpos.setElevationM(aptpos.getElevationM() + 3000.0); // FEET yuk :-(
456 t->Init(tpos, ident, s);
457 ai_list.push_back(t);
458 traffic[ident].push_back(t);
464 // Generate a VFR arrival at airport apt, at least distance d (meters) out.
465 void FGAIMgr::GenerateVFRArrival(const string& apt, double d) {
469 // Search for valid airports in the vicinity of the user and activate them if necessary
470 void FGAIMgr::SearchByPos(double range) {
471 //cout << "In SearchByPos(...)" << endl;
473 // get bucket number for plane position
474 _userAircraftPos = SGGeod::fromDegFt(lon_node->getDoubleValue(),
475 lat_node->getDoubleValue(), elev_node->getDoubleValue());
476 SGBucket buck(_userAircraftPos);
478 // get neigboring buckets
479 int bx = (int)( range*SG_NM_TO_METER / buck.get_width_m() / 2);
480 //cout << "bx = " << bx << endl;
481 int by = (int)( range*SG_NM_TO_METER / buck.get_height_m() / 2 );
482 //cout << "by = " << by << endl;
484 // Search for airports with facitities files --------------------------
485 // loop over bucket range
486 for ( int i=-bx; i<=bx; i++) {
487 //cout << "i loop\n";
488 for ( int j=-by; j<=by; j++) {
489 //cout << "j loop\n";
490 buck = sgBucketOffset(_userAircraftPos.getLongitudeDeg(), _userAircraftPos.getLatitudeDeg(), i, j);
491 long int bucket = buck.gen_index();
492 //cout << "bucket is " << bucket << endl;
493 if(facilities.find(bucket) != facilities.end()) {
494 ID_list_type* apts = facilities[bucket];
495 ID_list_iterator current = apts->begin();
496 ID_list_iterator last = apts->end();
498 //cout << "Size of apts is " << apts->size() << endl;
500 //double rlon = lon * SGD_DEGREES_TO_RADIANS;
501 //double rlat = lat * SGD_DEGREES_TO_RADIANS;
502 for(; current != last; ++current) {
503 //cout << "Found " << *current << endl;;
504 if(activated.find(*current) == activated.end()) {
505 //cout << "Activating " << *current << endl;
507 //if(dclFindAirportID(*current, &a)) {
508 // // We can do something here based on distance from the user if we wish.
510 //string s = *current;
511 //cout << "s = " << s << '\n';
512 ActivateAirport(*current);
513 //ActivateSimpleAirport(*current); // TODO - put this back to ActivateAirport when that code is done.
514 //cout << "Activation done" << endl;
516 //cout << *current << " already activated" << endl;
522 //-------------------------------------------------------------
524 // Search for any towered airports in the vicinity ------------
525 comm_list_type towered;
526 comm_list_iterator twd_itr;
528 int num_twd = current_commlist->FindByPos(_userAircraftPos, range, &towered, TOWER);
530 double closest = 1000000;
532 for(twd_itr = towered.begin(); twd_itr != towered.end(); twd_itr++) {
533 // Only activate the closest airport not already activated each time.
534 if(activated.find(twd_itr->ident) == activated.end()) {
535 double sep = dclGetHorizontalSeparation(_userAircraftPos, fgGetAirportPos(twd_itr->ident));
544 // TODO - find out why empty strings come through here when all in-range airports done.
545 GenerateSimpleAirportTraffic(s);
546 //cout << "**************ACTIVATING SIMPLE AIRPORT, ident = " << s << '\n';
552 string FGAIMgr::GenerateCallsign() {
553 // For now we'll just generate US callsigns until we can regionally identify airports.
555 // Add 3 to 5 numbers and make up to 5 with letters.
557 double d = sg_random();
560 //cout << "First n, n = " << n << '\n';
562 //cout << "j = " << j << '\n';
563 for(int i=0; i<j; ++i) {
564 int n = int(sg_random() * 10);
566 s += (char)('0' + n);
568 for(int i=j; i<5; ++i) {
569 int n = int(sg_random() * 26);
571 //cout << "Alpha, n = " << n << '\n';
572 s += (char)('A' + n);
574 //cout << "s = " << s << '\n';
578 string FGAIMgr::GenerateUniqueCallsign() {
580 string s = GenerateCallsign();
581 if(!ai_callsigns_used[s]) {
582 ai_callsigns_used[s] = 1;
588 // This will be moved somewhere else eventually!!!!
589 string FGAIMgr::GenerateShortForm(const string& callsign, const string& plane_str, bool local) {
590 //cout << callsign << '\n';
592 if(local) s = "Trainer-";
594 for(int i=3; i>0; --i) {
595 char c = callsign[callsign.size() - i];
599 if(isalpha(c)) s += GetPhoneticLetter(c);
600 else s += ConvertNumToSpokenDigits(tmp);