]> git.mxchange.org Git - flightgear.git/blob - src/ATC/AILocalTraffic.cxx
This is step "1" of probably "many" in the process of separating out the
[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 <simgear/scene/model/location.hxx>
27
28 #include <Airports/runways.hxx>
29 #include <Main/globals.hxx>
30 #include <Scenery/scenery.hxx>
31 #include <Scenery/tilemgr.hxx>
32 #include <simgear/math/point3d.hxx>
33 #include <simgear/math/sg_geodesy.hxx>
34 #include <simgear/misc/sg_path.hxx>
35 #include <string>
36 #include <math.h>
37
38 SG_USING_STD(string);
39
40 #include "ATCmgr.hxx"
41 #include "AILocalTraffic.hxx"
42 #include "ATCutils.hxx"
43
44 FGAILocalTraffic::FGAILocalTraffic() {
45         ATC = globals->get_ATC_mgr();
46         
47         // TODO - unhardwire this - possibly let the AI manager set the callsign
48         plane.callsign = "Trainer-two-five-charlie";
49         plane.type = GA_SINGLE;
50         
51         roll = 0.0;
52         pitch = 0.0;
53         hdg = 270.0;
54         
55         //Hardwire initialisation for now - a lot of this should be read in from config eventually
56         Vr = 70.0;
57         best_rate_of_climb_speed = 70.0;
58         //best_rate_of_climb;
59         //nominal_climb_speed;
60         //nominal_climb_rate;
61         //nominal_circuit_speed;
62         //min_circuit_speed;
63         //max_circuit_speed;
64         nominal_descent_rate = 500.0;
65         nominal_final_speed = 65.0;
66         //nominal_approach_speed;
67         //stall_speed_landing_config;
68         nominalTaxiSpeed = 8.0;
69         taxiTurnRadius = 8.0;
70         wheelOffset = 1.45;     // Warning - hardwired to the C172 - we need to read this in from file.
71         elevInitGood = false;
72         // Init the property nodes
73         wind_from_hdg = fgGetNode("/environment/wind-from-heading-deg", true);
74         wind_speed_knots = fgGetNode("/environment/wind-speed-kts", true);
75         circuitsToFly = 0;
76         liningUp = false;
77         taxiRequestPending = false;
78         taxiRequestCleared = false;
79         holdingShort = false;
80         clearedToLineUp = false;
81         clearedToTakeOff = false;
82         reportReadyForDeparture = false;
83         contactTower = false;
84         contactGround = false;
85 }
86
87 FGAILocalTraffic::~FGAILocalTraffic() {
88 }
89
90
91 // Get details of the active runway
92 // It is assumed that by the time this is called the tower control and airport code will have been set up.
93 void FGAILocalTraffic::GetRwyDetails() {
94         //cout << "GetRwyDetails called" << endl;
95         
96         rwy.rwyID = tower->GetActiveRunway();
97         
98         // Now we need to get the threshold position and rwy heading
99         
100         SGPath path( globals->get_fg_root() );
101         path.append( "Airports" );
102         path.append( "runways.mk4" );
103         FGRunways runways( path.c_str() );
104
105         FGRunway runway;
106         bool rwyGood = runways.search(airportID, rwy.rwyID, &runway);
107         if(rwyGood) {
108                 // Get the threshold position
109         hdg = runway.heading;   // TODO - check - is this our heading we are setting here, and if so should we be?
110                 //cout << "hdg reset to " << hdg << '\n';
111                 double other_way = hdg - 180.0;
112                 while(other_way <= 0.0) {
113                         other_way += 360.0;
114                 }
115
116         // move to the +l end/center of the runway
117                 //cout << "Runway center is at " << runway.lon << ", " << runway.lat << '\n';
118         Point3D origin = Point3D(runway.lon, runway.lat, aptElev);
119                 Point3D ref = origin;
120         double tshlon, tshlat, tshr;
121                 double tolon, tolat, tor;
122                 rwy.length = runway.length * SG_FEET_TO_METER;
123         geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), other_way, 
124                                 rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
125         geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), hdg, 
126                                 rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor );
127                 // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user.
128                 // now copy what we need out of runway into rwy
129         rwy.threshold_pos = Point3D(tshlon, tshlat, aptElev);
130                 Point3D takeoff_end = Point3D(tolon, tolat, aptElev);
131                 //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n';
132                 //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
133                 rwy.hdg = hdg;
134                 // Set the projection for the local area
135                 ortho.Init(rwy.threshold_pos, rwy.hdg); 
136                 rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos);        // should come out as zero
137                 rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
138         } else {
139                 SG_LOG(SG_ATC, SG_ALERT, "Help  - can't get good runway in FGAILocalTraffic!!\n");
140         }
141 }
142
143 /* 
144 There are two possible scenarios during initialisation:
145 The first is that the user is flying towards the airport, and hence the traffic
146 could be initialised anywhere, as long as the AI planes are consistent with
147 each other.
148 The second is that the user has started the sim at or close to the airport, and
149 hence the traffic must be initialised with respect to the user as well as each other.
150 To a certain extent it's FGAIMgr that has to worry about this, but we need to provide
151 sufficient initialisation functionality within the plane classes to allow the manager
152 to initialy position them where and how required.
153 */
154 bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg initialLeg) {
155         //cout << "FGAILocalTraffic.Init(...) called" << endl;
156         // Hack alert - Hardwired path!!
157         string planepath = "Aircraft/c172/Models/c172-dpm.ac";
158         SGPath path = globals->get_fg_root();
159         path.append(planepath);
160         aip.init( path.str(), planepath.c_str(), globals->get_props(),
161                   globals->get_sim_time_sec() );
162         aip.setVisible(false);          // This will be set to true once a valid ground elevation has been determined
163         globals->get_scenery()->get_scene_graph()->addKid(aip.getSceneGraph());
164         
165         // Find the tower frequency - this is dependent on the ATC system being initialised before the AI system
166         airportID = ICAO;
167         AirportATC a;
168         if(ATC->GetAirportATCDetails(airportID, &a)) {
169                 if(a.tower_freq) {      // Has a tower
170                         tower = (FGTower*)ATC->GetATCPointer((string)airportID, TOWER); // Maybe need some error checking here
171                         if(tower == NULL) {
172                                 // Something has gone wrong - abort or carry on with un-towered operation?
173                                 return(false);
174                         }
175                         freq = (double)tower->get_freq() / 100.0;
176                         ground = tower->GetGroundPtr();
177                         if(ground == NULL) {
178                                 // Something has gone wrong :-(
179                                 SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a ground pointer from tower control in FGAILocalTraffic::Init() :-(");
180                                 return(false);
181                         } else if((initialState == PARKED) || (initialState == TAXIING)) {
182                                 freq = (double)ground->get_freq() / 100.0;
183                         }
184                         //cout << "AILocalTraffic freq is " << freq << '\n';
185                 } else {
186                         // Check CTAF, unicom etc
187                 }
188         } else {
189                 //cout << "Unable to find airport details in FGAILocalTraffic::Init()\n";
190         }
191
192         // Get the airport elevation
193         aptElev = dclGetAirportElev(airportID.c_str()) * SG_FEET_TO_METER;
194         //cout << "Airport elev in AILocalTraffic = " << aptElev << '\n';
195         // WARNING - we use this elev for the whole airport - some assumptions in the code 
196         // might fall down with very slopey airports.
197
198         //cout << "In Init(), initialState = " << initialState << endl;
199         operatingState = initialState;
200         switch(operatingState) {
201         case PARKED:
202                 ourGate = ground->GetGateNode();
203                 if(ourGate == NULL) {
204                         // Implies no available gates - what shall we do?
205                         // For now just vanish the plane - possibly we can make this more elegant in the future
206                         SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst attempting Init at " << airportID << '\n');
207                         return(false);
208                 }
209                 pitch = 0.0;
210                 roll = 0.0;
211                 vel = 0.0;
212                 slope = 0.0;
213                 pos = ourGate->pos;
214                 pos.setelev(aptElev);
215                 hdg = ourGate->heading;
216                 
217                 // Now we've set the position we can do the ground elev
218                 elevInitGood = false;
219                 inAir = false;
220                 DoGroundElev();
221                 
222                 Transform();
223                 break;
224         case TAXIING:
225                 // FIXME - implement this case properly
226                 return(false);  // remove this line when fixed!
227                 break;
228         case IN_PATTERN:
229                 // For now we'll always start the in_pattern case on the threshold ready to take-off
230                 // since we've got the implementation for this case already.
231                 // TODO - implement proper generic in_pattern startup.
232                 
233                 // Get the active runway details (and copy them into rwy)
234                 GetRwyDetails();
235
236                 // Initial position on threshold for now
237                 pos.setlat(rwy.threshold_pos.lat());
238                 pos.setlon(rwy.threshold_pos.lon());
239                 pos.setelev(rwy.threshold_pos.elev());
240                 hdg = rwy.hdg;
241                 
242                 // Now we've set the position we can do the ground elev
243                 // This might not always be necessary if we implement in-air start
244                 elevInitGood = false;
245                 inAir = false;
246                 DoGroundElev();
247                 
248                 pitch = 0.0;
249                 roll = 0.0;
250                 leg = TAKEOFF_ROLL;
251                 vel = 0.0;
252                 slope = 0.0;
253                 
254                 circuitsToFly = 0;              // ie just fly this circuit and then stop
255                 touchAndGo = false;
256                 // FIXME TODO - pattern direction is still hardwired
257                 patternDirection = -1;          // Left
258                 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
259                 if(rwy.rwyID.size() == 3) {
260                         patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
261                 }
262                 
263                 operatingState = IN_PATTERN;
264                 
265                 Transform();
266                 break;
267         default:
268                 SG_LOG(SG_ATC, SG_ALERT, "Attempt to set unknown operating state in FGAILocalTraffic.Init(...)\n");
269                 return(false);
270         }
271         
272         
273         return(true);
274 }
275
276 // Commands to do something from higher level logic
277 void FGAILocalTraffic::FlyCircuits(int numCircuits, bool tag) {
278         //cout << "FlyCircuits called" << endl;
279         
280         switch(operatingState) {
281         case IN_PATTERN:
282                 circuitsToFly += numCircuits;
283                 return;
284                 break;
285         case TAXIING:
286                 // For now we'll punt this and do nothing
287                 break;
288         case PARKED:
289                 circuitsToFly = numCircuits;    // Note that one too many circuits gets flown because we only test and decrement circuitsToFly after landing
290                                                                                 // thus flying one too many circuits.  TODO - Need to sort this out better!
291                 touchAndGo = tag;
292 #if 0   
293                 // Get the active runway details (and copy them into rwy)
294                 GetRwyDetails();
295                 
296                 // Get the takeoff node for the active runway, get a path to it and start taxiing
297                 path = ground->GetPath(ourGate, rwy.rwyID);
298                 if(path.size() < 2) {
299                         // something has gone wrong
300                         SG_LOG(SG_ATC, SG_ALERT, "Invalid path from gate to theshold in FGAILocalTraffic::FlyCircuits\n");
301                         return;
302                 }
303                 /*
304                 cout << "path returned was:" << endl;
305                 for(unsigned int i=0; i<path.size(); ++i) {
306                         switch(path[i]->struct_type) {
307                         case NODE:
308                                 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
309                                 break;
310                         case ARC:
311                                 cout << "ARC\n";
312                                 break;
313                         }
314                 }
315                 */
316                 // pop the gate - we're here already!
317                 path.erase(path.begin());
318                 //path.erase(path.begin());
319                 /*
320                 cout << "path after popping front is:" << endl;
321                 for(unsigned int i=0; i<path.size(); ++i) {
322                         switch(path[i]->struct_type) {
323                         case NODE:
324                                 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
325                                 break;
326                         case ARC:
327                                 cout << "ARC\n";
328                                 break;
329                         }
330                 }
331                 */
332                 
333                 taxiState = TD_OUTBOUND;
334                 StartTaxi();
335                 
336                 // Maybe the below should be set when we get to the threshold and prepare for TO?
337                 // FIXME TODO - pattern direction is still hardwired
338                 patternDirection = -1;          // Left
339                 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
340                 if(rwy.rwyID.size() == 3) {
341                         patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
342                 }
343
344                 Transform();
345 #endif
346                 break;
347         }
348 }   
349
350 // Run the internal calculations
351 void FGAILocalTraffic::Update(double dt) {
352         //cout << "A" << flush;
353         double responseTime = 10.0;             // seconds - this should get more sophisticated at some point
354         responseCounter += dt;
355         if((contactTower) && (responseCounter >= 8.0)) {
356                 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
357                 string trns = "Tower ";
358                 double f = globals->get_ATC_mgr()->GetFrequency(airportID, TOWER) / 100.0;      
359                 char buf[10];
360                 sprintf(buf, "%f", f);
361                 trns += buf;
362                 trns += " ";
363                 trns += plane.callsign;
364                 Transmit(trns);
365                 responseCounter = 0.0;
366                 contactTower = false;
367                 changeFreq = true;
368                 changeFreqType = TOWER;
369         }
370         
371         if((changeFreq) && (responseCounter > 8.0)) {
372                 switch(changeFreqType) {
373                 case TOWER:
374                         freq = (double)tower->get_freq() / 100.0;
375                         //Transmit("DING!");
376                         // Contact the tower, even if only virtually
377                         changeFreq = false;
378                         tower->ContactAtHoldShort(plane, this, CIRCUIT);
379                         break;
380                 case GROUND:
381                         freq = (double)ground->get_freq() / 100.0;
382                         break;
383                 // And to avoid compiler warnings...
384                 case APPROACH:
385                         break;
386                 case ATIS:
387                         break;
388                 case ENROUTE:
389                         break;
390                 case DEPARTURE:
391                         break;
392                 case INVALID:
393                         break;
394                 }
395         }
396         
397         //cout << "." << flush;
398                 
399         switch(operatingState) {
400         case IN_PATTERN:
401                 //cout << "In IN_PATTERN\n";
402                 if(!inAir) DoGroundElev();
403                 if(!elevInitGood) {
404                         if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
405                                 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
406                                 //cout << "TAKEOFF_ROLL, POS = " << pos.lon() << ", " << pos.lat() << ", " << pos.elev() << '\n';
407                                 //Transform();
408                                 aip.setVisible(true);
409                                 //cout << "Making plane visible!\n";
410                                 elevInitGood = true;
411                         }
412                 }
413                 FlyTrafficPattern(dt);
414                 Transform();
415                 break;
416         case TAXIING:
417                 //cout << "In TAXIING\n";
418                 //cout << "*" << flush;
419                 if(!elevInitGood) {
420                         //DoGroundElev();
421                         if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
422                                 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
423                                 //Transform();
424                                 aip.setVisible(true);
425                                 //Transform();
426                                 //cout << "Making plane visible!\n";
427                                 elevInitGood = true;
428                         }
429                 }
430                 DoGroundElev();
431                 //cout << "," << flush;
432                 if(!((holdingShort) && (!clearedToLineUp))) {
433                         //cout << "|" << flush;
434                         Taxi(dt);
435                 }
436                 //cout << ";" << flush;
437                 if((clearedToTakeOff) && (responseCounter >= 8.0)) {
438                         // possible assumption that we're at the hold short here - may not always hold
439                         // TODO - sort out the case where we're cleared to line-up first and then cleared to take-off on the rwy.
440                         taxiState = TD_LINING_UP;
441                         path = ground->GetPath(holdShortNode, rwy.rwyID);
442                         /*
443                         cout << "path returned was:" << endl;
444                         for(unsigned int i=0; i<path.size(); ++i) {
445                                 switch(path[i]->struct_type) {
446                                         case NODE:
447                                         cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
448                                         break;
449                                         case ARC:
450                                         cout << "ARC\n";
451                                         break;
452                                 }
453                         }
454                         */
455                         clearedToTakeOff = false;       // We *are* still cleared - this simply stops the response recurring!!
456                         holdingShort = false;
457                         string trns = "Cleared for take-off ";
458                         trns += plane.callsign;
459                         Transmit(trns);
460                         StartTaxi();
461                 }
462                 //cout << "^" << flush;
463                 Transform();
464                 break;
465                 case PARKED:
466                 //cout << "In PARKED\n";
467                 if(!elevInitGood) {
468                         DoGroundElev();
469                         if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
470                                 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
471                                 //Transform();
472                                 aip.setVisible(true);
473                                 //Transform();
474                                 //cout << "Making plane visible!\n";
475                                 elevInitGood = true;
476                         }
477                 }
478                 
479                 if(circuitsToFly) {
480                         if((taxiRequestPending) && (taxiRequestCleared)) {
481                                 //cout << "&" << flush;
482                                 // Get the active runway details (and copy them into rwy)
483                                 GetRwyDetails();
484                                 
485                                 // Get the takeoff node for the active runway, get a path to it and start taxiing
486                                 path = ground->GetPathToHoldShort(ourGate, rwy.rwyID);
487                                 if(path.size() < 2) {
488                                         // something has gone wrong
489                                         SG_LOG(SG_ATC, SG_ALERT, "Invalid path from gate to theshold in FGAILocalTraffic::FlyCircuits\n");
490                                         return;
491                                 }
492                                 /*
493                                 cout << "path returned was:\n";
494                                 for(unsigned int i=0; i<path.size(); ++i) {
495                                         switch(path[i]->struct_type) {
496                                                 case NODE:
497                                                 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
498                                                 break;
499                                                 case ARC:
500                                                 cout << "ARC\n";
501                                                 break;
502                                         }
503                                 }
504                                 */
505                                 path.erase(path.begin());       // pop the gate - we're here already!
506                                 taxiState = TD_OUTBOUND;
507                                 taxiRequestPending = false;
508                                 holdShortNode = (node*)(*(path.begin() + path.size()));
509                                 StartTaxi();
510                         } else if(!taxiRequestPending) {
511                                 //cout << "(" << flush;
512                                 ground->RequestDeparture(plane, this);
513                                 // Do some communication
514                                 // airport name + tower + airplane callsign + location + request taxi for + operation type + ?
515                                 string trns = "";
516                                 trns += tower->get_name();
517                                 trns += " tower ";
518                                 trns += plane.callsign;
519                                 trns += " on apron parking request taxi for traffic pattern";
520                                 //cout << "trns = " << trns << endl;
521                                 Transmit(trns);
522                                 taxiRequestCleared = false;
523                                 taxiRequestPending = true;
524                         }
525                 }
526                 
527                 //cout << "!" << flush;
528                                 
529                 // Maybe the below should be set when we get to the threshold and prepare for TO?
530                 // FIXME TODO - pattern direction is still hardwired
531                 patternDirection = -1;          // Left
532                 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
533                 if(rwy.rwyID.size() == 3) {
534                         patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
535                 }               
536                 // Do nothing
537                 Transform();
538                 //cout << ")" << flush;
539                 break;
540         default:
541                 break;
542         }
543         //cout << "I " << flush;
544 }
545
546 void FGAILocalTraffic::RegisterTransmission(int code) {
547         switch(code) {
548         case 1: // taxi request cleared
549                 taxiRequestCleared = true;
550                 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to taxi...");
551                 break;
552         case 2: // contact tower
553                 responseCounter = 0;
554                 contactTower = true;
555                 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact tower...");
556                 break;
557         case 3: // Cleared to line up
558                 responseCounter = 0;
559                 clearedToLineUp = true;
560                 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to line-up...");
561                 break;
562         case 4: // cleared to take-off
563                 responseCounter = 0;
564                 clearedToTakeOff = true;
565                 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to take-off...");
566                 break;
567         default:
568                 break;
569         }
570 }
571
572 // Fly a traffic pattern
573 // FIXME - far too much of the mechanics of turning, rolling, accellerating, descending etc is in here.
574 //         Move it out to FGAIPlane and have FlyTrafficPattern just specify what to do, not the implementation.
575 void FGAILocalTraffic::FlyTrafficPattern(double dt) {
576         // Need to differentiate between in-air (IAS governed) and on-ground (vel governed)
577         // Take-off is an interesting case - we are on the ground but takeoff speed is IAS governed.
578         
579         static bool transmitted = false;        // FIXME - this is a hack
580
581         // WIND
582         // Wind has two effects - a mechanical one in that IAS translates to a different vel, and the hdg != track,
583         // but also a piloting effect, in that the AI must be able to descend at a different rate in order to hit the threshold.
584         
585         //cout << "dt = " << dt << '\n';
586         double dist = 0;
587         // ack - I can't remember how long a rate 1 turn is meant to take.
588         double turn_time = 60.0;        // seconds - TODO - check this guess
589         double turn_circumference;
590         double turn_radius;
591         Point3D orthopos = ortho.ConvertToLocal(pos);   // ortho position of the plane
592         //cout << "runway elev = " << rwy.threshold_pos.elev() << ' ' << rwy.threshold_pos.elev() * SG_METER_TO_FEET << '\n';
593         //cout << "elev = " << pos.elev() << ' ' << pos.elev() * SG_METER_TO_FEET << '\n';
594
595         // HACK FOR TESTING - REMOVE
596         //cout << "Calling ExitRunway..." << endl;
597         //ExitRunway(orthopos);
598         //return;
599         // END HACK
600         
601         //wind
602         double wind_from = wind_from_hdg->getDoubleValue();
603         double wind_speed = wind_speed_knots->getDoubleValue();
604
605         switch(leg) {
606         case TAKEOFF_ROLL:
607                 //inAir = false;
608                 track = rwy.hdg;
609                 if(vel < 80.0) {
610                         double dveldt = 5.0;
611                         vel += dveldt * dt;
612                 }
613                 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
614                         pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
615                 }
616                 IAS = vel + (cos((hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
617                 if(IAS >= 70) {
618                         leg = CLIMBOUT;
619                         pitch = 10.0;
620                         IAS = best_rate_of_climb_speed;
621                         slope = 7.0;
622                         inAir = true;
623                 }
624                 break;
625         case CLIMBOUT:
626                 track = rwy.hdg;
627                 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 600) {
628                         cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n'; 
629                         leg = TURN1;
630                 }
631                 break;
632         case TURN1:
633                 track += (360.0 / turn_time) * dt * patternDirection;
634                 Bank(25.0 * patternDirection);
635                 if((track < (rwy.hdg - 89.0)) || (track > (rwy.hdg + 89.0))) {
636                         leg = CROSSWIND;
637                 }
638                 break;
639         case CROSSWIND:
640                 LevelWings();
641                 track = rwy.hdg + (90.0 * patternDirection);
642                 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
643                         slope = 0.0;
644                         pitch = 0.0;
645                         IAS = 80.0;             // FIXME - use smooth transistion to new speed
646                 }
647                 // turn 1000m out for now
648                 if(fabs(orthopos.x()) > 980) {
649                         leg = TURN2;
650                 }
651                 break;
652         case TURN2:
653                 track += (360.0 / turn_time) * dt * patternDirection;
654                 Bank(25.0 * patternDirection);
655                 // just in case we didn't make height on crosswind
656                 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
657                         slope = 0.0;
658                         pitch = 0.0;
659                         IAS = 80.0;             // FIXME - use smooth transistion to new speed
660                 }
661                 if((track < (rwy.hdg - 179.0)) || (track > (rwy.hdg + 179.0))) {
662                         leg = DOWNWIND;
663                         transmitted = false;
664                         //roll = 0.0;
665                 }
666                 break;
667         case DOWNWIND:
668                 LevelWings();
669                 track = rwy.hdg - (180 * patternDirection);     //should tend to bring track back into the 0->360 range
670                 // just in case we didn't make height on crosswind
671                 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
672                         slope = 0.0;
673                         pitch = 0.0;
674                         IAS = 90.0;             // FIXME - use smooth transistion to new speed
675                 }
676                 if((orthopos.y() < 0) && (!transmitted)) {
677                         TransmitPatternPositionReport();
678                         transmitted = true;
679                 }
680                 if(orthopos.y() < -480) {
681                         slope = -4.0;   // FIXME - calculate to descent at 500fpm and hit the threshold (taking wind into account as well!!)
682                         pitch = -3.0;
683                         IAS = 85.0;
684                 }
685                 if(orthopos.y() < -980) {
686                         //roll = -20;
687                         leg = TURN3;
688                         transmitted = false;
689                         IAS = 80.0;
690                 }
691                 break;
692         case TURN3:
693                 track += (360.0 / turn_time) * dt * patternDirection;
694                 Bank(25.0 * patternDirection);
695                 if(fabs(rwy.hdg - track) < 91.0) {
696                         leg = BASE;
697                 }
698                 break;
699         case BASE:
700                 LevelWings();
701                 if(!transmitted) {
702                         TransmitPatternPositionReport();
703                         transmitted = true;
704                 }
705                 track = rwy.hdg - (90 * patternDirection);
706                 slope = -6.0;   // FIXME - calculate to descent at 500fpm and hit the threshold
707                 pitch = -4.0;
708                 IAS = 70.0;     // FIXME - slowdown gradually
709                 // Try and arrange to turn nicely onto base
710                 turn_circumference = IAS * 0.514444 * turn_time;        
711                 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
712                 //We'll leave it as a hack with IAS for now but it needs revisiting.
713                 
714                 turn_radius = turn_circumference / (2.0 * DCL_PI);
715                 if(fabs(orthopos.x()) < (turn_radius + 50)) {
716                         leg = TURN4;
717                         transmitted = false;
718                         //roll = -20;
719                 }
720                 break;
721         case TURN4:
722                 track += (360.0 / turn_time) * dt * patternDirection;
723                 Bank(25.0 * patternDirection);
724                 if(fabs(track - rwy.hdg) < 0.6) {
725                         leg = FINAL;
726                         vel = nominal_final_speed;
727                 }
728                 break;
729         case FINAL:
730                 LevelWings();
731                 if(!transmitted) {
732                         TransmitPatternPositionReport();
733                         transmitted = true;
734                 }
735                 // Try and track the extended centreline
736                 track = rwy.hdg - (0.2 * orthopos.x());
737                 //cout << "orthopos.x() = " << orthopos.x() << " hdg = " << hdg << '\n';
738                 if(pos.elev() < (rwy.threshold_pos.elev()+20.0+wheelOffset)) {
739                         DoGroundElev(); // Need to call it here expicitly on final since it's only called
740                                         // for us in update(...) when the inAir flag is false.
741                 }
742                 if(pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) {
743                         if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
744                                 if((aip.getFGLocation()->get_cur_elev_m() + wheelOffset) > pos.elev()) {
745                                         slope = 0.0;
746                                         pitch = 0.0;
747                                         leg = LANDING_ROLL;
748                                         inAir = false;
749                                 }
750                         }       // else need a fallback position based on arpt elev in case ground elev determination fails?
751                 }
752                 break;
753         case LANDING_ROLL:
754                 //inAir = false;
755                 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
756                         pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
757                 }
758                 track = rwy.hdg;
759                 double dveldt = -5.0;
760                 vel += dveldt * dt;
761                 // FIXME - differentiate between touch and go and full stops
762                 if(vel <= 15.0) {
763                         //cout << "Vel <= 15.0, circuitsToFly = " << circuitsToFly << endl;
764                         if(circuitsToFly <= 0) {
765                                 //cout << "Calling ExitRunway..." << endl;
766                                 ExitRunway(orthopos);
767                                 return;
768                         } else {
769                                 //cout << "Taking off again..." << endl;
770                                 leg = TAKEOFF_ROLL;
771                                 --circuitsToFly;
772                         }
773                 }
774                 break;
775     }
776
777         if(inAir) {
778                 // FIXME - at the moment this is a bit screwy
779                 // The velocity correction is applied based on the relative headings.
780                 // Then the heading is changed based on the velocity.
781                 // Which comes first, the chicken or the egg?
782                 // Does it really matter?
783                 
784                 // Apply wind to ground-relative velocity if in the air
785                 vel = IAS - (cos((hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
786                 //crab = f(track, wind, vel);
787                 // The vector we need to fly is our desired vector minus the wind vector
788                 // TODO - we probably ought to use plib's built in vector types and operations for this
789                 // ie.  There's almost *certainly* a better way to do this!
790                 double gxx = vel * sin(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity x component wrt ground
791                 double gyy = vel * cos(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity y component wrt ground
792                 double wxx = wind_speed * sin((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS);    // Wind velocity x component
793                 double wyy = wind_speed * cos((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS);    // Wind velocity y component
794                 double axx = gxx - wxx; // Plane in-air velocity x component
795                 double ayy = gyy - wyy; // Plane in-air velocity y component
796                 // Now we want the angle between gxx and axx (which is the crab)
797                 double maga = sqrt(axx*axx + ayy*ayy);
798                 double magg = sqrt(gxx*gxx + gyy*gyy);
799                 crab = acos((axx*gxx + ayy*gyy) / (maga * magg));
800                 // At this point this works except we're getting the modulus of the angle
801                 //cout << "crab = " << crab << '\n';
802                 
803                 // Make sure both headings are in the 0->360 circle in order to get sane differences
804                 dclBoundHeading(wind_from);
805                 dclBoundHeading(track);
806                 if(track > wind_from) {
807                         if((track - wind_from) <= 180) {
808                                 crab *= -1.0;
809                         }
810                 } else {
811                         if((wind_from - track) >= 180) {
812                                 crab *= -1.0;
813                         }
814                 }
815         } else {        // on the ground - crab dosen't apply
816                 crab = 0.0;
817         }
818         
819         hdg = track + crab;
820         dist = vel * 0.514444 * dt;
821         pos = dclUpdatePosition(pos, track, slope, dist);
822 }
823
824 void FGAILocalTraffic::TransmitPatternPositionReport(void) {
825         // airport name + "traffic" + airplane callsign + pattern direction + pattern leg + rwy + ?
826         string trns = "";
827         
828         trns += tower->get_name();
829         trns += " Traffic ";
830         trns += plane.callsign;
831         if(patternDirection == 1) {
832                 trns += " right ";
833         } else {
834                 trns += " left ";
835         }
836         
837         // We could probably get rid of this whole switch statement and just pass a string containing the leg from the FlyPattern function.
838         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?
839         case TURN1:
840                 // Fall through to CROSSWIND
841         case CROSSWIND: // I don't think this case will be used here but it can't hurt to leave it in
842                 trns += "crosswind ";
843                 break;
844         case TURN2:
845                 // Fall through to DOWNWIND
846         case DOWNWIND:
847                 trns += "downwind ";
848                 break;
849         case TURN3:
850                 // Fall through to BASE
851         case BASE:
852                 trns += "base ";
853                 break;
854         case TURN4:
855                 // Fall through to FINAL
856         case FINAL:             // maybe this should include long/short final if appropriate?
857                 trns += "final ";
858                 break;
859         default:                // Hopefully this won't be used
860                 trns += "pattern ";
861                 break;
862         }
863         // FIXME - I've hardwired the runway call as well!! (We could work this out from rwy heading and mag deviation)
864         trns += ConvertRwyNumToSpokenString(1);
865         
866         // And add the airport name again
867         trns += tower->get_name();
868         
869         Transmit(trns);
870 }
871
872 void FGAILocalTraffic::ExitRunway(Point3D orthopos) {
873         //cout << "In ExitRunway" << endl;
874         //cout << "Runway ID is " << rwy.ID << endl;
875         node_array_type exitNodes = ground->GetExits(rwy.rwyID);        //I suppose we ought to have some fallback for rwy with no defined exits?
876         /*
877         cout << "Node ID's of exits are ";
878         for(unsigned int i=0; i<exitNodes.size(); ++i) {
879                 cout << exitNodes[i]->nodeID << ' ';
880         }
881         cout << endl;
882         */
883         if(exitNodes.size()) {
884                 //Find the next exit from orthopos.y
885                 double d;
886                 double dist = 100000;   //ie. longer than any runway in existance
887                 double backdist = 100000;
888                 node_array_iterator nItr = exitNodes.begin();
889                 node* rwyExit = *(exitNodes.begin());
890                 //int gateID;           //This might want to be more persistant at some point
891                 while(nItr != exitNodes.end()) {
892                         d = ortho.ConvertToLocal((*nItr)->pos).y() - ortho.ConvertToLocal(pos).y();     //FIXME - consider making orthopos a class variable
893                         if(d > 0.0) {
894                                 if(d < dist) {
895                                         dist = d;
896                                         rwyExit = *nItr;
897                                 }
898                         } else {
899                                 if(fabs(d) < backdist) {
900                                         backdist = d;
901                                         //TODO - need some logic here that if we don't get a forward exit we turn round and store the backwards one
902                                 }
903                         }
904                         ++nItr;
905                 }
906                 ourGate = ground->GetGateNode();
907                 if(ourGate == NULL) {
908                         // Implies no available gates - what shall we do?
909                         // For now just vanish the plane - possibly we can make this more elegant in the future
910                         SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst landing at " << airportID << '\n');
911                         aip.setVisible(false);
912                         operatingState = PARKED;
913                         return;
914                 }
915                 path = ground->GetPath(rwyExit, ourGate);
916                 /*
917                 cout << "path returned was:" << endl;
918                 for(unsigned int i=0; i<path.size(); ++i) {
919                         switch(path[i]->struct_type) {
920                         case NODE:
921                                 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
922                                 break;
923                         case ARC:
924                                 cout << "ARC\n";
925                                 break;
926                         }
927                 }
928                 */
929                 taxiState = TD_INBOUND;
930                 StartTaxi();
931         } else {
932                 // Something must have gone wrong with the ground network file - or there is only a rwy here and no exits defined
933                 SG_LOG(SG_ATC, SG_ALERT, "No exits found by FGAILocalTraffic from runway " << rwy.rwyID << " at " << airportID << '\n');
934                 // What shall we do - just remove the plane from sight?
935                 aip.setVisible(false);
936                 operatingState = PARKED;
937         }
938 }
939
940 // Set the class variable nextTaxiNode to the next node in the path
941 // and update taxiPathPos, the class variable path iterator position
942 // TODO - maybe should return error codes to the calling function if we fail here
943 void FGAILocalTraffic::GetNextTaxiNode() {
944         //cout << "GetNextTaxiNode called " << endl;
945         //cout << "taxiPathPos = " << taxiPathPos << endl;
946         ground_network_path_iterator pathItr = path.begin() + taxiPathPos;
947         if(pathItr == path.end()) {
948                 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - no more nodes in path\n");
949         } else {
950                 if((*pathItr)->struct_type == NODE) {
951                         //cout << "ITS A NODE" << endl;
952                         //*pathItr = new node;
953                         nextTaxiNode = (node*)*pathItr;
954                         ++taxiPathPos;
955                         //delete pathItr;
956                 } else {
957                         //cout << "ITS NOT A NODE" << endl;
958                         //The first item in found must have been an arc
959                         //Assume for now that it was straight
960                         pathItr++;
961                         taxiPathPos++;
962                         if(pathItr == path.end()) {
963                                 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - path ended with an arc\n");
964                         } else if((*pathItr)->struct_type == NODE) {
965                                 nextTaxiNode = (node*)*pathItr;
966                                 ++taxiPathPos;
967                         } else {
968                                 //OOPS - two non-nodes in a row - that shouldn't happen ATM
969                                 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - two non-nodes in sequence\n");
970                         }
971                 }
972         }
973 }           
974
975 // StartTaxi - set up the taxiing state - call only at the start of taxiing
976 void FGAILocalTraffic::StartTaxi() {
977         //cout << "StartTaxi called" << endl;
978         operatingState = TAXIING;
979         taxiPathPos = 0;
980         
981         //Set the desired heading
982         //Assume we are aiming for first node on path
983         //Eventually we may need to consider the fact that we might start on a curved arc and
984         //not be able to head directly for the first node.
985         GetNextTaxiNode();      // sets the class variable nextTaxiNode to the next taxi node!
986         desiredTaxiHeading = GetHeadingFromTo(pos, nextTaxiNode->pos);
987         //cout << "First taxi heading is " << desiredTaxiHeading << endl;
988 }
989
990 // speed in knots, headings in degrees, radius in meters.
991 static double TaxiTurnTowardsHeading(double current_hdg, double desired_hdg, double speed, double radius, double dt) {
992         // wrap heading - this prevents a logic bug where the plane would just go round in circles!!
993         while(current_hdg < 0.0) {
994                 current_hdg += 360.0;
995         }
996         while(current_hdg > 360.0) {
997                 current_hdg -= 360.0;
998         }
999         if(fabs(current_hdg - desired_hdg) > 0.1) {
1000                 // Which is the quickest direction to turn onto heading?
1001                 if(desired_hdg > current_hdg) {
1002                         if((desired_hdg - current_hdg) <= 180) {
1003                                 // turn right
1004                                 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1005                                 // TODO - check that increments are less than the delta that we check for the right direction
1006                                 // Probably need to reduce convergence speed as convergence is reached
1007                         } else {
1008                                 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;   
1009                         }
1010                 } else {
1011                         if((current_hdg - desired_hdg) <= 180) {
1012                                 // turn left
1013                                 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1014                                 // TODO - check that increments are less than the delta that we check for the right direction
1015                                 // Probably need to reduce convergence speed as convergence is reached
1016                         } else {
1017                                 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;   
1018                         }
1019                 }               
1020         }
1021         return(current_hdg);
1022 }
1023
1024 void FGAILocalTraffic::Taxi(double dt) {
1025         //cout << "Taxi called" << endl;
1026         // Logic - if we are further away from next point than turn radius then head for it
1027         // If we have reached turning point then get next point and turn onto that heading
1028         // Look out for the finish!!
1029
1030         //Point3D orthopos = ortho.ConvertToLocal(pos); // ortho position of the plane
1031         desiredTaxiHeading = GetHeadingFromTo(pos, nextTaxiNode->pos);
1032         
1033         bool lastNode = (taxiPathPos == path.size() ? true : false);
1034         if(lastNode) {
1035                 //cout << "LAST NODE\n";
1036         }
1037
1038         // HACK ALERT! - for now we will taxi at constant speed for straights and turns
1039         
1040         // Remember that hdg is always equal to track when taxiing so we don't have to consider them both
1041         double dist_to_go = dclGetHorizontalSeparation(pos, nextTaxiNode->pos); // we may be able to do this more cheaply using orthopos
1042         //cout << "dist_to_go = " << dist_to_go << endl;
1043         if((nextTaxiNode->type == GATE) && (dist_to_go <= 0.1)) {
1044                 // This might be more robust to outward paths starting with a gate if we check for either
1045                 // last node or TD_INBOUND ?
1046                 // park up
1047                 operatingState = PARKED;
1048         } else if(((dist_to_go > taxiTurnRadius) || (nextTaxiNode->type == GATE)) && (!liningUp)){
1049                 // if the turn radius is r, and speed is s, then in a time dt we turn through
1050                 // ((s.dt)/(PI.r)) x 180 degrees
1051                 // or alternatively (s.dt)/r radians
1052                 //cout << "hdg = " << hdg << " desired taxi heading = " << desiredTaxiHeading << '\n';
1053                 hdg = TaxiTurnTowardsHeading(hdg, desiredTaxiHeading, nominalTaxiSpeed, taxiTurnRadius, dt);
1054                 double vel = nominalTaxiSpeed;
1055                 //cout << "vel = " << vel << endl;
1056                 double dist = vel * 0.514444 * dt;
1057                 //cout << "dist = " << dist << endl;
1058                 double track = hdg;
1059                 //cout << "track = " << track << endl;
1060                 double slope = 0.0;
1061                 pos = dclUpdatePosition(pos, track, slope, dist);
1062                 //cout << "Updated position...\n";
1063                 if(aip.getFGLocation()->get_cur_elev_m() > -9990) {
1064                         pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
1065                 } // else don't change the elev until we get a valid ground elev again!
1066         } else if(lastNode) {
1067                 if(taxiState == TD_LINING_UP) {
1068                         if((!liningUp) && (dist_to_go <= taxiTurnRadius)) {
1069                                 liningUp = true;
1070                         }
1071                         if(liningUp) {
1072                                 hdg = TaxiTurnTowardsHeading(hdg, rwy.hdg, nominalTaxiSpeed, taxiTurnRadius, dt);
1073                                 double vel = nominalTaxiSpeed;
1074                                 //cout << "vel = " << vel << endl;
1075                                 double dist = vel * 0.514444 * dt;
1076                                 //cout << "dist = " << dist << endl;
1077                                 double track = hdg;
1078                                 //cout << "track = " << track << endl;
1079                                 double slope = 0.0;
1080                                 pos = dclUpdatePosition(pos, track, slope, dist);
1081                                 //cout << "Updated position...\n";
1082                                 if(aip.getFGLocation()->get_cur_elev_m() > -9990) {
1083                                         pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
1084                                 } // else don't change the elev until we get a valid ground elev again!
1085                                 if(fabs(hdg - rwy.hdg) <= 1.0) {
1086                                         operatingState = IN_PATTERN;
1087                                         leg = TAKEOFF_ROLL;
1088                                         inAir = false;
1089                                         liningUp = false;
1090                                 }
1091                         }
1092                 } else if(taxiState == TD_OUTBOUND) {
1093                         // Pause awaiting further instructions
1094                         // and for now assume we've reached the hold-short node
1095                         holdingShort = true;
1096                 } // else at the moment assume TD_INBOUND always ends in a gate in which case we can ignore it
1097         } else {
1098                 // Time to turn (we've already checked it's not the end we're heading for).
1099                 // set the target node to be the next node which will prompt automatically turning onto
1100                 // the right heading in the stuff above, with the usual provisos applied.
1101                 GetNextTaxiNode();
1102                 // For now why not just recursively call this function?
1103                 Taxi(dt);
1104         }
1105 }
1106
1107
1108 // Warning - ground elev determination is CPU intensive
1109 // Either this function or the logic of how often it is called
1110 // will almost certainly change.
1111 void FGAILocalTraffic::DoGroundElev() {
1112
1113         // It would be nice if we could set the correct tile center here in order to get a correct
1114         // answer with one call to the function, but what I tried in the two commented-out lines
1115         // below only intermittently worked, and I haven't quite groked why yet.
1116         //SGBucket buck(pos.lon(), pos.lat());
1117         //aip.getFGLocation()->set_tile_center(Point3D(buck.get_center_lon(), buck.get_center_lat(), 0.0));
1118         
1119         double visibility_meters = fgGetDouble("/environment/visibility-m");
1120         //globals->get_tile_mgr()->prep_ssg_nodes( acmodel_location,
1121         globals->get_tile_mgr()->prep_ssg_nodes( aip.getFGLocation(),   visibility_meters );
1122         Point3D scenery_center = globals->get_scenery()->get_center();
1123         globals->get_tile_mgr()->update( aip.getFGLocation(), visibility_meters, (aip.getFGLocation())->get_absolute_view_pos( scenery_center ) );
1124         // save results of update in FGLocation for fdm...
1125         
1126         //if ( globals->get_scenery()->get_cur_elev() > -9990 ) {
1127         //      acmodel_location->
1128         //      set_cur_elev_m( globals->get_scenery()->get_cur_elev() );
1129         //}
1130         
1131         // The need for this here means that at least 2 consecutive passes are needed :-(
1132         aip.getFGLocation()->set_tile_center( globals->get_scenery()->get_next_center() );
1133         
1134         //cout << "Transform Elev is " << globals->get_scenery()->get_cur_elev() << '\n';
1135         aip.getFGLocation()->set_cur_elev_m(globals->get_scenery()->get_cur_elev());
1136         //return(globals->get_scenery()->get_cur_elev());
1137 }
1138