]> git.mxchange.org Git - flightgear.git/blob - src/ATC/AILocalTraffic.cxx
Make AI traffic more robust to not getting a list of runway exits - it is now simply...
[flightgear.git] / src / ATC / 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., 675 Mass Ave, Cambridge, MA 02139, USA.
21
22 #ifdef HAVE_CONFIG_H
23 #  include <config.h>
24 #endif
25
26 #include <Main/globals.hxx>
27 #include <Main/location.hxx>
28 #include <Scenery/scenery.hxx>
29 #include <simgear/math/point3d.hxx>
30 #include <simgear/math/sg_geodesy.hxx>
31 #include <simgear/misc/sg_path.hxx>
32 #include <string>
33 #include <math.h>
34
35 SG_USING_STD(string);
36
37 #include "ATCmgr.hxx"
38 #include "AILocalTraffic.hxx"
39 #include "ATCutils.hxx"
40
41 FGAILocalTraffic::FGAILocalTraffic() {
42         //Hardwire initialisation for now - a lot of this should be read in from config eventually
43         Vr = 70.0;
44         best_rate_of_climb_speed = 70.0;
45         //best_rate_of_climb;
46         //nominal_climb_speed;
47         //nominal_climb_rate;
48         //nominal_circuit_speed;
49         //min_circuit_speed;
50         //max_circuit_speed;
51         nominal_descent_rate = 500.0;
52         nominal_final_speed = 65.0;
53         //nominal_approach_speed;
54         //stall_speed_landing_config;
55         nominalTaxiSpeed = 8.0;
56         taxiTurnRadius = 8.0;
57         // Init the property nodes
58         wind_from_hdg = fgGetNode("/environment/wind-from-heading-deg", true);
59         wind_speed_knots = fgGetNode("/environment/wind-speed-kts", true);
60         circuitsToFly = 0;
61 }
62
63 FGAILocalTraffic::~FGAILocalTraffic() {
64 }
65
66 void FGAILocalTraffic::Init() {
67         // Hack alert - Hardwired path!!
68         string planepath = "Aircraft/c172/Models/c172-dpm.ac";
69         SGPath path = globals->get_fg_root();
70         path.append(planepath);
71         aip.init(planepath.c_str());
72         aip.setVisible(true);
73         globals->get_scenery()->get_scene_graph()->addKid(aip.getSceneGraph());
74         // is it OK to leave it like this until the first time transform is called?
75         // Really ought to be started in a parking space unless otherwise specified?
76         
77         // Find the tower frequency - this is dependent on the ATC system being initialised before the AI system
78         // FIXME - ATM this is hardwired.
79         airportID = "KEMT";
80         AirportATC a;
81         if(globals->get_ATC_mgr()->GetAirportATCDetails((string)airportID, &a)) {
82                 if(a.tower_freq) {      // Has a tower
83                         tower = (FGTower*)globals->get_ATC_mgr()->GetATCPointer((string)airportID, TOWER);      // Maybe need some error checking here
84                         freq = (double)tower->get_freq() / 100.0;
85                         //cout << "***********************************AILocalTraffic freq = " << freq << '\n';
86                 } else {
87                         // Check CTAF, unicom etc
88                 }
89         } else {
90                 //cout << "Unable to find airport details in FGAILocalTraffic::Init()\n";
91         }
92
93         // Initiallise the FGAirportData structure
94         // This needs a complete overhaul soon - what happens if we have 2 AI planes at same airport - they don't both need a structure
95         // This needs to be handled by the ATC manager or similar so only one set of physical data per airport is instantiated
96         // ie. TODO TODO FIXME FIXME
97         airport.Init();
98         
99 }
100
101 // Commands to do something from higher level logic
102 void FGAILocalTraffic::FlyCircuits(int numCircuits, bool tag) {
103         circuitsToFly += numCircuits - 1;       // Hack (-1) because we only test and decrement circuitsToFly after landing
104                                                                                 // thus flying one to many circuits.  TODO - Need to sort this out better!
105         touchAndGo = tag;
106         
107         //At the moment we'll assume that we are always finished previous circuits when called,
108         //And just teleport to the threshold to start.
109         //This is a hack though, we need to check where we are and taxi out if appropriate.
110         operatingState = IN_PATTERN;
111 #define DCL_KEMT true
112         //#define DCL_KPAO true
113 #ifdef DCL_KEMT
114         // Hardwire to KEMT for now
115         // Hardwired points at each end of KEMT runway
116         Point3D P010(-118.037483, 34.081358, 296 * SG_FEET_TO_METER);
117         Point3D P190(-118.032308, 34.090456, 299.395263 * SG_FEET_TO_METER);
118         Point3D takeoff_end;
119         bool d010 = true;       // use this to change the hardwired runway direction
120         if(d010) {
121                 rwy.threshold_pos = P010;
122                 takeoff_end = P190;
123                 rwy.hdg = 25.32;        //from default.apt
124                 rwy.ID = 1;
125                 patternDirection = -1;  // Left
126                 pos.setelev(rwy.threshold_pos.elev() + (-8.5 * SG_FEET_TO_METER));  // This is a complete hack - the rendered runway takes the underlying scenery elev rather than the published runway elev so I should use height above terrain or something.
127         } else {
128                 rwy.threshold_pos = P190;
129                 takeoff_end = P010;
130                 rwy.hdg = 205.32;
131                 rwy.ID = 19;
132                 patternDirection = 1;   // Right
133                 pos.setelev(rwy.threshold_pos.elev() + (-0.0 * SG_FEET_TO_METER));  // This is a complete hack - the rendered runway takes the underlying scenery elev rather than the published runway elev so I should use height above terrain or something.
134         }
135 #else   
136         //KPAO - might be a better choice since its in the default scenery
137         //Hardwire it to the default (no wind) direction
138         Point3D threshold_end(-122.1124358, 37.45848783, 6.8 * SG_FEET_TO_METER);       // These positions are from airnav.com and don't quite seem to correspond with the sim scenery
139         Point3D takeoff_end(-122.1176522, 37.463752, 6.7 * SG_FEET_TO_METER);
140         rwy.threshold_pos = threshold_end;
141         rwy.hdg = 315.0;
142         rwy.ID = ???
143                 patternDirection = 1;   // Right
144         pos.setelev(rwy.threshold_pos.elev() + (-0.0 * SG_FEET_TO_METER));  // This is a complete hack - the rendered runway takes the underlying scenery elev rather than the published runway elev so I should use height above terrain or something.
145 #endif
146         
147         //rwy.threshold_pos.setlat(34.081358);
148         //rwy.threshold_pos.setlon(-118.037483);
149         //rwy.mag_hdg = 12.0;
150         //rwy.mag_var = 14.0;
151         //rwy.hdg = rwy.mag_hdg + rwy.mag_var;
152         //rwy.threshold_pos.setelev(296 * SG_FEET_TO_METER);
153         
154         // Initial position on threshold for now
155         // TODO - check wind / default runway
156         pos.setlat(rwy.threshold_pos.lat());
157         pos.setlon(rwy.threshold_pos.lon());
158         hdg = rwy.hdg;
159         
160         pitch = 0.0;
161         roll = 0.0;
162         leg = TAKEOFF_ROLL;
163         vel = 0.0;
164         slope = 0.0;
165         
166         // Now set the position of the plane and then re-get the elevation!! (Didn't work - elev always returned as zero) :-(
167         //aip.setPosition(pos.lon(), pos.lat(), pos.elev() * SG_METER_TO_FEET);
168         //cout << "*********************** elev in FGAILocalTraffic = " << aip.getFGLocation()->get_cur_elev_m() << '\n';
169         
170         // Set the projection for the local area
171         ortho.Init(rwy.threshold_pos, rwy.hdg); 
172         rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos);        // should come out as zero
173         // Hardwire to KEMT for now
174         rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
175         //cout << "*********************************************************************************\n";
176         //cout << "*********************************************************************************\n";
177         //cout << "*********************************************************************************\n";
178         //cout << "end1ortho = " << rwy.end1ortho << '\n';
179         //cout << "end2ortho = " << rwy.end2ortho << '\n';      // end2ortho.x() should be zero or thereabouts
180         
181         Transform();
182 }   
183
184 // Run the internal calculations
185 void FGAILocalTraffic::Update(double dt) {
186         //std::cout << "In FGAILocalTraffic::Update\n";
187         // Hardwire flying traffic pattern for now - eventually also needs to be able to taxi to and from runway and GA parking area.
188         switch(operatingState) {
189         case IN_PATTERN:
190                 FlyTrafficPattern(dt);
191                 Transform();
192                 break;
193         case TAXIING:
194                 Taxi(dt);
195                 Transform();
196                 break;
197         case PARKED:
198                 // Do nothing
199                 break;
200         default:
201                 break;
202         }
203         //cout << "elev in FGAILocalTraffic = " << aip.getFGLocation()->get_cur_elev_m() << '\n';
204         // This should become if(the plane has moved) then Transform()
205 }
206
207 // Fly a traffic pattern
208 // FIXME - far too much of the mechanics of turning, rolling, accellerating, descending etc is in here.
209 //         Move it out to FGAIPlane and have FlyTrafficPattern just specify what to do, not the implementation.
210 void FGAILocalTraffic::FlyTrafficPattern(double dt) {
211         // Need to differentiate between in-air (IAS governed) and on-ground (vel governed)
212         // Take-off is an interesting case - we are on the ground but takeoff speed is IAS governed.
213         bool inAir = true;      // FIXME - possibly make into a class variable
214         
215         static bool transmitted = false;        // FIXME - this is a hack
216
217         // WIND
218         // Wind has two effects - a mechanical one in that IAS translates to a different vel, and the hdg != track,
219         // but also a piloting effect, in that the AI must be able to descend at a different rate in order to hit the threshold.
220         
221         //cout << "dt = " << dt << '\n';
222         double dist = 0;
223         // ack - I can't remember how long a rate 1 turn is meant to take.
224         double turn_time = 60.0;        // seconds - TODO - check this guess
225         double turn_circumference;
226         double turn_radius;
227         Point3D orthopos = ortho.ConvertToLocal(pos);   // ortho position of the plane
228         //cout << "runway elev = " << rwy.threshold_pos.elev() << ' ' << rwy.threshold_pos.elev() * SG_METER_TO_FEET << '\n';
229         //cout << "elev = " << pos.elev() << ' ' << pos.elev() * SG_METER_TO_FEET << '\n';
230
231         // HACK FOR TESTING - REMOVE
232         //cout << "Calling ExitRunway..." << endl;
233         //ExitRunway(orthopos);
234         //return;
235         // END HACK
236         
237         //wind
238         double wind_from = wind_from_hdg->getDoubleValue();
239         double wind_speed = wind_speed_knots->getDoubleValue();
240
241         switch(leg) {
242         case TAKEOFF_ROLL:
243                 inAir = false;
244                 track = rwy.hdg;
245                 if(vel < 80.0) {
246                         double dveldt = 5.0;
247                         vel += dveldt * dt;
248                 }
249                 IAS = vel + (cos((hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
250                 if(IAS >= 70) {
251                         leg = CLIMBOUT;
252                         pitch = 10.0;
253                         IAS = best_rate_of_climb_speed;
254                         slope = 7.0;
255                 }
256                 break;
257         case CLIMBOUT:
258                 track = rwy.hdg;
259                 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 600) {
260                         leg = TURN1;
261                 }
262                 break;
263         case TURN1:
264                 track += (360.0 / turn_time) * dt * patternDirection;
265                 Bank(25.0 * patternDirection);
266                 if((track < (rwy.hdg - 89.0)) || (track > (rwy.hdg + 89.0))) {
267                         leg = CROSSWIND;
268                 }
269                 break;
270         case CROSSWIND:
271                 LevelWings();
272                 track = rwy.hdg + (90.0 * patternDirection);
273                 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
274                         slope = 0.0;
275                         pitch = 0.0;
276                         IAS = 80.0;             // FIXME - use smooth transistion to new speed
277                 }
278                 // turn 1000m out for now
279                 if(fabs(orthopos.x()) > 980) {
280                         leg = TURN2;
281                 }
282                 break;
283         case TURN2:
284                 track += (360.0 / turn_time) * dt * patternDirection;
285                 Bank(25.0 * patternDirection);
286                 // just in case we didn't make height on crosswind
287                 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
288                         slope = 0.0;
289                         pitch = 0.0;
290                         IAS = 80.0;             // FIXME - use smooth transistion to new speed
291                 }
292                 if((track < (rwy.hdg - 179.0)) || (track > (rwy.hdg + 179.0))) {
293                         leg = DOWNWIND;
294                         transmitted = false;
295                         //roll = 0.0;
296                 }
297                 break;
298         case DOWNWIND:
299                 LevelWings();
300                 track = rwy.hdg - (180 * patternDirection);     //should tend to bring track back into the 0->360 range
301                 // just in case we didn't make height on crosswind
302                 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
303                         slope = 0.0;
304                         pitch = 0.0;
305                         IAS = 90.0;             // FIXME - use smooth transistion to new speed
306                 }
307                 if((orthopos.y() < 0) && (!transmitted)) {
308                         TransmitPatternPositionReport();
309                         transmitted = true;
310                 }
311                 if(orthopos.y() < -480) {
312                         slope = -4.0;   // FIXME - calculate to descent at 500fpm and hit the threshold (taking wind into account as well!!)
313                         pitch = -3.0;
314                         IAS = 85.0;
315                 }
316                 if(orthopos.y() < -980) {
317                         //roll = -20;
318                         leg = TURN3;
319                         transmitted = false;
320                         IAS = 80.0;
321                 }
322                 break;
323         case TURN3:
324                 track += (360.0 / turn_time) * dt * patternDirection;
325                 Bank(25.0 * patternDirection);
326                 if(fabs(rwy.hdg - track) < 91.0) {
327                         leg = BASE;
328                 }
329                 break;
330         case BASE:
331                 LevelWings();
332                 if(!transmitted) {
333                         TransmitPatternPositionReport();
334                         transmitted = true;
335                 }
336                 track = rwy.hdg - (90 * patternDirection);
337                 slope = -6.0;   // FIXME - calculate to descent at 500fpm and hit the threshold
338                 pitch = -4.0;
339                 IAS = 70.0;     // FIXME - slowdown gradually
340                 // Try and arrange to turn nicely onto base
341                 turn_circumference = IAS * 0.514444 * turn_time;        
342                 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
343                 //We'll leave it as a hack with IAS for now but it needs revisiting.
344                 
345                 turn_radius = turn_circumference / (2.0 * DCL_PI);
346                 if(fabs(orthopos.x()) < (turn_radius + 50)) {
347                         leg = TURN4;
348                         transmitted = false;
349                         //roll = -20;
350                 }
351                 break;
352         case TURN4:
353                 track += (360.0 / turn_time) * dt * patternDirection;
354                 Bank(25.0 * patternDirection);
355                 if(fabs(track - rwy.hdg) < 0.6) {
356                         leg = FINAL;
357                         vel = nominal_final_speed;
358                 }
359                 break;
360         case FINAL:
361                 LevelWings();
362                 if(!transmitted) {
363                         TransmitPatternPositionReport();
364                         transmitted = true;
365                 }
366                 // Try and track the extended centreline
367                 track = rwy.hdg - (0.2 * orthopos.x());
368                 //cout << "orthopos.x() = " << orthopos.x() << " hdg = " << hdg << '\n';
369                 if(pos.elev() <= rwy.threshold_pos.elev()) {
370                         pos.setelev(rwy.threshold_pos.elev());// + (-8.5 * SG_FEET_TO_METER));  // This is a complete hack - the rendered runway takes the underlying scenery elev rather than the published runway elev so I should use height above terrain or something.
371                         slope = 0.0;
372                         pitch = 0.0;
373                         leg = LANDING_ROLL;
374                 }
375                 break;
376         case LANDING_ROLL:
377                 inAir = false;
378                 track = rwy.hdg;
379                 double dveldt = -5.0;
380                 vel += dveldt * dt;
381                 // FIXME - differentiate between touch and go and full stops
382                 if(vel <= 15.0) {
383                         //cout << "Vel <= 15.0, circuitsToFly = " << circuitsToFly << endl;
384                         if(circuitsToFly <= 0) {
385                                 //cout << "Calling ExitRunway..." << endl;
386                                 ExitRunway(orthopos);
387                                 return;
388                         } else {
389                                 //cout << "Taking off again..." << endl;
390                                 leg = TAKEOFF_ROLL;
391                                 --circuitsToFly;
392                         }
393                 }
394                 break;
395     }
396
397         if(inAir) {
398                 // FIXME - at the moment this is a bit screwy
399                 // The velocity correction is applied based on the relative headings.
400                 // Then the heading is changed based on the velocity.
401                 // Which comes first, the chicken or the egg?
402                 // Does it really matter?
403                 
404                 // Apply wind to ground-relative velocity if in the air
405                 vel = IAS - (cos((hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
406                 //crab = f(track, wind, vel);
407                 // The vector we need to fly is our desired vector minus the wind vector
408                 // TODO - we probably ought to use plib's built in vector types and operations for this
409                 // ie.  There's almost *certainly* a better way to do this!
410                 double gxx = vel * sin(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity x component wrt ground
411                 double gyy = vel * cos(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity y component wrt ground
412                 double wxx = wind_speed * sin((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS);    // Wind velocity x component
413                 double wyy = wind_speed * cos((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS);    // Wind velocity y component
414                 double axx = gxx - wxx; // Plane in-air velocity x component
415                 double ayy = gyy - wyy; // Plane in-air velocity y component
416                 // Now we want the angle between gxx and axx (which is the crab)
417                 double maga = sqrt(axx*axx + ayy*ayy);
418                 double magg = sqrt(gxx*gxx + gyy*gyy);
419                 crab = acos((axx*gxx + ayy*gyy) / (maga * magg));
420                 // At this point this works except we're getting the modulus of the angle
421                 //cout << "crab = " << crab << '\n';
422                 
423                 // Make sure both headings are in the 0->360 circle in order to get sane differences
424                 dclBoundHeading(wind_from);
425                 dclBoundHeading(track);
426                 if(track > wind_from) {
427                         if((track - wind_from) <= 180) {
428                                 crab *= -1.0;
429                         }
430                 } else {
431                         if((wind_from - track) >= 180) {
432                                 crab *= -1.0;
433                         }
434                 }
435         } else {        // on the ground - crab dosen't apply
436                 crab = 0.0;
437         }
438         
439         hdg = track + crab;
440         dist = vel * 0.514444 * dt;
441         pos = dclUpdatePosition(pos, track, slope, dist);
442 }
443
444 void FGAILocalTraffic::TransmitPatternPositionReport(void) {
445         // airport name + "traffic" + airplane callsign + pattern direction + pattern leg + rwy + ?
446         string trns = "";
447         
448         trns += tower->get_name();
449         trns += " Traffic ";
450         // FIXME - add the callsign to the class variables
451         trns += "Trainer-two-five-charlie ";
452         if(patternDirection == 1) {
453                 trns += "right ";
454         } else {
455                 trns += "left ";
456         }
457         
458         // We could probably get rid of this whole switch statement and just pass a string containing the leg from the FlyPattern function.
459         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?
460         case TURN1:
461                 // Fall through to CROSSWIND
462         case CROSSWIND: // I don't think this case will be used here but it can't hurt to leave it in
463                 trns += "crosswind ";
464                 break;
465         case TURN2:
466                 // Fall through to DOWNWIND
467         case DOWNWIND:
468                 trns += "downwind ";
469                 break;
470         case TURN3:
471                 // Fall through to BASE
472         case BASE:
473                 trns += "base ";
474                 break;
475         case TURN4:
476                 // Fall through to FINAL
477         case FINAL:             // maybe this should include long/short final if appropriate?
478                 trns += "final ";
479                 break;
480         default:                // Hopefully this won't be used
481                 trns += "pattern ";
482                 break;
483         }
484         // FIXME - I've hardwired the runway call as well!! (We could work this out from rwy heading and mag deviation)
485         trns += ConvertRwyNumToSpokenString(1);
486         
487         // And add the airport name again
488         trns += tower->get_name();
489         
490         Transmit(trns);
491 }
492
493 void FGAILocalTraffic::ExitRunway(Point3D orthopos) {
494         //cout << "In ExitRunway" << endl;
495         //cout << "Runway ID is " << rwy.ID << endl;
496         node_array_type exitNodes = airport.GetExits(rwy.ID);   //I suppose we ought to have some fallback for rwy with no defined exits?
497         //cout << "Got exits" << endl;
498         //cout << "Size of exits array is " << exitNodes.size() << endl;
499         if(exitNodes.size()) {
500                 //Find the next exit from orthopos.y
501                 double d;
502                 double dist = 100000;   //ie. longer than any runway in existance
503                 double backdist = 100000;
504                 node_array_iterator nItr = exitNodes.begin();
505                 node* rwyExit = *(exitNodes.begin());
506                 int gateID;             //This might want to be more persistant at some point
507                 while(nItr != exitNodes.end()) {
508                         d = ortho.ConvertToLocal((*nItr)->pos).y() - ortho.ConvertToLocal(pos).y();     //FIXME - consider making orthopos a class variable
509                         if(d > 0.0) {
510                                 if(d < dist) {
511                                         dist = d;
512                                         rwyExit = *nItr;
513                                 }
514                         } else {
515                                 if(fabs(d) < backdist) {
516                                         backdist = d;
517                                         //TODO - need some logic here that if we don't get a forward exit we turn round and store the backwards one
518                                 }
519                         }
520                         ++nItr;
521                 }
522                 //cout << "Calculated dist, dist = " << dist << endl;
523                 // GetNodeList(exitNode->parking) and add to from here to exit node 
524                 gateID = airport.GetRandomGateID();
525                 //cout << "gateID = " << gateID << endl;
526                 in_dest = airport.GetGateNode(gateID);
527                 //cout << "in_dest got..." << endl;
528                 path = airport.GetPath(rwyExit, in_dest);       //TODO - need to convert a and b to actual nodes!!
529                 //cout << "path got..." << endl;
530                 //cout << "Size of path is " << path.size() << endl;
531                 taxiState = TD_INBOUND;
532                 StartTaxi();
533         } else {
534                 // Something must have gone wrong with the ground network file - or there is only a rwy here and no exits defined
535                 SG_LOG(SG_GENERAL, SG_ALERT, "No exits found by FGAILocalTraffic from runway " << rwy.ID << " at " << airportID << '\n');
536                 // What shall we do - just remove the plane from sight?
537                 aip.setVisible(false);
538                 operatingState = PARKED;
539         }
540 }
541
542 // Set the class variable nextTaxiNode to the next node in the path
543 // and update taxiPathPos, the class variable path iterator position
544 // TODO - maybe should return error codes to the calling function if we fail here
545 void FGAILocalTraffic::GetNextTaxiNode() {
546         //cout << "GetNextTaxiNode called " << endl;
547         //cout << "taxiPathPos = " << taxiPathPos << endl;
548         ground_network_path_iterator pathItr = path.begin() + taxiPathPos;
549         if(pathItr == path.end()) {
550                 SG_LOG(SG_GENERAL, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - no more nodes in path\n");
551         } else {
552                 if((*pathItr)->struct_type == NODE) {
553                         //cout << "ITS A NODE" << endl;
554                         //*pathItr = new node;
555                         nextTaxiNode = (node*)*pathItr;
556                         ++taxiPathPos;
557                         //delete pathItr;
558                 } else {
559                         //cout << "ITS NOT A NODE" << endl;
560                         //The first item in found must have been an arc
561                         //Assume for now that it was straight
562                         pathItr++;
563                         taxiPathPos++;
564                         if(pathItr == path.end()) {
565                                 SG_LOG(SG_GENERAL, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - path ended with an arc\n");
566                         } else if((*pathItr)->struct_type == NODE) {
567                                 nextTaxiNode = (node*)*pathItr;
568                                 ++taxiPathPos;
569                         } else {
570                                 //OOPS - two non-nodes in a row - that shouldn't happen ATM
571                                 SG_LOG(SG_GENERAL, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - two non-nodes in sequence\n");
572                         }
573                 }
574         }
575 }           
576
577 // StartTaxi - set up the taxiing state - call only at the start of taxiing
578 void FGAILocalTraffic::StartTaxi() {
579         //cout << "StartTaxi called" << endl;
580         operatingState = TAXIING;
581         taxiPathPos = 0;
582         
583         //Set the desired heading
584         //Assume we are aiming for first node on path
585         //Eventually we may need to consider the fact that we might start on a curved arc and
586         //not be able to head directly for the first node.
587         GetNextTaxiNode();      // sets the class variable nextTaxiNode to the next taxi node!
588         desiredTaxiHeading = GetHeadingFromTo(pos, nextTaxiNode->pos);
589         //cout << "First taxi heading is " << desiredTaxiHeading << endl;
590 }
591
592 void FGAILocalTraffic::Taxi(double dt) {
593         //cout << "Taxi called" << endl;
594         // Logic - if we are further away from next point than turn radius then head for it
595         // If we have reached turning point then get next point and turn onto that heading
596         // Look out for the finish!!
597
598         Point3D orthopos = ortho.ConvertToLocal(pos);   // ortho position of the plane
599         desiredTaxiHeading = GetHeadingFromTo(pos, nextTaxiNode->pos);
600
601         // HACK ALERT! - for now we will taxi at constant speed for straights and turns
602         
603         // Remember that hdg is always equal to track when taxiing so we don't have to consider them both
604         double dist_to_go = dclGetHorizontalSeparation(pos, nextTaxiNode->pos); // we may be able to do this more cheaply using orthopos
605         //cout << "dist_to_go = " << dist_to_go << endl;
606         if((nextTaxiNode->type == GATE) && (dist_to_go <= 0.1)) {
607                 // park up
608                 //taxiing = false;
609                 //parked = true;
610                 operatingState = PARKED;
611         } else if((dist_to_go > taxiTurnRadius) || (nextTaxiNode->type == GATE)) {
612                 // if the turn radius is r, and speed is s, then in a time dt we turn through
613                 // ((s.dt)/(PI.r)) x 180 degrees
614                 // or alternatively (s.dt)/r radians
615                 //cout << "hdg = " << hdg << " desired taxi heading = " << desiredTaxiHeading << '\n';
616                 if(fabs(hdg - desiredTaxiHeading) > 0.1) {
617                         // Which is the quickest direction to turn onto heading?
618                         if(desiredTaxiHeading > hdg) {
619                                 if((desiredTaxiHeading - hdg) <= 180) {
620                                         // turn right
621                                         hdg += ((nominalTaxiSpeed * 0.514444 * dt) / (taxiTurnRadius * DCL_PI)) * 180.0;
622                                         // TODO - check that increments are less than the delta that we check for the right direction
623                                         // Probably need to reduce convergence speed as convergence is reached
624                                 } else {
625                                         hdg -= ((nominalTaxiSpeed * 0.514444 * dt) / (taxiTurnRadius * DCL_PI)) * 180.0;        
626                                 }
627                         } else {
628                                 if((hdg - desiredTaxiHeading) <= 180) {
629                                         // turn left
630                                         hdg -= ((nominalTaxiSpeed * 0.514444 * dt) / (taxiTurnRadius * DCL_PI)) * 180.0;
631                                         // TODO - check that increments are less than the delta that we check for the right direction
632                                         // Probably need to reduce convergence speed as convergence is reached
633                                 } else {
634                                         hdg += ((nominalTaxiSpeed * 0.514444 * dt) / (taxiTurnRadius * DCL_PI)) * 180.0;        
635                                 }
636                         }               
637                 }
638                 double vel = nominalTaxiSpeed;
639                 //cout << "vel = " << vel << endl;
640                 double dist = vel * 0.514444 * dt;
641                 //cout << "dist = " << dist << endl;
642                 double track = hdg;
643                 //cout << "track = " << track << endl;
644                 double slope = 0.0;
645                 pos = dclUpdatePosition(pos, track, slope, dist);
646                 //cout << "Updated position...\n";
647                 // FIXME - HACK in absense of proper ground elevation determination
648                 // Linearly interpolate altitude when taxiing between N and S extremes of orthopos
649                 pos.setelev((287.5 + ((299.3 - 287.5) * fabs(orthopos.y() / 1000.0))) * SG_FEET_TO_METER);
650         } else {
651                 // Time to turn (we've already checked it's not the end we're heading for).
652                 // set the target node to be the next node which will prompt automatically turning onto
653                 // the right heading in the stuff above, with the usual provisos applied.
654                 GetNextTaxiNode();
655                 // For now why not just recursively call this function?
656                 Taxi(dt);
657         }
658 }
659