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., 675 Mass Ave, Cambridge, MA 02139, USA.
22 #include <simgear/misc/sg_path.hxx>
24 #include <Main/fg_props.hxx>
25 #include <Main/globals.hxx>
26 #include <simgear/math/sg_random.h>
33 # include <sys/types.h> // for directory reading
34 # include <dirent.h> // for directory reading
38 # include <WeatherCM/FGLocalWeatherDatabase.h>
40 # include <Environment/environment_mgr.hxx>
41 # include <Environment/environment.hxx>
45 #include "AILocalTraffic.hxx"
46 #include "AIGAVFRTraffic.hxx"
47 #include "ATCutils.hxx"
48 #include "commlist.hxx"
54 ATC = globals->get_ATC_mgr();
56 ai_callsigns_used["CFGFS"] = 1; // so we don't inadvertently use this
57 // TODO - use the proper user callsign when it becomes user settable.
65 void FGAIMgr::init() {
66 //cout << "AIMgr::init called..." << endl;
68 // Pointers to user's position
69 lon_node = fgGetNode("/position/longitude-deg", true);
70 lat_node = fgGetNode("/position/latitude-deg", true);
71 elev_node = fgGetNode("/position/altitude-ft", true);
73 lon = lon_node->getDoubleValue();
74 lat = lat_node->getDoubleValue();
75 elev = elev_node->getDoubleValue();
77 // Load up models at the start to avoid pausing later
78 // Hack alert - Hardwired paths!!
79 string planepath = "Aircraft/c172/Models/c172-dpm.ac";
80 _defaultModel = sgLoad3DModel( globals->get_fg_root(),
83 globals->get_sim_time_sec() );
85 planepath = "Aircraft/pa28-161/Models/pa28-161.ac";
86 _piperModel = sgLoad3DModel( globals->get_fg_root(),
89 globals->get_sim_time_sec() );
91 // go through the $FG_ROOT/ATC directory and find all *.taxi files
92 SGPath path(globals->get_fg_root());
94 string dir = path.dir();
99 // WARNING - I (DCL) haven't tested this on MSVC - this is simply cribbed from TerraGear
102 struct _finddata_t de;
105 path_str = dir + "\\*.*";
107 if ( ( hfile = _findfirst( path.c_str(), &de ) ) == -1 ) {
108 SG_LOG(SG_ATC, SG_WARN, "cannot open directory " << dir);
110 // load all .taxi files
113 pos = file.find(".");
114 ext = file.substr(pos + 1);
116 f_ident = file.substr(0, pos);
118 if(dclFindAirportID(f_ident, &a)) {
119 SGBucket sgb(a.longitude, a.latitude);
120 int idx = sgb.gen_index();
121 if(facilities.find(idx) != facilities.end()) {
122 facilities[idx]->push_back(f_ident);
124 aptID_list_type* apts = new aptID_list_type;
125 apts->push_back(f_ident);
126 facilities[idx] = apts;
128 SG_LOG(SG_ATC, SG_BULK, "Mapping " << f_ident << " to bucket " << idx);
131 } while ( _findnext( hfile, &de ) == 0 );
138 if ( (d = opendir( dir.c_str() )) == NULL ) {
139 SG_LOG(SG_ATC, SG_WARN, "cannot open directory " << dir);
141 // load all .taxi files
142 while ( (de = readdir(d)) != NULL ) {
144 pos = file.find(".");
145 ext = file.substr(pos + 1);
147 f_ident = file.substr(0, pos);
149 if(dclFindAirportID(f_ident, &a)) {
150 SGBucket sgb(a.longitude, a.latitude);
151 int idx = sgb.gen_index();
152 if(facilities.find(idx) != facilities.end()) {
153 facilities[idx]->push_back(f_ident);
155 ID_list_type* apts = new ID_list_type;
156 apts->push_back(f_ident);
157 facilities[idx] = apts;
159 SG_LOG(SG_ATC, SG_BULK, "Mapping " << f_ident << " to bucket " << idx);
167 // See if are in range at startup and activate if necessary
172 //cout << "AIMgr::init done..." << endl;
176 FGATCAlignedProjection ortho;
177 ortho.Init(dclGetAirportPos("KEMT"), 205.0); // Guess of rwy19 heading
178 //Point3D ip = ortho.ConvertFromLocal(Point3D(6000, 1000, 1000)); // 90 deg entry
179 //Point3D ip = ortho.ConvertFromLocal(Point3D(-7000, 3000, 1000)); // 45 deg entry
180 Point3D ip = ortho.ConvertFromLocal(Point3D(1000, -7000, 1000)); // straight-in
181 ATC->AIRegisterAirport("KEMT");
182 FGAIGAVFRTraffic* p = new FGAIGAVFRTraffic();
183 p->SetModel(_defaultModel);
184 p->Init(ip, "KEMT", GenerateShortForm(GenerateUniqueCallsign()));
185 ai_list.push_back(p);
186 traffic[ident].push_back(p);
187 activated["KEMT"] = 1;
191 void FGAIMgr::bind() {
194 void FGAIMgr::unbind() {
197 void FGAIMgr::update(double dt) {
200 SG_LOG(SG_ATC, SG_WARN, "Warning - AIMgr::update(...) called before AIMgr::init()");
203 //cout << activated.size() << '\n';
205 Point3D userPos = Point3D(lon_node->getDoubleValue(), lat_node->getDoubleValue(), elev_node->getDoubleValue());
207 // TODO - make these class variables!!
211 // Don't update any planes for first 50 runs through - this avoids some possible initialisation anomalies
212 // Might not need it now we have fade-in though?
221 } else if(j == 200) {
222 // Go through the list of activated airports and remove those out of range
223 //cout << "The following airports have been activated by the AI system:\n";
224 ai_activated_map_iterator apt_itr = activated.begin();
225 while(apt_itr != activated.end()) {
226 //cout << "FIRST IS " << (*apt_itr).first << '\n';
227 if(dclGetHorizontalSeparation(userPos, dclGetAirportPos((*apt_itr).first)) > (35.0 * 1600.0)) {
228 // Then get rid of it and make sure the iterator is left pointing to the next one!
229 string s = (*apt_itr).first;
230 if(traffic.find(s) != traffic.end()) {
231 //cout << "s = " << s << ", traffic[s].size() = " << traffic[s].size() << '\n';
232 if(traffic[s].size()) {
235 //cout << "Erasing " << (*apt_itr).first << " and traffic" << '\n';
236 activated.erase(apt_itr++);
240 //cout << "Erasing " << (*apt_itr).first << ' ' << (*apt_itr).second << '\n';
241 activated.erase(apt_itr++);
247 } else if(j == 180) {
248 // Go through the list of activated airports and do the random airplane generation
249 ai_traffic_map_iterator it = traffic.begin();
250 while(it != traffic.end()) {
251 string s = (*it).first;
252 //cout << "s = " << s << " size = " << (*it).second.size() << '\n';
253 // Only generate extra traffic if within a certain distance of the user,
254 // TODO - maybe take users's tuned freq into account as well.
255 double d = dclGetHorizontalSeparation(userPos, dclGetAirportPos(s));
256 if(d < (15.0 * 1600.0)) {
259 //cout << "Size of list is " << (*it).second.size() << " at " << s << '\n';
260 if((*it).second.size()) {
261 FGAIEntity* e = *((*it).second.rbegin());
262 cd = dclGetHorizontalSeparation(e->GetPos(), dclGetAirportPos(s));
263 if(cd < (d < 5000 ? 10000 : d + 5000)) {
271 //cout << "Generating extra traffic at airport " << s << ", at least " << cd << " meters out\n";
272 //GenerateSimpleAirportTraffic(s, cd);
273 GenerateSimpleAirportTraffic(s, cd + 2000.0); // The random seems a bit wierd - traffic could get far too bunched without the +2000.
282 //cout << "Size of AI list is " << ai_list.size() << '\n';
284 // TODO - need to add a check of if any activated airports have gone out of range
286 string rs; // plane to be removed, if one.
287 if(removalList.size()) {
288 rs = *(removalList.begin());
289 removalList.pop_front();
294 // Traverse the list of active planes and run all their update methods
295 // TODO - spread the load - not all planes should need updating every frame.
296 // Note that this will require dt to be calculated for each plane though
297 // since they rely on it to calculate distance travelled.
298 ai_list_itr = ai_list.begin();
299 while(ai_list_itr != ai_list.end()) {
300 FGAIEntity *e = *ai_list_itr;
301 if(rs.size() && e->GetCallsign() == rs) {
302 //cout << "Removing " << rs << " from ai_list\n";
303 ai_list_itr = ai_list.erase(ai_list_itr);
305 // This is a hack - we should deref this plane from the airport count!
312 //cout << "Size of AI list is " << ai_list.size() << '\n';
315 void FGAIMgr::ScheduleRemoval(string s) {
316 //cout << "Scheduling removal of plane " << s << " from AIMgr\n";
317 removalList.push_back(s);
320 // Activate AI traffic at an airport
321 void FGAIMgr::ActivateAirport(string ident) {
322 ATC->AIRegisterAirport(ident);
323 // TODO - need to start the traffic more randomly
324 FGAILocalTraffic* local_traffic = new FGAILocalTraffic;
325 //local_traffic->Init(ident, IN_PATTERN, TAKEOFF_ROLL);
326 local_traffic->Init(GenerateShortForm(GenerateUniqueCallsign()), ident);
327 local_traffic->FlyCircuits(1, true); // Fly 2 circuits with touch & go in between
328 ai_list.push_back(local_traffic);
329 traffic[ident].push_back(local_traffic);
330 //cout << "******** ACTIVATING AIRPORT, ident = " << ident << '\n';
331 activated[ident] = 1;
334 // Hack - Generate AI traffic at an airport with no facilities file
335 void FGAIMgr::GenerateSimpleAirportTraffic(string ident, double min_dist) {
336 // Ugly hack - don't let VFR Cessnas operate at a hardwired list of major airports
337 // This will go eventually once airport .xml files specify the traffic profile
338 if(ident == "KSFO" || ident == "KDFW" || ident == "EGLL" || ident == "KORD" || ident == "KJFK"
339 || ident == "KMSP" || ident == "KLAX" || ident == "KBOS" || ident == "KEDW"
340 || ident == "KSEA" || ident == "EHAM") {
345 // TODO - check for military airports - this should be in the current data.
346 // UGGH - there's no point at the moment - everything is labelled civil in basic.dat!
348 if(dclFindAirportID(ident, &a)) {
349 cout << "CODE IS " << a.code << '\n';
351 // UG - can't find the airport!
356 Point3D aptpos = dclGetAirportPos(ident); // TODO - check for elev of -9999
357 //cout << "ident = " << ident << ", elev = " << aptpos.elev() << '\n';
359 // Operate from airports at 3000ft and below only to avoid the default cloud layers and since we don't degrade AI performance with altitude.
360 if(aptpos.elev() > 3000) {
361 //cout << "High alt airports not yet supported - returning\n";
365 // Rough hack for plane type - make 70% of the planes cessnas, the rest pipers.
368 // Get the time and only operate VFR in the (approximate) daytime.
369 //SGTime *t = globals->get_time_params();
370 string time_str = fgGetString("sim/time/gmt-string");
371 int loc_time = atoi((time_str.substr(0,3)).c_str());
372 //cout << "gmt_time = " << loc_time << '\n';
373 loc_time += (int)((aptpos.lon() / 360.0) * 24.0);
374 while(loc_time < 0) loc_time += 24;
375 while(loc_time > 24) loc_time -= 24;
376 //cout << "loc_time = " << loc_time << '\n';
377 if(loc_time < 7 || loc_time > 19) return;
379 // Check that the visibility is OK for IFR operation.
382 //sgVec3 position = { aptpos.lat(), aptpos.lon(), aptpos.elev() };
383 //FGPhysicalProperty stationweather = WeatherDatabase->get(position);
385 FGEnvironment stationweather =
386 ((FGEnvironmentMgr *)globals->get_subsystem("environment"))
387 ->getEnvironment(aptpos.lat(), aptpos.lon(), aptpos.elev()); // TODO - check whether this should take ft or m for elev.
390 visibility = fgGetDouble("/environment/visibility-m");
392 visibility = stationweather.get_visibility_m();
394 // Technically we can do VFR down to 1 mile (1600m) but that's pretty murky!
395 //cout << "vis = " << visibility << '\n';
396 if(visibility < 3000) return;
398 ATC->AIRegisterAirport(ident);
400 // Next - get the distance from user to the airport.
401 Point3D userpos = Point3D(lon_node->getDoubleValue(), lat_node->getDoubleValue(), elev_node->getDoubleValue());
402 double d = dclGetHorizontalSeparation(userpos, aptpos); // in meters
404 int lev = fgGetInt("/sim/ai-traffic/level");
405 if(lev < 1 || lev > 3) lev = 2;
406 if(visibility < 6000) lev = 1;
407 //cout << "level = " << lev << '\n';
409 // Next - generate any local / circuit traffic
412 // --------------------------- THIS BLOCK IS JUST FOR TESTING - COMMENT OUT BEFORE RELEASE ---------------
413 // Finally - generate VFR approaching traffic
415 if(ident == "KPOC") {
417 double avd = 3000.0; // average spacing of arriving traffic in meters - relate to airport business and AI density setting one day!
418 //while(ad < (d < 10000 ? 12000 : d + 2000)) {
419 for(int i=0; i<8; ++i) {
420 double dd = sg_random() * avd;
421 // put a minimum spacing in for now since I don't think tower will cope otherwise!
422 if(dd < 1500) dd = 1500;
425 double dir = int(sg_random() * 36);
429 if(sg_random() < 0.3) cessna = false;
431 string s = GenerateShortForm(GenerateUniqueCallsign(), (cessna ? "Cessna-" : "Piper-"));
432 FGAIGAVFRTraffic* t = new FGAIGAVFRTraffic();
433 t->SetModel(cessna ? _defaultModel : _piperModel);
434 //cout << "Generating VFR traffic " << s << " inbound to " << ident << " " << ad << " meters out from " << dir << " degrees\n";
435 Point3D tpos = dclUpdatePosition(aptpos, dir, 6.0, ad);
436 if(tpos.elev() > (aptpos.elev() + 3000.0)) tpos.setelev(aptpos.elev() + 3000.0);
437 t->Init(tpos, ident, s);
438 ai_list.push_back(t);
441 activated[ident] = 1;
443 //---------------------------------------------------------------------------------------------------
446 double ad; // Minimum distance out of first arriving plane in meters.
447 double mind; // Minimum spacing of traffic in meters
448 double avd; // average spacing of arriving traffic in meters - relate to airport business and AI density setting one day!
449 // Finally - generate VFR approaching traffic
456 } else if(lev == 2) {
461 ad = 9000.0; // Start the first aircraft at least 9K out for now.
466 // Check if there is already arriving traffic at this airport
467 cout << "BING A " << ident << '\n';
468 if(traffic.find(ident) != traffic.end()) {
469 cout << "BING B " << ident << '\n';
470 ai_list_type lst = traffic[ident];
471 cout << "BING C " << ident << '\n';
473 cout << "BING D " << ident << '\n';
474 double cd = dclGetHorizontalSeparation(aptpos, (*lst.rbegin())->GetPos());
475 cout << "ident = " << ident << ", cd = " << cd << '\n';
480 if(min_dist != 0) ad = min_dist;
481 //cout << "ident = " << ident << ", ad = " << ad << '\n';
482 while(ad < (d < 5000 ? 15000 : d + 10000)) {
483 double dd = mind + (sg_random() * (avd - mind));
485 double dir = int(sg_random() * 36);
488 if(sg_random() < 0.3) cessna = false;
490 string s = GenerateShortForm(GenerateUniqueCallsign(), (cessna ? "Cessna-" : "Piper-"));
491 FGAIGAVFRTraffic* t = new FGAIGAVFRTraffic();
492 t->SetModel(cessna ? _defaultModel : _piperModel);
493 //cout << "Generating VFR traffic " << s << " inbound to " << ident << " " << ad << " meters out from " << dir << " degrees\n";
494 Point3D tpos = dclUpdatePosition(aptpos, dir, 6.0, ad);
495 if(tpos.elev() > (aptpos.elev() + 3000.0)) tpos.setelev(aptpos.elev() + 3000.0); // FEET yuk :-(
496 t->Init(tpos, ident, s);
497 ai_list.push_back(t);
498 traffic[ident].push_back(t);
504 // Generate a VFR arrival at airport apt, at least distance d (meters) out.
505 void FGAIMgr::GenerateVFRArrival(string apt, double d) {
509 // Search for valid airports in the vicinity of the user and activate them if necessary
510 void FGAIMgr::SearchByPos(double range) {
511 //cout << "In SearchByPos(...)" << endl;
513 // get bucket number for plane position
514 lon = lon_node->getDoubleValue();
515 lat = lat_node->getDoubleValue();
516 elev = elev_node->getDoubleValue() * SG_FEET_TO_METER;
517 SGBucket buck(lon, lat);
519 // get neigboring buckets
520 int bx = (int)( range*SG_NM_TO_METER / buck.get_width_m() / 2);
521 //cout << "bx = " << bx << endl;
522 int by = (int)( range*SG_NM_TO_METER / buck.get_height_m() / 2 );
523 //cout << "by = " << by << endl;
525 // Search for airports with facitities files --------------------------
526 // loop over bucket range
527 for ( int i=-bx; i<=bx; i++) {
528 //cout << "i loop\n";
529 for ( int j=-by; j<=by; j++) {
530 //cout << "j loop\n";
531 buck = sgBucketOffset(lon, lat, i, j);
532 long int bucket = buck.gen_index();
533 //cout << "bucket is " << bucket << endl;
534 if(facilities.find(bucket) != facilities.end()) {
535 ID_list_type* apts = facilities[bucket];
536 ID_list_iterator current = apts->begin();
537 ID_list_iterator last = apts->end();
539 //cout << "Size of apts is " << apts->size() << endl;
541 //double rlon = lon * SGD_DEGREES_TO_RADIANS;
542 //double rlat = lat * SGD_DEGREES_TO_RADIANS;
543 //Point3D aircraft = sgGeodToCart( Point3D(rlon, rlat, elev) );
545 for(; current != last; ++current) {
546 //cout << "Found " << *current << endl;;
547 if(activated.find(*current) == activated.end()) {
548 //cout << "Activating " << *current << endl;
550 //if(dclFindAirportID(*current, &a)) {
551 // // We can do something here based on distance from the user if we wish.
553 //string s = *current;
554 //cout << "s = " << s << '\n';
555 ActivateAirport(*current);
556 //ActivateSimpleAirport(*current); // TODO - put this back to ActivateAirport when that code is done.
557 //cout << "Activation done" << endl;
559 //cout << *current << " already activated" << endl;
565 //-------------------------------------------------------------
567 // Search for any towered airports in the vicinity ------------
568 comm_list_type towered;
569 comm_list_iterator twd_itr;
571 int num_twd = current_commlist->FindByPos(lon, lat, elev, range, &towered, TOWER);
573 double closest = 1000000;
575 for(twd_itr = towered.begin(); twd_itr != towered.end(); twd_itr++) {
576 // Only activate the closest airport not already activated each time.
577 if(activated.find(twd_itr->ident) == activated.end()) {
578 double sep = dclGetHorizontalSeparation(Point3D(lon, lat, elev), dclGetAirportPos(twd_itr->ident));
587 // TODO - find out why empty strings come through here when all in-range airports done.
588 GenerateSimpleAirportTraffic(s);
589 //cout << "**************ACTIVATING SIMPLE AIRPORT, ident = " << s << '\n';
595 string FGAIMgr::GenerateCallsign() {
596 // For now we'll just generate US callsigns until we can regionally identify airports.
598 // Add 3 to 5 numbers and make up to 5 with letters.
600 double d = sg_random();
603 //cout << "First n, n = " << n << '\n';
605 //cout << "j = " << j << '\n';
606 for(int i=0; i<j; ++i) {
607 int n = int(sg_random() * 10);
609 s += (char)('0' + n);
611 for(int i=j; i<5; ++i) {
612 int n = int(sg_random() * 26);
614 //cout << "Alpha, n = " << n << '\n';
615 s += (char)('A' + n);
617 //cout << "s = " << s << '\n';
621 string FGAIMgr::GenerateUniqueCallsign() {
623 string s = GenerateCallsign();
624 if(!ai_callsigns_used[s]) {
625 ai_callsigns_used[s] = 1;
631 // This will be moved somewhere else eventually!!!!
632 string FGAIMgr::GenerateShortForm(string callsign, string plane_str, bool local) {
633 //cout << callsign << '\n';
635 if(local) s = "Trainer-";
637 for(int i=3; i>0; --i) {
638 char c = callsign[callsign.size() - i];
642 if(isalpha(c)) s += GetPhoneticIdent(c);
643 else s += ConvertNumToSpokenDigits(tmp);