]> git.mxchange.org Git - flightgear.git/blob - AILocalTraffic.cxx
4c3e67c5782233f4bd7835ce06255a03474edd15
[flightgear.git] / AILocalTraffic.cxx
1 // FGAILocalTraffic - AIEntity derived class with enough logic to
2 // fly and interact with the traffic pattern.
3 //
4 // Written by David Luff, started March 2002.
5 //
6 // Copyright (C) 2002  David C. Luff - david.luff@nottingham.ac.uk
7 //
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.
12 //
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.
17 //
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.
21
22 /*==========================================================
23
24 TODO list.
25
26 Should get pattern direction from tower.
27
28 Need to continually monitor and adjust deviation from glideslope
29 during descent to avoid occasionally landing short or long.
30
31 ============================================================*/
32
33 #ifdef HAVE_CONFIG_H
34 #  include <config.h>
35 #endif
36
37 #include <Airports/runways.hxx>
38 #include <Main/globals.hxx>
39 #include <Main/viewer.hxx>
40 #include <Scenery/scenery.hxx>
41 #include <Scenery/tilemgr.hxx>
42 #include <simgear/math/SGMath.hxx>
43 #include <simgear/misc/sg_path.hxx>
44 #include <string>
45 #include <math.h>
46
47 using std::string;
48
49 #include "ATCmgr.hxx"
50 #include "AILocalTraffic.hxx"
51 #include "ATCutils.hxx"
52 #include "AIMgr.hxx"
53
54 FGAILocalTraffic::FGAILocalTraffic() {
55         ATC = globals->get_ATC_mgr();
56         
57         // TODO - unhardwire this
58         plane.type = GA_SINGLE;
59         
60         _roll = 0.0;
61         _pitch = 0.0;
62         _hdg = 270.0;
63         
64         //Hardwire initialisation for now - a lot of this should be read in from config eventually
65         Vr = 70.0;
66         best_rate_of_climb_speed = 70.0;
67         //best_rate_of_climb;
68         //nominal_climb_speed;
69         //nominal_climb_rate;
70         //nominal_circuit_speed;
71         //min_circuit_speed;
72         //max_circuit_speed;
73         nominal_descent_rate = 500.0;
74         nominal_final_speed = 65.0;
75         //nominal_approach_speed;
76         //stall_speed_landing_config;
77         nominalTaxiSpeed = 7.5;
78         taxiTurnRadius = 8.0;
79         wheelOffset = 1.45;     // Warning - hardwired to the C172 - we need to read this in from file.
80         elevInitGood = false;
81         // Init the property nodes
82         wind_from_hdg = fgGetNode("/environment/wind-from-heading-deg", true);
83         wind_speed_knots = fgGetNode("/environment/wind-speed-kt", true);
84         circuitsToFly = 0;
85         liningUp = false;
86         taxiRequestPending = false;
87         taxiRequestCleared = false;
88         holdingShort = false;
89         clearedToLineUp = false;
90         clearedToTakeOff = false;
91         _clearedToLand = false;
92         reportReadyForDeparture = false;
93         contactTower = false;
94         contactGround = false;
95         _taxiToGA = false;
96         _removeSelf = false;
97         
98         descending = false;
99         targetDescentRate = 0.0;
100         goAround = false;
101         goAroundCalled = false;
102         
103         transmitted = false;
104         
105         freeTaxi = false;
106         _savedSlope = 0.0;
107         
108         _controlled = false;
109         
110         _invisible = false;
111
112         ground = NULL;
113         tower = NULL;
114         ourGate = NULL;
115         nextTaxiNode = NULL;
116         holdShortNode = NULL;
117 }
118
119 FGAILocalTraffic::~FGAILocalTraffic() {
120 }
121
122 void FGAILocalTraffic::GetAirportDetails(const string& id) {
123         AirportATC a;
124         if(ATC->GetAirportATCDetails(airportID, &a)) {
125                 if(a.tower_freq) {      // Has a tower - TODO - check the opening hours!!!
126                         tower = (FGTower*)ATC->GetATCPointer(airportID, TOWER);
127                         if(tower == NULL) {
128                                 // Something has gone wrong - abort or carry on with un-towered operation?
129                                 SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a tower pointer from tower control for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
130                                 _controlled = false;
131                         } else {
132                                 _controlled = true;
133                         }
134                         if(tower) {
135                                 ground = tower->GetGroundPtr();
136                                 if(ground == NULL) {
137                                         // Something has gone wrong :-(
138                                         SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a ground pointer from tower control in FGAILocalTraffic::GetAirportDetails() :-(");
139                                 }
140                         }
141                 } else {
142                         _controlled = false;
143                         // TODO - Check CTAF, unicom etc
144                 }
145         } else {
146                 SG_LOG(SG_ATC, SG_ALERT, "Unable to find airport details in for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
147                 _controlled = false;
148         }
149         // Get the airport elevation
150         aptElev = fgGetAirportElev(airportID.c_str());
151         //cout << "Airport elev in AILocalTraffic = " << aptElev << '\n';
152         // WARNING - we use this elev for the whole airport - some assumptions in the code 
153         // might fall down with very slopey airports.
154 }
155
156 // Get details of the active runway
157 // It is assumed that by the time this is called the tower control and airport code will have been set up.
158 void FGAILocalTraffic::GetRwyDetails(const string& id) {
159         //cout << "GetRwyDetails called" << endl;
160         
161   const FGAirport* apt = fgFindAirportID(id);
162   assert(apt);
163   FGRunway* runway(apt->getActiveRunwayForUsage());
164
165   double hdg = runway->headingDeg();
166   double other_way = hdg - 180.0;
167   while(other_way <= 0.0) {
168     other_way += 360.0;
169   }
170   
171         // move to the +l end/center of the runway
172                 //cout << "Runway center is at " << runway._lon << ", " << runway._lat << '\n';
173         double tshlon = 0.0, tshlat = 0.0, tshr;
174                 double tolon = 0.0, tolat = 0.0, tor;
175                 rwy.length = runway->lengthM();
176                 rwy.width = runway->widthM();
177         geo_direct_wgs_84 ( aptElev, runway->latitude(), runway->longitude(), other_way, 
178                                 rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
179         geo_direct_wgs_84 ( aptElev, runway->latitude(), runway->longitude(), hdg, 
180                                 rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor );
181                 // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user.
182                 // now copy what we need out of runway into rwy
183         rwy.threshold_pos = SGGeod::fromDegM(tshlon, tshlat, aptElev);
184                 SGGeod takeoff_end = SGGeod::fromDegM(tolon, tolat, aptElev);
185                 //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n';
186                 //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
187                 rwy.hdg = hdg;
188                 // Set the projection for the local area
189                 //cout << "Initing ortho for airport " << id << '\n';
190                 ortho.Init(rwy.threshold_pos, rwy.hdg); 
191                 rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos);        // should come out as zero
192                 rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
193 }
194
195
196 /* 
197 There are two possible scenarios during initialisation:
198 The first is that the user is flying towards the airport, and hence the traffic
199 could be initialised anywhere, as long as the AI planes are consistent with
200 each other.
201 The second is that the user has started the sim at or close to the airport, and
202 hence the traffic must be initialised with respect to the user as well as each other.
203 To a certain extent it's FGAIMgr that has to worry about this, but we need to provide
204 sufficient initialisation functionality within the plane classes to allow the manager
205 to initially position them where and how required.
206 */
207 bool FGAILocalTraffic::Init(const string& callsign, const string& ICAO, OperatingState initialState, PatternLeg initialLeg) {
208         //cout << "FGAILocalTraffic.Init(...) called" << endl;
209         airportID = ICAO;
210         
211         plane.callsign = callsign;
212         
213         if(initialState == EN_ROUTE) return(true);
214         
215         // Get the ATC pointers and airport elev
216         GetAirportDetails(airportID);
217         
218         // Get the active runway details (and copy them into rwy)
219         GetRwyDetails(airportID);
220         //cout << "Runway is " << rwy.rwyID << '\n';
221         
222         // FIXME TODO - pattern direction is still hardwired
223         patternDirection = -1;          // Left
224         // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
225         if(rwy.rwyID.size() == 3) {
226                 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
227         }
228         
229         if(_controlled) {
230                 if((initialState == PARKED) || (initialState == TAXIING)) {
231                         freq = (double)ground->get_freq() / 100.0;
232                 } else {
233                         freq = (double)tower->get_freq() / 100.0;
234                 }
235         } else {
236                 freq = 122.8;
237                 // TODO - find the proper freq if CTAF or unicom or after-hours.
238         }
239
240         //cout << "In Init(), initialState = " << initialState << endl;
241         operatingState = initialState;
242         SGVec3d orthopos;
243         switch(operatingState) {
244         case PARKED:
245                 tuned_station = ground;
246                 ourGate = ground->GetGateNode();
247                 if(ourGate == NULL) {
248                         // Implies no available gates - what shall we do?
249                         // For now just vanish the plane - possibly we can make this more elegant in the future
250                         SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst attempting Init at " << airportID << '\n');
251                         return(false);
252                 }
253                 _pitch = 0.0;
254                 _roll = 0.0;
255                 vel = 0.0;
256                 slope = 0.0;
257                 _pos = ourGate->pos;
258                 _pos.setElevationM(aptElev);
259                 _hdg = ourGate->heading;
260                 Transform();
261                 
262                 // Now we've set the position we can do the ground elev
263                 elevInitGood = false;
264                 inAir = false;
265                 DoGroundElev();
266                 
267                 break;
268         case TAXIING:
269                 //tuned_station = ground;
270                 // FIXME - implement this case properly
271                 // For now we'll assume that the plane should start at the hold short in this case
272                 // and that we're working without ground network elements.  Ie. an airport with no facility file.
273                 if(_controlled) {
274                         tuned_station = tower;
275                 } else {
276                         tuned_station = NULL;
277                 }
278                 freeTaxi = true;
279                 // Set a position and orientation in an approximate place for hold short.
280                 //cout << "rwy.width = " << rwy.width << '\n';
281                 orthopos = SGVec3d((rwy.width / 2.0 + 10.0) * -1.0, 0.0, 0.0);
282                 // TODO - set the x pos to be +ve if a RH parallel rwy.
283                 _pos = ortho.ConvertFromLocal(orthopos);
284                 _pos.setElevationM(aptElev);
285                 _hdg = rwy.hdg + 90.0;
286                 // TODO - reset the heading if RH rwy.
287                 _pitch = 0.0;
288                 _roll = 0.0;
289                 vel = 0.0;
290                 slope = 0.0;
291                 elevInitGood = false;
292                 inAir = false;
293                 Transform();
294                 DoGroundElev();
295                 //Transform();
296                 
297                 responseCounter = 0.0;
298                 contactTower = false;
299                 changeFreq = true;
300                 holdingShort = true;
301                 clearedToLineUp = false;
302                 changeFreqType = TOWER;
303                 
304                 break;
305         case IN_PATTERN:
306                 // For now we'll always start the in_pattern case on the threshold ready to take-off
307                 // since we've got the implementation for this case already.
308                 // TODO - implement proper generic in_pattern startup.
309                 
310                 // 18/10/03 - adding the ability to start on downwind (mainly to speed testing of the go-around code!!)
311                 
312                 //cout << "Starting in pattern...\n";
313                 
314                 if(_controlled) {
315                         tuned_station = tower;
316                 } else {
317                         tuned_station = NULL;
318                 }
319                 
320                 circuitsToFly = 0;              // ie just fly this circuit and then stop
321                 touchAndGo = false;
322
323                 if(initialLeg == DOWNWIND) {
324                         _pos = ortho.ConvertFromLocal(SGVec3d(1000*patternDirection, 800, 0.0));
325                         _pos.setElevationM(rwy.threshold_pos.getElevationM() + 1000 * SG_FEET_TO_METER);
326                         _hdg = rwy.hdg + 180.0;
327                         leg = DOWNWIND;
328                         elevInitGood = false;
329                         inAir = true;
330                         SetTrack(rwy.hdg - (180 * patternDirection));
331                         slope = 0.0;
332                         _pitch = 0.0;
333                         _roll = 0.0;
334                         IAS = 90.0;
335                         descending = false;
336                         _aip.setVisible(true);
337                         if(_controlled) {
338                                 tower->RegisterAIPlane(plane, this, CIRCUIT, DOWNWIND);
339                         }
340                         Transform();
341                 } else {                        
342                         // Default to initial position on threshold for now
343                         _pos = rwy.threshold_pos;
344                         _hdg = rwy.hdg;
345                         
346                         // Now we've set the position we can do the ground elev
347                         // This might not always be necessary if we implement in-air start
348                         elevInitGood = false;
349                         inAir = false;
350                         
351                         _pitch = 0.0;
352                         _roll = 0.0;
353                         leg = TAKEOFF_ROLL;
354                         vel = 0.0;
355                         slope = 0.0;
356                         
357                         Transform();
358                         DoGroundElev();
359                 }
360         
361                 operatingState = IN_PATTERN;
362                 
363                 break;
364         case EN_ROUTE:
365                 // This implies we're being init'd by AIGAVFRTraffic - simple return now
366                 return(true);
367         default:
368                 SG_LOG(SG_ATC, SG_ALERT, "Attempt to set unknown operating state in FGAILocalTraffic.Init(...)\n");
369                 return(false);
370         }
371         
372         
373         return(true);
374 }
375
376
377 // Set up downwind state - this is designed to be called from derived classes who are already tuned to tower
378 void FGAILocalTraffic::DownwindEntry() {
379         circuitsToFly = 0;              // ie just fly this circuit and then stop
380         touchAndGo = false;
381         operatingState = IN_PATTERN;
382         leg = DOWNWIND;
383         elevInitGood = false;
384         inAir = true;
385         SetTrack(rwy.hdg - (180 * patternDirection));
386         slope = 0.0;
387         _pitch = 0.0;
388         _roll = 0.0;
389         IAS = 90.0;
390         descending = false;
391 }
392
393 void FGAILocalTraffic::StraightInEntry(bool des) {
394         //cout << "************ STRAIGHT-IN ********************\n";
395         circuitsToFly = 0;              // ie just fly this circuit and then stop
396         touchAndGo = false;
397         operatingState = IN_PATTERN;
398         leg = FINAL;
399         elevInitGood = false;
400         inAir = true;
401         SetTrack(rwy.hdg);
402         transmitted = true;     // TODO - fix this hack.
403         // TODO - set up the next 5 properly for a descent!
404         slope = -5.5;
405         _pitch = 0.0;
406         _roll = 0.0;
407         IAS = 90.0;
408         descending = des;
409 }
410
411
412 // Return what type of landing we're doing on this circuit
413 LandingType FGAILocalTraffic::GetLandingOption() {
414         //cout << "circuitsToFly = " << circuitsToFly << '\n';
415         if(circuitsToFly) {
416                 return(touchAndGo ? TOUCH_AND_GO : STOP_AND_GO);
417         } else {
418                 return(FULL_STOP);
419         }
420 }
421         
422
423 // Commands to do something from higher level logic
424 void FGAILocalTraffic::FlyCircuits(int numCircuits, bool tag) {
425         //cout << "FlyCircuits called" << endl;
426         
427         switch(operatingState) {
428         case IN_PATTERN:
429                 circuitsToFly += numCircuits;
430                 return;
431                 break;
432         case TAXIING:
433                 // HACK - assume that we're taxiing out for now
434                 circuitsToFly += numCircuits;
435                 touchAndGo = tag;
436                 break;
437         case PARKED:
438                 circuitsToFly = numCircuits;    // Note that one too many circuits gets flown because we only test and decrement circuitsToFly after landing
439                                                                                 // thus flying one too many circuits.  TODO - Need to sort this out better!
440                 touchAndGo = tag;
441                 break;
442         case EN_ROUTE:
443                 break;
444         }
445 }   
446
447 // Run the internal calculations
448 void FGAILocalTraffic::Update(double dt) {
449         //cout << "U" << flush;
450         
451         // we shouldn't really need this since there's a LOD of 10K on the whole plane anyway I think.
452         // At the moment though I need to to avoid DList overflows - the whole plane LOD obviously isn't getting picked up.
453         if(!_invisible) {
454                 if(dclGetHorizontalSeparation(_pos, SGGeod::fromDegM(fgGetDouble("/position/longitude-deg"), fgGetDouble("/position/latitude-deg"), 0.0)) > 8000) _aip.setVisible(false);
455                 else _aip.setVisible(true);
456         } else {
457                 _aip.setVisible(false);
458         }
459         
460         //double responseTime = 10.0;           // seconds - this should get more sophisticated at some point
461         responseCounter += dt;
462         if((contactTower) && (responseCounter >= 8.0)) {
463                 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
464                 string trns = "Tower ";
465                 double f = globals->get_ATC_mgr()->GetFrequency(airportID, TOWER) / 100.0;      
466                 char buf[10];
467                 sprintf(buf, "%.2f", f);
468                 trns += buf;
469                 trns += " ";
470                 trns += plane.callsign;
471                 pending_transmission = trns;
472                 ConditionalTransmit(30.0);
473                 responseCounter = 0.0;
474                 contactTower = false;
475                 changeFreq = true;
476                 changeFreqType = TOWER;
477         }
478         
479         if((contactGround) && (responseCounter >= 8.0)) {
480                 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
481                 string trns = "Ground ";
482                 double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;     
483                 char buf[10];
484                 sprintf(buf, "%.2f", f);
485                 trns += buf;
486                 trns += " ";
487                 trns += "Good Day";
488                 pending_transmission = trns;
489                 ConditionalTransmit(5.0);
490                 responseCounter = 0.0;
491                 contactGround = false;
492                 changeFreq = true;
493                 changeFreqType = GROUND;
494         }
495         
496         if((_taxiToGA) && (responseCounter >= 8.0)) {
497                 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
498                 string trns = "GA Parking, Thank you and Good Day";
499                 //double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;   
500                 pending_transmission = trns;
501                 ConditionalTransmit(5.0, 99);
502                 _taxiToGA = false;
503                 if(_controlled) {
504                         tower->DeregisterAIPlane(plane.callsign);
505                 }
506                 // NOTE - we can't delete this instance yet since then the frequency won't get release when the message display finishes.
507         }
508
509         if((_removeSelf) && (responseCounter >= 8.0)) {
510                 _removeSelf = false;
511                 // MEGA HACK - check if we are at a simple airport or not first instead of simply hardwiring KEMT as the only non-simple airport.
512                 // TODO FIXME TODO FIXME !!!!!!!
513                 if(airportID != "KEMT") globals->get_AI_mgr()->ScheduleRemoval(plane.callsign);
514         }
515         
516         if((changeFreq) && (responseCounter > 8.0)) {
517                 switch(changeFreqType) {
518                 case TOWER:
519                         if(!tower) {
520                                 SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to tower in FGAILocalTraffic, but tower is NULL!!!");
521                                 break;
522                         }
523                         tuned_station = tower;
524                         freq = (double)tower->get_freq() / 100.0;
525                         //Transmit("DING!");
526                         // Contact the tower, even if only virtually
527                         pending_transmission = plane.callsign;
528                         pending_transmission += " at hold short for runway ";
529                         pending_transmission += ConvertRwyNumToSpokenString(rwy.rwyID);
530                         pending_transmission += " traffic pattern ";
531                         if(circuitsToFly) {
532                                 pending_transmission += ConvertNumToSpokenDigits(circuitsToFly + 1);
533                                 pending_transmission += " circuits touch and go";
534                         } else {
535                                 pending_transmission += " one circuit to full stop";
536                         }
537                         Transmit(2);
538                         break;
539                 case GROUND:
540                         if(!tower) {
541                                 SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to ground in FGAILocalTraffic, but tower is NULL!!!");
542                                 break;
543                         }
544                         if(!ground) {
545                                 SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to ground in FGAILocalTraffic, but ground is NULL!!!");
546                                 break;
547                         }
548                         tower->DeregisterAIPlane(plane.callsign);
549                         tuned_station = ground;
550                         freq = (double)ground->get_freq() / 100.0;
551                         break;
552                 // And to avoid compiler warnings...
553                 case APPROACH:  break;
554                 case ATIS:      break;
555     case AWOS:      break;
556                 case ENROUTE:   break;
557                 case DEPARTURE: break;
558                 case INVALID:   break;
559                 }
560                 changeFreq = false;
561         }
562         
563         //cout << "," << flush;
564                 
565         switch(operatingState) {
566         case IN_PATTERN:
567                 //cout << "In IN_PATTERN\n";
568                 if(!inAir) {
569                         DoGroundElev();
570                         if(!elevInitGood) {
571                                 if(_ground_elevation_m > -9990.0) {
572                                         _pos.setElevationM(_ground_elevation_m + wheelOffset);
573                                         //cout << "TAKEOFF_ROLL, POS = " << pos.lon() << ", " << pos.lat() << ", " << pos.elev() << '\n';
574                                         //Transform();
575                                         _aip.setVisible(true);
576                                         //cout << "Making plane visible!\n";
577                                         elevInitGood = true;
578                                 }
579                         }
580                 }
581                 FlyTrafficPattern(dt);
582                 Transform();
583                 break;
584         case TAXIING:
585                 //cout << "In TAXIING\n";
586                 //cout << "*" << flush;
587                 if(!elevInitGood) {
588                         //DoGroundElev();
589                         if(_ground_elevation_m > -9990.0) {
590                                 _pos.setElevationM(_ground_elevation_m + wheelOffset);
591                                 //Transform();
592                                 _aip.setVisible(true);
593                                 //Transform();
594                                 //cout << "Making plane visible!\n";
595                                 elevInitGood = true;
596                         }
597                 }
598                 DoGroundElev();
599                 //cout << "~" << flush;
600                 if(!((holdingShort) && (!clearedToLineUp))) {
601                         //cout << "|" << flush;
602                         Taxi(dt);
603                 }
604                 //cout << ";" << flush;
605                 if((clearedToTakeOff) && (responseCounter >= 8.0)) {
606                         // possible assumption that we're at the hold short here - may not always hold
607                         // TODO - sort out the case where we're cleared to line-up first and then cleared to take-off on the rwy.
608                         taxiState = TD_LINING_UP;
609                         //cout << "A" << endl;
610                         path = ground->GetPath(holdShortNode, rwy.rwyID);
611                         //cout << "B" << endl;
612                         if(!path.size()) {      // Assume no facility file so we'll just taxi to a point on the runway near the threshold
613                                 //cout << "C" << endl;
614                                 node* np = new node;
615                                 np->struct_type = NODE;
616                                 np->pos = ortho.ConvertFromLocal(SGVec3d(0.0, 10.0, 0.0));
617                                 path.push_back(np);
618                         } else {
619                                 //cout << "D" << endl;
620                         }
621                         /*
622                         cout << "path returned was:" << endl;
623                         for(unsigned int i=0; i<path.size(); ++i) {
624                                 switch(path[i]->struct_type) {
625                                         case NODE:
626                                         cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
627                                         break;
628                                         case ARC:
629                                         cout << "ARC\n";
630                                         break;
631                                 }
632                         }
633                         */
634                         clearedToTakeOff = false;       // We *are* still cleared - this simply stops the response recurring!!
635                         holdingShort = false;
636                         string trns = "Cleared for take-off ";
637                         trns += plane.callsign;
638                         pending_transmission = trns;
639                         Transmit();
640                         StartTaxi();
641                 }
642                 //cout << "^" << flush;
643                 Transform();
644                 break;
645         case PARKED:
646                 //cout << "In PARKED\n";
647                 if(!elevInitGood) {
648                         DoGroundElev();
649                         if(_ground_elevation_m > -9990.0) {
650                                 _pos.setElevationM(_ground_elevation_m + wheelOffset);
651                                 //Transform();
652                                 _aip.setVisible(true);
653                                 //Transform();
654                                 //cout << "Making plane visible!\n";
655                                 elevInitGood = true;
656                         }
657                 }
658                 
659                 if(circuitsToFly) {
660                         if((taxiRequestPending) && (taxiRequestCleared)) {
661                                 //cout << "&" << flush;
662                                 // Get the active runway details (in case they've changed since init)
663                                 GetRwyDetails(airportID);
664                                 
665                                 // Get the takeoff node for the active runway, get a path to it and start taxiing
666                                 path = ground->GetPathToHoldShort(ourGate, rwy.rwyID);
667                                 if(path.size() < 2) {
668                                         // something has gone wrong
669                                         SG_LOG(SG_ATC, SG_ALERT, "Invalid path from gate to theshold in FGAILocalTraffic::FlyCircuits\n");
670                                         return;
671                                 }
672                                 /*
673                                 cout << "path returned was:\n";
674                                 for(unsigned int i=0; i<path.size(); ++i) {
675                                         switch(path[i]->struct_type) {
676                                                 case NODE:
677                                                 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
678                                                 break;
679                                                 case ARC:
680                                                 cout << "ARC\n";
681                                                 break;
682                                         }
683                                 }
684                                 */
685                                 path.erase(path.begin());       // pop the gate - we're here already!
686                                 taxiState = TD_OUTBOUND;
687                                 taxiRequestPending = false;
688                                 holdShortNode = (node*)(*(path.begin() + path.size()));
689                                 StartTaxi();
690                         } else if(!taxiRequestPending) {
691                                 //cout << "(" << flush;
692                                 // Do some communication
693                                 // airport name + tower + airplane callsign + location + request taxi for + operation type + ?
694                                 string trns = "";
695                                 if(_controlled) {
696                                         trns += tower->get_name();
697                                         trns += " tower ";
698                                 } else {
699                                         trns += "Traffic ";
700                                         // TODO - get the airport name somehow if uncontrolled
701                                 }
702                                 trns += plane.callsign;
703                                 trns += " on apron parking request taxi for traffic pattern";
704                                 //cout << "trns = " << trns << endl;
705                                 pending_transmission = trns;
706                                 Transmit(1);
707                                 taxiRequestCleared = false;
708                                 taxiRequestPending = true;
709                         }
710                 }
711                 
712                 //cout << "!" << flush;
713                                 
714                 // Maybe the below should be set when we get to the threshold and prepare for TO?
715                 // FIXME TODO - pattern direction is still hardwired
716                 patternDirection = -1;          // Left
717                 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
718                 if(rwy.rwyID.size() == 3) {
719                         patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
720                 }               
721                 // Do nothing
722                 Transform();
723                 //cout << ")" << flush;
724                 break;
725         default:
726                 break;
727         }
728         //cout << "I " << flush;
729         
730         //cout << "Update _pos = " << _pos << ", vis = " << _aip.getVisible() << '\n';
731         
732         // Convienience output for AI debugging using the property logger
733         //fgSetDouble("/AI/Local1/ortho-x", (ortho.ConvertToLocal(_pos)).x());
734         //fgSetDouble("/AI/Local1/ortho-y", (ortho.ConvertToLocal(_pos)).y());
735         //fgSetDouble("/AI/Local1/elev", _pos.elev() * SG_METER_TO_FEET);
736         
737         // And finally, call parent.
738         FGAIPlane::Update(dt);
739 }
740
741 void FGAILocalTraffic::RegisterTransmission(int code) {
742         switch(code) {
743         case 1: // taxi request cleared
744                 taxiRequestCleared = true;
745                 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to taxi...");
746                 break;
747         case 2: // contact tower
748                 responseCounter = 0;
749                 contactTower = true;
750                 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact tower...");
751                 break;
752         case 3: // Cleared to line up
753                 responseCounter = 0;
754                 clearedToLineUp = true;
755                 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to line-up...");
756                 break;
757         case 4: // cleared to take-off
758                 responseCounter = 0;
759                 clearedToTakeOff = true;
760                 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to take-off...");
761                 break;
762         case 5: // contact ground
763                 responseCounter = 0;
764                 contactGround = true;
765                 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact ground...");
766                 break;
767         // case 6 is a temporary mega-hack for controlled airports without separate ground control
768         case 6: // taxi to the GA parking
769                 responseCounter = 0;
770                 _taxiToGA = true;
771                 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to taxi to the GA parking...");
772                 break;
773         case 7:  // Cleared to land (also implies cleared for the option
774                 _clearedToLand = true;
775                 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to land...");
776                 break;
777         case 13: // Go around!
778                 responseCounter = 0;
779                 goAround = true;
780                 _clearedToLand = false;
781                 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to go-around!!");
782                 break;
783         default:
784                 break;
785         }
786 }
787
788 // Fly a traffic pattern
789 // FIXME - far too much of the mechanics of turning, rolling, accellerating, descending etc is in here.
790 //         Move it out to FGAIPlane and have FlyTrafficPattern just specify what to do, not the implementation.
791 void FGAILocalTraffic::FlyTrafficPattern(double dt) {
792         // Need to differentiate between in-air (IAS governed) and on-ground (vel governed)
793         // Take-off is an interesting case - we are on the ground but takeoff speed is IAS governed.
794         
795         // WIND
796         // Wind has two effects - a mechanical one in that IAS translates to a different vel, and the hdg != track,
797         // but also a piloting effect, in that the AI must be able to descend at a different rate in order to hit the threshold.
798         
799         //cout << "dt = " << dt << '\n';
800         double dist = 0;
801         // ack - I can't remember how long a rate 1 turn is meant to take.
802         double turn_time = 60.0;        // seconds - TODO - check this guess
803         double turn_circumference;
804         double turn_radius;
805         SGVec3d orthopos = ortho.ConvertToLocal(_pos);  // ortho position of the plane
806         //cout << "runway elev = " << rwy.threshold_pos.getElevationM() << ' ' << rwy.threshold_pos.getElevationM() * SG_METER_TO_FEET << '\n';
807         //cout << "elev = " << _pos.elev() << ' ' << _pos.elev() * SG_METER_TO_FEET << '\n';
808
809         // HACK FOR TESTING - REMOVE
810         //cout << "Calling ExitRunway..." << endl;
811         //ExitRunway(orthopos);
812         //return;
813         // END HACK
814         
815         //wind
816         double wind_from = wind_from_hdg->getDoubleValue();
817         double wind_speed = wind_speed_knots->getDoubleValue();
818
819         double dveldt;
820         
821         switch(leg) {
822         case TAKEOFF_ROLL:
823                 //inAir = false;
824                 track = rwy.hdg;
825                 if(vel < 80.0) {
826                         double dveldt = 5.0;
827                         vel += dveldt * dt;
828                 }
829                 if(_ground_elevation_m > -9990.0) {
830                         _pos.setElevationM(_ground_elevation_m + wheelOffset);
831                 }
832                 IAS = vel + (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
833                 if(IAS >= 70) {
834                         leg = CLIMBOUT;
835                         SetTrack(rwy.hdg);      // Hands over control of turning to AIPlane
836                         _pitch = 10.0;
837                         IAS = best_rate_of_climb_speed;
838                         //slope = 7.0;  
839                         slope = 6.0;    // Reduced it slightly since it's climbing a lot steeper than I can in the JSBSim C172.
840                         inAir = true;
841                 }
842                 break;
843         case CLIMBOUT:
844                 // Turn to crosswind if above 700ft AND if other traffic allows
845                 // (decided in FGTower and accessed through GetCrosswindConstraint(...)).
846                 // According to AIM, traffic should climb to within 300ft of pattern altitude before commencing crosswind turn.
847                 // TODO - At hot 'n high airports this may be 500ft AGL though - need to make this a variable.
848                 if((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET > 700) {
849                         double cc = 0.0;
850                         if(_controlled && tower->GetCrosswindConstraint(cc)) {
851                                 if(orthopos.y() > cc) {
852                                         //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n'; 
853                                         leg = TURN1;
854                                 }
855                         } else if(orthopos.y() > 1500.0) {   // Added this constraint as a hack to prevent turning too early when going around.
856                                 // TODO - We should be doing it as a distance from takeoff end, not theshold end though.
857                                 //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n'; 
858                                 leg = TURN1;
859                         }
860                 }
861                 // Need to check for levelling off in case we can't turn crosswind as soon
862                 // as we would like due to other traffic.
863                 if((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET > 1000) {
864                         slope = 0.0;
865                         _pitch = 0.0;
866                         IAS = 80.0;             // FIXME - use smooth transistion to new speed and attitude.
867                 }
868                 if(goAround && !goAroundCalled) {
869                         if(responseCounter > 5.5) {
870                                 pending_transmission = plane.callsign;
871                                 pending_transmission += " going around";
872                                 Transmit();
873                                 goAroundCalled = true;
874                         }
875                 }               
876                 break;
877         case TURN1:
878                 SetTrack(rwy.hdg + (90.0 * patternDirection));
879                 if((track < (rwy.hdg - 89.0)) || (track > (rwy.hdg + 89.0))) {
880                         leg = CROSSWIND;
881                 }
882                 break;
883         case CROSSWIND:
884                 goAround = false;
885                 if((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET > 1000) {
886                         slope = 0.0;
887                         _pitch = 0.0;
888                         IAS = 80.0;             // FIXME - use smooth transistion to new speed
889                 }
890                 // turn 1000m out for now, taking other traffic into accout
891                 if(fabs(orthopos.x()) > 900) {
892                         double dd = 0.0;
893                         if(_controlled && tower->GetDownwindConstraint(dd)) {
894                                 if(fabs(orthopos.x()) > fabs(dd)) {
895                                         //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n'; 
896                                         leg = TURN2;
897                                 }
898                         } else {
899                                 //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n'; 
900                                 leg = TURN2;
901                         }
902                 }
903                 break;
904         case TURN2:
905                 SetTrack(rwy.hdg - (180 * patternDirection));
906                 // just in case we didn't make height on crosswind
907                 if((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET > 1000) {
908                         slope = 0.0;
909                         _pitch = 0.0;
910                         IAS = 80.0;             // FIXME - use smooth transistion to new speed
911                 }
912                 if((track < (rwy.hdg - 179.0)) || (track > (rwy.hdg + 179.0))) {
913                         leg = DOWNWIND;
914                         transmitted = false;
915                 }
916                 break;
917         case DOWNWIND:
918                 // just in case we didn't make height on crosswind
919                 if(((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET > 995) && ((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET < 1015)) {
920                         slope = 0.0;
921                         _pitch = 0.0;
922                         IAS = 90.0;             // FIXME - use smooth transistion to new speed
923                 }
924                 if((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET >= 1015) {
925                         slope = -1.0;
926                         _pitch = -1.0;
927                         IAS = 90.0;             // FIXME - use smooth transistion to new speed
928                 }
929                 if((orthopos.y() < 0) && (!transmitted)) {
930                         TransmitPatternPositionReport();
931                         transmitted = true;
932                 }
933                 if((orthopos.y() < -100) && (!descending)) {
934                         //cout << "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDdddd\n";
935                         // Maybe we should think about when to start descending.
936                         // For now we're assuming that we aim to follow the same glidepath regardless of wind.
937                         double d1;
938                         double d2;
939                         CalculateSoD(((_controlled && tower->GetBaseConstraint(d1)) ? d1 : -1000.0), ((_controlled && tower->GetDownwindConstraint(d2)) ? d2 : 1000.0 * patternDirection), (patternDirection ? true : false));
940                         if(SoD.leg == DOWNWIND) {
941                                 descending = (orthopos.y() < SoD.y ? true : false);
942                         }
943
944                 }
945                 if(descending) {
946                         slope = -5.5;   // FIXME - calculate to descent at 500fpm and hit the desired point on the runway (taking wind into account as well!!)
947                         _pitch = -3.0;
948                         IAS = 85.0;
949                 }
950                 
951                 // Try and arrange to turn nicely onto base
952                 turn_circumference = IAS * 0.514444 * turn_time;        
953                 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
954                 //We'll leave it as a hack with IAS for now but it needs revisiting.            
955                 turn_radius = turn_circumference / (2.0 * DCL_PI);
956                 if(orthopos.y() < -1000.0 + turn_radius) {
957                 //if(orthopos.y() < -980) {
958                         double bb = 0.0;
959                         if(_controlled && tower->GetBaseConstraint(bb)) {
960                                 if(fabs(orthopos.y()) > fabs(bb)) {
961                                         //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n'; 
962                                         leg = TURN3;
963                                         transmitted = false;
964                                         IAS = 80.0;
965                                 }
966                         } else {
967                                 //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n'; 
968                                 leg = TURN3;
969                                 transmitted = false;
970                                 IAS = 80.0;
971                         }
972                 }
973                 break;
974         case TURN3:
975                 SetTrack(rwy.hdg - (90 * patternDirection));
976                 if(fabs(rwy.hdg - track) < 91.0) {
977                         leg = BASE;
978                 }
979                 break;
980         case BASE:
981                 if(!transmitted) {
982                         // Base report should only be transmitted at uncontrolled airport - not towered.
983                         if(!_controlled) TransmitPatternPositionReport();
984                         transmitted = true;
985                 }
986                 
987                 if(!descending) {
988                         double d1;
989                         // Make downwind leg position artifically large to avoid any chance of SoD being returned as
990                         // on downwind when we are already on base.
991                         CalculateSoD(((_controlled && tower->GetBaseConstraint(d1)) ? d1 : -1000.0), (10000.0 * patternDirection), (patternDirection ? true : false));
992                         if(SoD.leg == BASE) {
993                                 descending = (fabs(orthopos.y()) < fabs(SoD.y) ? true : false);
994                         }
995
996                 }
997                 if(descending) {
998                         slope = -5.5;   // FIXME - calculate to descent at 500fpm and hit the threshold (taking wind into account as well!!)
999                         _pitch = -4.0;
1000                         IAS = 70.0;
1001                 }
1002                 
1003                 // Try and arrange to turn nicely onto final
1004                 turn_circumference = IAS * 0.514444 * turn_time;        
1005                 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
1006                 //We'll leave it as a hack with IAS for now but it needs revisiting.            
1007                 turn_radius = turn_circumference / (2.0 * DCL_PI);
1008                 if(fabs(orthopos.x()) < (turn_radius + 50)) {
1009                         leg = TURN4;
1010                         transmitted = false;
1011                         //roll = -20;
1012                 }
1013                 break;
1014         case TURN4:
1015                 SetTrack(rwy.hdg);
1016                 if(fabs(track - rwy.hdg) < 0.6) {
1017                         leg = FINAL;
1018                         vel = nominal_final_speed;
1019                 }
1020                 break;
1021         case FINAL:
1022                 if(goAround && responseCounter > 2.0) {
1023                         leg = CLIMBOUT;
1024                         _pitch = 8.0;
1025                         IAS = best_rate_of_climb_speed;
1026                         slope = 5.0;    // A bit less steep than the initial climbout.
1027                         inAir = true;
1028                         goAroundCalled = false;
1029                         descending = false;
1030                         break;
1031                 }
1032                 LevelWings();
1033                 if(!transmitted) {
1034                         if((!_controlled) || (!_clearedToLand)) TransmitPatternPositionReport();
1035                         transmitted = true;
1036                 }
1037                 if(!descending) {
1038                         // Make base leg position artifically large to avoid any chance of SoD being returned as
1039                         // on base or downwind when we are already on final.
1040                         CalculateSoD(-10000.0, (1000.0 * patternDirection), (patternDirection ? true : false));
1041                         if(SoD.leg == FINAL) {
1042                                 descending = (fabs(orthopos.y()) < fabs(SoD.y) ? true : false);
1043                         }
1044
1045                 }
1046                 if(descending) {
1047                         if(orthopos.y() < -50.0) {
1048                                 double thesh_offset = 30.0;
1049                                 slope = atan((_pos.getElevationM() - fgGetAirportElev(airportID)) / (orthopos.y() - thesh_offset)) * DCL_RADIANS_TO_DEGREES;
1050                                 //cout << "slope = " << slope << ", elev = " << _pos.elev() << ", apt_elev = " << fgGetAirportElev(airportID) << ", op.y = " << orthopos.y() << '\n';
1051                                 if(slope < -10.0) slope = -10.0;
1052                                 _savedSlope = slope;
1053                                 _pitch = -4.0;
1054                                 IAS = 70.0;
1055                         } else {
1056                                 if(_pos.getElevationM() < (rwy.threshold_pos.getElevationM()+10.0+wheelOffset)) {
1057                                         if(_ground_elevation_m > -9990.0) {
1058                                                 if(_pos.getElevationM() < (_ground_elevation_m + wheelOffset + 1.0)) {
1059                                                         slope = -2.0;
1060                                                         _pitch = 1.0;
1061                                                         IAS = 55.0;
1062                                                 } else if(_pos.getElevationM() < (_ground_elevation_m + wheelOffset + 5.0)) {
1063                                                         slope = -4.0;
1064                                                         _pitch = -2.0;
1065                                                         IAS = 60.0;
1066                                                 } else {
1067                                                         slope = _savedSlope;
1068                                                         _pitch = -3.0;
1069                                                         IAS = 65.0;
1070                                                 }
1071                                         } else {
1072                                                 // Elev not determined
1073                                                 slope = _savedSlope;
1074                                                 _pitch = -3.0;
1075                                                 IAS = 65.0;
1076                                         }
1077                                 } else {
1078                                         slope = _savedSlope;
1079                                         _pitch = -3.0;
1080                                         IAS = 65.0;
1081                                 }
1082                         }
1083                 }
1084                 // Try and track the extended centreline
1085                 SetTrack(rwy.hdg - (0.2 * orthopos.x()));
1086                 //cout << "orthopos.x() = " << orthopos.x() << " hdg = " << hdg << '\n';
1087                 if(_pos.getElevationM() < (rwy.threshold_pos.getElevationM()+20.0+wheelOffset)) {
1088                         DoGroundElev(); // Need to call it here expicitly on final since it's only called
1089                                         // for us in update(...) when the inAir flag is false.
1090                 }
1091                 if(_pos.getElevationM() < (rwy.threshold_pos.getElevationM()+10.0+wheelOffset)) {
1092                         //slope = -1.0;
1093                         //_pitch = 1.0;
1094                         if(_ground_elevation_m > -9990.0) {
1095                                 if((_ground_elevation_m + wheelOffset) > _pos.getElevationM()) {
1096                                         slope = 0.0;
1097                                         _pitch = 0.0;
1098                                         leg = LANDING_ROLL;
1099                                         inAir = false;
1100                                         LevelWings();
1101                                         ClearTrack();   // Take over explicit track handling since AIPlane currently always banks when changing course 
1102                                 }
1103                         }       // else need a fallback position based on arpt elev in case ground elev determination fails?
1104                 } else {
1105                         // TODO
1106                 }                       
1107                 break;
1108         case LANDING_ROLL:
1109                 //inAir = false;
1110                 descending = false;
1111                 if(_ground_elevation_m > -9990.0) {
1112                         _pos.setElevationM(_ground_elevation_m + wheelOffset);
1113                 }
1114                 track = rwy.hdg;
1115                 dveldt = -5.0;
1116                 vel += dveldt * dt;
1117                 // FIXME - differentiate between touch and go and full stops
1118                 if(vel <= 15.0) {
1119                         //cout << "Vel <= 15.0, circuitsToFly = " << circuitsToFly << endl;
1120                         if(circuitsToFly <= 0) {
1121                                 //cout << "Calling ExitRunway..." << endl;
1122                                 ExitRunway(orthopos);
1123                                 return;
1124                         } else {
1125                                 //cout << "Taking off again..." << endl;
1126                                 leg = TAKEOFF_ROLL;
1127                                 --circuitsToFly;
1128                         }
1129                 }
1130                 break;
1131         case LEG_UNKNOWN:
1132                 break;
1133     }
1134
1135         if(inAir) {
1136                 // calculate ground speed and crab from the wind triangle
1137                 double wind_angle = GetAngleDiff_deg(wind_from + 180, track);
1138                 double wind_side = (wind_angle < 0) ? -1.0 : 1.0;
1139
1140                 double sine_of_crab = wind_speed / IAS * sin(fabs(wind_angle) * DCL_DEGREES_TO_RADIANS);
1141                 if (sine_of_crab >= 1.0) {
1142                         // The crosswind component is greater than the IAS,
1143                         // we can't keep the aircraft on track.
1144                         // Assume increased IAS such that it cancels lateral speed.
1145                         // This is unrealistic, but not sure how the rest of the sim
1146                         // would react to the aircraft going off course.
1147                         // Should be a rare case anyway.
1148                         crab = wind_side * 90.0;
1149                 } else {
1150                         crab = asin(sine_of_crab) * DCL_RADIANS_TO_DEGREES * wind_side;
1151                 }
1152                 vel = cos(wind_angle * DCL_DEGREES_TO_RADIANS) * wind_speed
1153                         + cos(crab * DCL_DEGREES_TO_RADIANS) * IAS;
1154         } else {        // on the ground - crab dosen't apply
1155                 crab = 0.0;
1156         }
1157         
1158         //cout << "X " << orthopos.x() << "  Y " << orthopos.y() << "  SLOPE " << slope << "  elev " << _pos.elev() * SG_METER_TO_FEET << '\n';
1159         
1160         _hdg = track + crab;
1161         dclBoundHeading(_hdg);
1162         dist = vel * 0.514444 * dt;
1163         _pos = dclUpdatePosition(_pos, track, slope, dist);
1164 }
1165
1166 // Pattern direction is true for right, false for left
1167 void FGAILocalTraffic::CalculateSoD(double base_leg_pos, double downwind_leg_pos, bool pattern_direction) {
1168         // For now we'll ignore wind and hardwire the glide angle.
1169         double ga = 5.5;        //degrees
1170         double pa = 1000.0 * SG_FEET_TO_METER;  // pattern altitude in meters
1171         // FIXME - get glideslope angle and pattern altitude agl from airport details if available
1172         
1173         // For convienience, we'll have +ve versions of the input distances
1174         double blp = fabs(base_leg_pos);
1175         double dlp = fabs(downwind_leg_pos);
1176         
1177         //double turn_allowance = 150.0;        // Approximate distance in meters that a 90deg corner is shortened by turned in a light plane.
1178         
1179         double stod = pa / tan(ga * DCL_DEGREES_TO_RADIANS);    // distance in meters from touchdown point to start descent
1180         //cout << "Descent to start = " << stod << " meters out\n";
1181         if(stod < blp) {        // Start descending on final
1182                 SoD.leg = FINAL;
1183                 SoD.y = stod * -1.0;
1184                 SoD.x = 0.0;
1185         } else if(stod < (blp + dlp)) { // Start descending on base leg
1186                 SoD.leg = BASE;
1187                 SoD.y = blp * -1.0;
1188                 SoD.x = (pattern_direction ? (stod - dlp) : (stod - dlp) * -1.0);
1189         } else {        // Start descending on downwind leg
1190                 SoD.leg = DOWNWIND;
1191                 SoD.x = (pattern_direction ? dlp : dlp * -1.0);
1192                 SoD.y = (blp - (stod - (blp + dlp))) * -1.0;
1193         }
1194 }
1195
1196 void FGAILocalTraffic::TransmitPatternPositionReport(void) {
1197         // airport name + "traffic" + airplane callsign + pattern direction + pattern leg + rwy + ?
1198         string trns;
1199         int code = 0;
1200         const string& apt_name = _controlled ? tower->get_name() : airportID;
1201
1202         trns += apt_name;
1203         trns += " Traffic ";
1204         trns += plane.callsign;
1205         if(patternDirection == 1) {
1206                 trns += " right ";
1207         } else {
1208                 trns += " left ";
1209         }
1210         
1211         // We could probably get rid of this whole switch statement and just pass a string containing the leg from the FlyPattern function.
1212         switch(leg) {   // We'll assume that transmissions in turns are intended for next leg - do pilots ever call out that they are in the turn?
1213         case TURN1:
1214                 // Fall through to CROSSWIND
1215         case CROSSWIND: // I don't think this case will be used here but it can't hurt to leave it in
1216                 trns += "crosswind ";
1217                 break;
1218         case TURN2:
1219                 // Fall through to DOWNWIND
1220         case DOWNWIND:
1221                 trns += "downwind ";
1222                 code = 11;
1223                 break;
1224         case TURN3:
1225                 // Fall through to BASE
1226         case BASE:
1227                 trns += "base ";
1228                 break;
1229         case TURN4:
1230                 // Fall through to FINAL
1231         case FINAL:             // maybe this should include long/short final if appropriate?
1232                 trns += "final ";
1233                 code = 13;
1234                 break;
1235         default:                // Hopefully this won't be used
1236                 trns += "pattern ";
1237                 break;
1238         }
1239         trns += ConvertRwyNumToSpokenString(rwy.rwyID);
1240         
1241         trns += ' ';
1242         
1243         // And add the airport name again
1244         trns += apt_name;
1245         
1246         pending_transmission = trns;
1247         ConditionalTransmit(60.0, code);                // Assume a report of this leg will be invalid if we can't transmit within a minute.
1248 }
1249
1250 // Callback handler
1251 // TODO - Really should enumerate these coded values.
1252 void FGAILocalTraffic::ProcessCallback(int code) {
1253         // 1 - Request Departure from ground
1254         // 2 - Report at hold short
1255         // 3 - Report runway vacated
1256         // 10 - report crosswind
1257         // 11 - report downwind
1258         // 12 - report base
1259         // 13 - report final
1260         if(code == 1) {
1261                 ground->RequestDeparture(plane, this);
1262         } else if(code == 2) {
1263                 if (_controlled) tower->ContactAtHoldShort(plane, this, CIRCUIT);
1264         } else if(code == 3) {
1265                 if (_controlled) tower->ReportRunwayVacated(plane.callsign);
1266         } else if(code == 11) {
1267                 if (_controlled) tower->ReportDownwind(plane.callsign);
1268         } else if(code == 13) {
1269                 if (_controlled) tower->ReportFinal(plane.callsign);
1270         } else if(code == 99) { // Flag this instance for deletion
1271                 responseCounter = 0;
1272                 _removeSelf = true;
1273                 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " delete instance callback called.");
1274         }
1275 }
1276
1277 void FGAILocalTraffic::ExitRunway(const SGVec3d& orthopos) {
1278         //cout << "In ExitRunway" << endl;
1279         //cout << "Runway ID is " << rwy.ID << endl;
1280         
1281         _clearedToLand = false;
1282         
1283         node_array_type exitNodes = ground->GetExits(rwy.rwyID);        //I suppose we ought to have some fallback for rwy with no defined exits?
1284         /*
1285         cout << "Node ID's of exits are ";
1286         for(unsigned int i=0; i<exitNodes.size(); ++i) {
1287                 cout << exitNodes[i]->nodeID << ' ';
1288         }
1289         cout << endl;
1290         */
1291         if(exitNodes.size()) {
1292                 //Find the next exit from orthopos.y
1293                 double d;
1294                 double dist = 100000;   //ie. longer than any runway in existance
1295                 double backdist = 100000;
1296                 node_array_iterator nItr = exitNodes.begin();
1297                 node* rwyExit = *(exitNodes.begin());
1298                 //int gateID;           //This might want to be more persistant at some point
1299                 while(nItr != exitNodes.end()) {
1300                         d = ortho.ConvertToLocal((*nItr)->pos).y() - ortho.ConvertToLocal(_pos).y();    //FIXME - consider making orthopos a class variable
1301                         if(d > 0.0) {
1302                                 if(d < dist) {
1303                                         dist = d;
1304                                         rwyExit = *nItr;
1305                                 }
1306                         } else {
1307                                 if(fabs(d) < backdist) {
1308                                         backdist = d;
1309                                         //TODO - need some logic here that if we don't get a forward exit we turn round and store the backwards one
1310                                 }
1311                         }
1312                         ++nItr;
1313                 }
1314                 ourGate = ground->GetGateNode();
1315                 if(ourGate == NULL) {
1316                         // Implies no available gates - what shall we do?
1317                         // For now just vanish the plane - possibly we can make this more elegant in the future
1318                         SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst landing at " << airportID << '\n');
1319                         //_aip.setVisible(false);
1320                         //cout << "Setting visible false\n";
1321                         operatingState = PARKED;
1322                         return;
1323                 }
1324                 path = ground->GetPath(rwyExit, ourGate);
1325                 /*
1326                 cout << "path returned was:" << endl;
1327                 for(unsigned int i=0; i<path.size(); ++i) {
1328                         switch(path[i]->struct_type) {
1329                         case NODE:
1330                                 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
1331                                 break;
1332                         case ARC:
1333                                 cout << "ARC\n";
1334                                 break;
1335                         }
1336                 }
1337                 */
1338                 taxiState = TD_INBOUND;
1339                 StartTaxi();
1340         } else {
1341                 // Something must have gone wrong with the ground network file - or there is only a rwy here and no exits defined
1342                 SG_LOG(SG_ATC, SG_INFO, "No exits found by FGAILocalTraffic from runway " << rwy.rwyID << " at " << airportID << '\n');
1343                 //if(airportID == "KRHV") cout << "No exits found by " << plane.callsign << " from runway " << rwy.rwyID << " at " << airportID << '\n';
1344                 // What shall we do - just remove the plane from sight?
1345                 _aip.setVisible(false);
1346                 _invisible = true;
1347                 //cout << "Setting visible false\n";
1348                 //tower->ReportRunwayVacated(plane.callsign);
1349                 string trns = "Clear of the runway ";
1350                 trns += plane.callsign;
1351                 pending_transmission = trns;
1352                 Transmit(3);
1353                 operatingState = PARKED;
1354         }
1355 }
1356
1357 // Set the class variable nextTaxiNode to the next node in the path
1358 // and update taxiPathPos, the class variable path iterator position
1359 // TODO - maybe should return error codes to the calling function if we fail here
1360 void FGAILocalTraffic::GetNextTaxiNode() {
1361         //cout << "GetNextTaxiNode called " << endl;
1362         //cout << "taxiPathPos = " << taxiPathPos << endl;
1363         ground_network_path_iterator pathItr = path.begin() + taxiPathPos;
1364         if(pathItr == path.end()) {
1365                 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - no more nodes in path\n");
1366         } else {
1367                 if((*pathItr)->struct_type == NODE) {
1368                         //cout << "ITS A NODE" << endl;
1369                         //*pathItr = new node;
1370                         nextTaxiNode = (node*)*pathItr;
1371                         ++taxiPathPos;
1372                         //delete pathItr;
1373                 } else {
1374                         //cout << "ITS NOT A NODE" << endl;
1375                         //The first item in found must have been an arc
1376                         //Assume for now that it was straight
1377                         pathItr++;
1378                         taxiPathPos++;
1379                         if(pathItr == path.end()) {
1380                                 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - path ended with an arc\n");
1381                         } else if((*pathItr)->struct_type == NODE) {
1382                                 nextTaxiNode = (node*)*pathItr;
1383                                 ++taxiPathPos;
1384                         } else {
1385                                 //OOPS - two non-nodes in a row - that shouldn't happen ATM
1386                                 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - two non-nodes in sequence\n");
1387                         }
1388                 }
1389         }
1390 }           
1391
1392 // StartTaxi - set up the taxiing state - call only at the start of taxiing
1393 void FGAILocalTraffic::StartTaxi() {
1394         //cout << "StartTaxi called" << endl;
1395         operatingState = TAXIING;
1396         
1397         taxiPathPos = 0;
1398         
1399         //Set the desired heading
1400         //Assume we are aiming for first node on path
1401         //Eventually we may need to consider the fact that we might start on a curved arc and
1402         //not be able to head directly for the first node.
1403         GetNextTaxiNode();      // sets the class variable nextTaxiNode to the next taxi node!
1404         desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos);
1405         //cout << "First taxi heading is " << desiredTaxiHeading << endl;
1406 }
1407
1408 // speed in knots, headings in degrees, radius in meters.
1409 static double TaxiTurnTowardsHeading(double current_hdg, double desired_hdg, double speed, double radius, double dt) {
1410         // wrap heading - this prevents a logic bug where the plane would just go round in circles!!
1411         while(current_hdg < 0.0) {
1412                 current_hdg += 360.0;
1413         }
1414         while(current_hdg > 360.0) {
1415                 current_hdg -= 360.0;
1416         }
1417         if(fabs(current_hdg - desired_hdg) > 0.1) {
1418                 // Which is the quickest direction to turn onto heading?
1419                 if(desired_hdg > current_hdg) {
1420                         if((desired_hdg - current_hdg) <= 180) {
1421                                 // turn right
1422                                 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1423                                 // TODO - check that increments are less than the delta that we check for the right direction
1424                                 // Probably need to reduce convergence speed as convergence is reached
1425                         } else {
1426                                 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;   
1427                         }
1428                 } else {
1429                         if((current_hdg - desired_hdg) <= 180) {
1430                                 // turn left
1431                                 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1432                                 // TODO - check that increments are less than the delta that we check for the right direction
1433                                 // Probably need to reduce convergence speed as convergence is reached
1434                         } else {
1435                                 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;   
1436                         }
1437                 }               
1438         }
1439         return(current_hdg);
1440 }
1441
1442 void FGAILocalTraffic::Taxi(double dt) {
1443         //cout << "Taxi called" << endl;
1444         // Logic - if we are further away from next point than turn radius then head for it
1445         // If we have reached turning point then get next point and turn onto that heading
1446         // Look out for the finish!!
1447
1448         desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos);
1449         
1450         bool lastNode = (taxiPathPos == path.size() ? true : false);
1451         if(lastNode) {
1452                 //cout << "LAST NODE\n";
1453         }
1454
1455         // HACK ALERT! - for now we will taxi at constant speed for straights and turns
1456         
1457         // Remember that hdg is always equal to track when taxiing so we don't have to consider them both
1458         double dist_to_go = dclGetHorizontalSeparation(_pos, nextTaxiNode->pos);        // we may be able to do this more cheaply using orthopos
1459         //cout << "dist_to_go = " << dist_to_go << endl;
1460         if((nextTaxiNode->type == GATE) && (dist_to_go <= 0.1)) {
1461                 // This might be more robust to outward paths starting with a gate if we check for either
1462                 // last node or TD_INBOUND ?
1463                 // park up
1464                 operatingState = PARKED;
1465         } else if(((dist_to_go > taxiTurnRadius) || (nextTaxiNode->type == GATE)) && (!liningUp)){
1466                 // if the turn radius is r, and speed is s, then in a time dt we turn through
1467                 // ((s.dt)/(PI.r)) x 180 degrees
1468                 // or alternatively (s.dt)/r radians
1469                 //cout << "hdg = " << hdg << " desired taxi heading = " << desiredTaxiHeading << '\n';
1470                 _hdg = TaxiTurnTowardsHeading(_hdg, desiredTaxiHeading, nominalTaxiSpeed, taxiTurnRadius, dt);
1471                 double vel = nominalTaxiSpeed;
1472                 //cout << "vel = " << vel << endl;
1473                 double dist = vel * 0.514444 * dt;
1474                 //cout << "dist = " << dist << endl;
1475                 double track = _hdg;
1476                 //cout << "track = " << track << endl;
1477                 double slope = 0.0;
1478                 _pos = dclUpdatePosition(_pos, track, slope, dist);
1479                 //cout << "Updated position...\n";
1480                 if(_ground_elevation_m > -9990) {
1481                         _pos.setElevationM(_ground_elevation_m + wheelOffset);
1482                 } // else don't change the elev until we get a valid ground elev again!
1483         } else if(lastNode) {
1484                 if(taxiState == TD_LINING_UP) {
1485                         if((!liningUp) && (dist_to_go <= taxiTurnRadius)) {
1486                                 liningUp = true;
1487                         }
1488                         if(liningUp) {
1489                                 _hdg = TaxiTurnTowardsHeading(_hdg, rwy.hdg, nominalTaxiSpeed, taxiTurnRadius, dt);
1490                                 double vel = nominalTaxiSpeed;
1491                                 //cout << "vel = " << vel << endl;
1492                                 double dist = vel * 0.514444 * dt;
1493                                 //cout << "dist = " << dist << endl;
1494                                 double track = _hdg;
1495                                 //cout << "track = " << track << endl;
1496                                 double slope = 0.0;
1497                                 _pos = dclUpdatePosition(_pos, track, slope, dist);
1498                                 //cout << "Updated position...\n";
1499                                 if(_ground_elevation_m > -9990) {
1500                                         _pos.setElevationM(_ground_elevation_m + wheelOffset);
1501                                 } // else don't change the elev until we get a valid ground elev again!
1502                                 if(fabs(_hdg - rwy.hdg) <= 1.0) {
1503                                         operatingState = IN_PATTERN;
1504                                         leg = TAKEOFF_ROLL;
1505                                         inAir = false;
1506                                         liningUp = false;
1507                                 }
1508                         }
1509                 } else if(taxiState == TD_OUTBOUND) {
1510                         // Pause awaiting further instructions
1511                         // and for now assume we've reached the hold-short node
1512                         holdingShort = true;
1513                 } // else at the moment assume TD_INBOUND always ends in a gate in which case we can ignore it
1514         } else {
1515                 // Time to turn (we've already checked it's not the end we're heading for).
1516                 // set the target node to be the next node which will prompt automatically turning onto
1517                 // the right heading in the stuff above, with the usual provisos applied.
1518                 GetNextTaxiNode();
1519                 // For now why not just recursively call this function?
1520                 Taxi(dt);
1521         }
1522 }
1523
1524
1525 // Warning - ground elev determination is CPU intensive
1526 // Either this function or the logic of how often it is called
1527 // will almost certainly change.
1528 void FGAILocalTraffic::DoGroundElev() {
1529         // Only do the proper hitlist stuff if we are within visible range of the viewer.
1530         double visibility_meters = fgGetDouble("/environment/visibility-m");
1531         FGViewer* vw = globals->get_current_view();
1532         if(dclGetHorizontalSeparation(_pos, SGGeod::fromGeodM(vw->getPosition(), 0.0)) > visibility_meters) {
1533                 _ground_elevation_m = aptElev;
1534                 return;
1535         }
1536
1537   // FIXME: make shure the pos.lat/pos.lon values are in degrees ...
1538   double range = 500.0;
1539   if (!globals->get_tile_mgr()->scenery_available(_aip.getPosition(), range)) {
1540     // Try to shedule tiles for that position.
1541     globals->get_tile_mgr()->update( _aip.getPosition(), range );
1542   }
1543
1544   // FIXME: make shure the pos.lat/pos.lon values are in degrees ...
1545   double alt;
1546   if (globals->get_scenery()->get_elevation_m(SGGeod::fromGeodM(_aip.getPosition(), 20000), alt, 0, _aip.getSceneGraph()))
1547     _ground_elevation_m = alt;
1548 }
1549