1 // FGAILocalTraffic - AIEntity derived class with enough logic to
2 // fly and interact with the traffic pattern.
4 // Written by David Luff, started March 2002.
6 // Copyright (C) 2002 David C. Luff - david.luff@nottingham.ac.uk
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // General Public License for more details.
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 #include <Airports/runways.hxx>
27 #include <Main/globals.hxx>
28 #include <Main/location.hxx>
29 #include <Scenery/scenery.hxx>
30 #include <Scenery/tilemgr.hxx>
31 #include <simgear/math/point3d.hxx>
32 #include <simgear/math/sg_geodesy.hxx>
33 #include <simgear/misc/sg_path.hxx>
40 #include "AILocalTraffic.hxx"
41 #include "ATCutils.hxx"
43 FGAILocalTraffic::FGAILocalTraffic() {
44 ATC = globals->get_ATC_mgr();
46 // TODO - unhardwire this - possibly let the AI manager set the callsign
47 plane.callsign = "Trainer-two-five-charlie";
48 plane.type = GA_SINGLE;
54 //Hardwire initialisation for now - a lot of this should be read in from config eventually
56 best_rate_of_climb_speed = 70.0;
58 //nominal_climb_speed;
60 //nominal_circuit_speed;
63 nominal_descent_rate = 500.0;
64 nominal_final_speed = 65.0;
65 //nominal_approach_speed;
66 //stall_speed_landing_config;
67 nominalTaxiSpeed = 8.0;
69 wheelOffset = 1.45; // Warning - hardwired to the C172 - we need to read this in from file.
71 // Init the property nodes
72 wind_from_hdg = fgGetNode("/environment/wind-from-heading-deg", true);
73 wind_speed_knots = fgGetNode("/environment/wind-speed-kts", true);
76 taxiRequestPending = false;
77 taxiRequestCleared = false;
79 clearedToLineUp = false;
80 clearedToTakeOff = false;
81 reportReadyForDeparture = false;
83 contactGround = false;
86 FGAILocalTraffic::~FGAILocalTraffic() {
90 // Get details of the active runway
91 // It is assumed that by the time this is called the tower control and airport code will have been set up.
92 void FGAILocalTraffic::GetRwyDetails() {
93 //cout << "GetRwyDetails called" << endl;
95 rwy.rwyID = tower->GetActiveRunway();
97 // Now we need to get the threshold position and rwy heading
99 SGPath path( globals->get_fg_root() );
100 path.append( "Airports" );
101 path.append( "runways.mk4" );
102 FGRunways runways( path.c_str() );
105 bool rwyGood = runways.search(airportID, rwy.rwyID, &runway);
107 // Get the threshold position
108 hdg = runway.heading; // TODO - check - is this our heading we are setting here, and if so should we be?
109 //cout << "hdg reset to " << hdg << '\n';
110 double other_way = hdg - 180.0;
111 while(other_way <= 0.0) {
115 // move to the +l end/center of the runway
116 //cout << "Runway center is at " << runway.lon << ", " << runway.lat << '\n';
117 Point3D origin = Point3D(runway.lon, runway.lat, aptElev);
118 Point3D ref = origin;
119 double tshlon, tshlat, tshr;
120 double tolon, tolat, tor;
121 rwy.length = runway.length * SG_FEET_TO_METER;
122 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), other_way,
123 rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
124 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), hdg,
125 rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor );
126 // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user.
127 // now copy what we need out of runway into rwy
128 rwy.threshold_pos = Point3D(tshlon, tshlat, aptElev);
129 Point3D takeoff_end = Point3D(tolon, tolat, aptElev);
130 //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n';
131 //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
133 // Set the projection for the local area
134 ortho.Init(rwy.threshold_pos, rwy.hdg);
135 rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos); // should come out as zero
136 rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
138 SG_LOG(SG_ATC, SG_ALERT, "Help - can't get good runway in FGAILocalTraffic!!\n");
143 There are two possible scenarios during initialisation:
144 The first is that the user is flying towards the airport, and hence the traffic
145 could be initialised anywhere, as long as the AI planes are consistent with
147 The second is that the user has started the sim at or close to the airport, and
148 hence the traffic must be initialised with respect to the user as well as each other.
149 To a certain extent it's FGAIMgr that has to worry about this, but we need to provide
150 sufficient initialisation functionality within the plane classes to allow the manager
151 to initialy position them where and how required.
153 bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg initialLeg) {
154 //cout << "FGAILocalTraffic.Init(...) called" << endl;
155 // Hack alert - Hardwired path!!
156 string planepath = "Aircraft/c172/Models/c172-dpm.ac";
157 SGPath path = globals->get_fg_root();
158 path.append(planepath);
159 aip.init(planepath.c_str());
160 aip.setVisible(false); // This will be set to true once a valid ground elevation has been determined
161 globals->get_scenery()->get_scene_graph()->addKid(aip.getSceneGraph());
163 // Find the tower frequency - this is dependent on the ATC system being initialised before the AI system
166 if(ATC->GetAirportATCDetails(airportID, &a)) {
167 if(a.tower_freq) { // Has a tower
168 tower = (FGTower*)ATC->GetATCPointer((string)airportID, TOWER); // Maybe need some error checking here
170 // Something has gone wrong - abort or carry on with un-towered operation?
173 freq = (double)tower->get_freq() / 100.0;
174 ground = tower->GetGroundPtr();
176 // Something has gone wrong :-(
177 SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a ground pointer from tower control in FGAILocalTraffic::Init() :-(");
179 } else if((initialState == PARKED) || (initialState == TAXIING)) {
180 freq = (double)ground->get_freq() / 100.0;
182 //cout << "AILocalTraffic freq is " << freq << '\n';
184 // Check CTAF, unicom etc
187 //cout << "Unable to find airport details in FGAILocalTraffic::Init()\n";
190 // Get the airport elevation
191 aptElev = dclGetAirportElev(airportID.c_str()) * SG_FEET_TO_METER;
192 //cout << "Airport elev in AILocalTraffic = " << aptElev << '\n';
193 // WARNING - we use this elev for the whole airport - some assumptions in the code
194 // might fall down with very slopey airports.
196 //cout << "In Init(), initialState = " << initialState << endl;
197 operatingState = initialState;
198 switch(operatingState) {
200 ourGate = ground->GetGateNode();
201 if(ourGate == NULL) {
202 // Implies no available gates - what shall we do?
203 // For now just vanish the plane - possibly we can make this more elegant in the future
204 SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst attempting Init at " << airportID << '\n');
212 pos.setelev(aptElev);
213 hdg = ourGate->heading;
215 // Now we've set the position we can do the ground elev
216 elevInitGood = false;
223 // FIXME - implement this case properly
224 return(false); // remove this line when fixed!
227 // For now we'll always start the in_pattern case on the threshold ready to take-off
228 // since we've got the implementation for this case already.
229 // TODO - implement proper generic in_pattern startup.
231 // Get the active runway details (and copy them into rwy)
234 // Initial position on threshold for now
235 pos.setlat(rwy.threshold_pos.lat());
236 pos.setlon(rwy.threshold_pos.lon());
237 pos.setelev(rwy.threshold_pos.elev());
240 // Now we've set the position we can do the ground elev
241 // This might not always be necessary if we implement in-air start
242 elevInitGood = false;
252 circuitsToFly = 0; // ie just fly this circuit and then stop
254 // FIXME TODO - pattern direction is still hardwired
255 patternDirection = -1; // Left
256 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
257 if(rwy.rwyID.size() == 3) {
258 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
261 operatingState = IN_PATTERN;
266 SG_LOG(SG_ATC, SG_ALERT, "Attempt to set unknown operating state in FGAILocalTraffic.Init(...)\n");
274 // Commands to do something from higher level logic
275 void FGAILocalTraffic::FlyCircuits(int numCircuits, bool tag) {
276 //cout << "FlyCircuits called" << endl;
278 switch(operatingState) {
280 circuitsToFly += numCircuits;
284 // For now we'll punt this and do nothing
287 circuitsToFly = numCircuits; // Note that one too many circuits gets flown because we only test and decrement circuitsToFly after landing
288 // thus flying one too many circuits. TODO - Need to sort this out better!
291 // Get the active runway details (and copy them into rwy)
294 // Get the takeoff node for the active runway, get a path to it and start taxiing
295 path = ground->GetPath(ourGate, rwy.rwyID);
296 if(path.size() < 2) {
297 // something has gone wrong
298 SG_LOG(SG_ATC, SG_ALERT, "Invalid path from gate to theshold in FGAILocalTraffic::FlyCircuits\n");
302 cout << "path returned was:" << endl;
303 for(unsigned int i=0; i<path.size(); ++i) {
304 switch(path[i]->struct_type) {
306 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
314 // pop the gate - we're here already!
315 path.erase(path.begin());
316 //path.erase(path.begin());
318 cout << "path after popping front is:" << endl;
319 for(unsigned int i=0; i<path.size(); ++i) {
320 switch(path[i]->struct_type) {
322 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
331 taxiState = TD_OUTBOUND;
334 // Maybe the below should be set when we get to the threshold and prepare for TO?
335 // FIXME TODO - pattern direction is still hardwired
336 patternDirection = -1; // Left
337 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
338 if(rwy.rwyID.size() == 3) {
339 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
348 // Run the internal calculations
349 void FGAILocalTraffic::Update(double dt) {
350 //cout << "A" << flush;
351 double responseTime = 10.0; // seconds - this should get more sophisticated at some point
352 responseCounter += dt;
353 if((contactTower) && (responseCounter >= 8.0)) {
354 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
355 string trns = "Tower ";
356 double f = globals->get_ATC_mgr()->GetFrequency(airportID, TOWER) / 100.0;
358 sprintf(buf, "%f", f);
361 trns += plane.callsign;
363 responseCounter = 0.0;
364 contactTower = false;
366 changeFreqType = TOWER;
369 if((changeFreq) && (responseCounter > 8.0)) {
370 switch(changeFreqType) {
372 freq = (double)tower->get_freq() / 100.0;
374 // Contact the tower, even if only virtually
376 tower->ContactAtHoldShort(plane, this, CIRCUIT);
379 freq = (double)ground->get_freq() / 100.0;
381 // And to avoid compiler warnings...
395 //cout << "." << flush;
397 switch(operatingState) {
399 //cout << "In IN_PATTERN\n";
400 if(!inAir) DoGroundElev();
402 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
403 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
404 //cout << "TAKEOFF_ROLL, POS = " << pos.lon() << ", " << pos.lat() << ", " << pos.elev() << '\n';
406 aip.setVisible(true);
407 //cout << "Making plane visible!\n";
411 FlyTrafficPattern(dt);
415 //cout << "In TAXIING\n";
416 //cout << "*" << flush;
419 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
420 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
422 aip.setVisible(true);
424 //cout << "Making plane visible!\n";
429 //cout << "," << flush;
430 if(!((holdingShort) && (!clearedToLineUp))) {
431 //cout << "|" << flush;
434 //cout << ";" << flush;
435 if((clearedToTakeOff) && (responseCounter >= 8.0)) {
436 // possible assumption that we're at the hold short here - may not always hold
437 // TODO - sort out the case where we're cleared to line-up first and then cleared to take-off on the rwy.
438 taxiState = TD_LINING_UP;
439 path = ground->GetPath(holdShortNode, rwy.rwyID);
441 cout << "path returned was:" << endl;
442 for(unsigned int i=0; i<path.size(); ++i) {
443 switch(path[i]->struct_type) {
445 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
453 clearedToTakeOff = false; // We *are* still cleared - this simply stops the response recurring!!
454 holdingShort = false;
455 string trns = "Cleared for take-off ";
456 trns += plane.callsign;
460 //cout << "^" << flush;
464 //cout << "In PARKED\n";
467 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
468 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
470 aip.setVisible(true);
472 //cout << "Making plane visible!\n";
478 if((taxiRequestPending) && (taxiRequestCleared)) {
479 //cout << "&" << flush;
480 // Get the active runway details (and copy them into rwy)
483 // Get the takeoff node for the active runway, get a path to it and start taxiing
484 path = ground->GetPathToHoldShort(ourGate, rwy.rwyID);
485 if(path.size() < 2) {
486 // something has gone wrong
487 SG_LOG(SG_ATC, SG_ALERT, "Invalid path from gate to theshold in FGAILocalTraffic::FlyCircuits\n");
491 cout << "path returned was:\n";
492 for(unsigned int i=0; i<path.size(); ++i) {
493 switch(path[i]->struct_type) {
495 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
503 path.erase(path.begin()); // pop the gate - we're here already!
504 taxiState = TD_OUTBOUND;
505 taxiRequestPending = false;
506 holdShortNode = (node*)(*(path.begin() + path.size()));
508 } else if(!taxiRequestPending) {
509 //cout << "(" << flush;
510 ground->RequestDeparture(plane, this);
511 // Do some communication
512 // airport name + tower + airplane callsign + location + request taxi for + operation type + ?
514 trns += tower->get_name();
516 trns += plane.callsign;
517 trns += " on apron parking request taxi for traffic pattern";
518 //cout << "trns = " << trns << endl;
520 taxiRequestCleared = false;
521 taxiRequestPending = true;
525 //cout << "!" << flush;
527 // Maybe the below should be set when we get to the threshold and prepare for TO?
528 // FIXME TODO - pattern direction is still hardwired
529 patternDirection = -1; // Left
530 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
531 if(rwy.rwyID.size() == 3) {
532 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
536 //cout << ")" << flush;
541 //cout << "I " << flush;
544 void FGAILocalTraffic::RegisterTransmission(int code) {
546 case 1: // taxi request cleared
547 taxiRequestCleared = true;
548 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to taxi...");
550 case 2: // contact tower
553 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact tower...");
555 case 3: // Cleared to line up
557 clearedToLineUp = true;
558 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to line-up...");
560 case 4: // cleared to take-off
562 clearedToTakeOff = true;
563 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to take-off...");
570 // Fly a traffic pattern
571 // FIXME - far too much of the mechanics of turning, rolling, accellerating, descending etc is in here.
572 // Move it out to FGAIPlane and have FlyTrafficPattern just specify what to do, not the implementation.
573 void FGAILocalTraffic::FlyTrafficPattern(double dt) {
574 // Need to differentiate between in-air (IAS governed) and on-ground (vel governed)
575 // Take-off is an interesting case - we are on the ground but takeoff speed is IAS governed.
577 static bool transmitted = false; // FIXME - this is a hack
580 // Wind has two effects - a mechanical one in that IAS translates to a different vel, and the hdg != track,
581 // but also a piloting effect, in that the AI must be able to descend at a different rate in order to hit the threshold.
583 //cout << "dt = " << dt << '\n';
585 // ack - I can't remember how long a rate 1 turn is meant to take.
586 double turn_time = 60.0; // seconds - TODO - check this guess
587 double turn_circumference;
589 Point3D orthopos = ortho.ConvertToLocal(pos); // ortho position of the plane
590 //cout << "runway elev = " << rwy.threshold_pos.elev() << ' ' << rwy.threshold_pos.elev() * SG_METER_TO_FEET << '\n';
591 //cout << "elev = " << pos.elev() << ' ' << pos.elev() * SG_METER_TO_FEET << '\n';
593 // HACK FOR TESTING - REMOVE
594 //cout << "Calling ExitRunway..." << endl;
595 //ExitRunway(orthopos);
600 double wind_from = wind_from_hdg->getDoubleValue();
601 double wind_speed = wind_speed_knots->getDoubleValue();
611 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
612 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
614 IAS = vel + (cos((hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
618 IAS = best_rate_of_climb_speed;
625 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 600) {
626 cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
631 track += (360.0 / turn_time) * dt * patternDirection;
632 Bank(25.0 * patternDirection);
633 if((track < (rwy.hdg - 89.0)) || (track > (rwy.hdg + 89.0))) {
639 track = rwy.hdg + (90.0 * patternDirection);
640 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
643 IAS = 80.0; // FIXME - use smooth transistion to new speed
645 // turn 1000m out for now
646 if(fabs(orthopos.x()) > 980) {
651 track += (360.0 / turn_time) * dt * patternDirection;
652 Bank(25.0 * patternDirection);
653 // just in case we didn't make height on crosswind
654 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
657 IAS = 80.0; // FIXME - use smooth transistion to new speed
659 if((track < (rwy.hdg - 179.0)) || (track > (rwy.hdg + 179.0))) {
667 track = rwy.hdg - (180 * patternDirection); //should tend to bring track back into the 0->360 range
668 // just in case we didn't make height on crosswind
669 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
672 IAS = 90.0; // FIXME - use smooth transistion to new speed
674 if((orthopos.y() < 0) && (!transmitted)) {
675 TransmitPatternPositionReport();
678 if(orthopos.y() < -480) {
679 slope = -4.0; // FIXME - calculate to descent at 500fpm and hit the threshold (taking wind into account as well!!)
683 if(orthopos.y() < -980) {
691 track += (360.0 / turn_time) * dt * patternDirection;
692 Bank(25.0 * patternDirection);
693 if(fabs(rwy.hdg - track) < 91.0) {
700 TransmitPatternPositionReport();
703 track = rwy.hdg - (90 * patternDirection);
704 slope = -6.0; // FIXME - calculate to descent at 500fpm and hit the threshold
706 IAS = 70.0; // FIXME - slowdown gradually
707 // Try and arrange to turn nicely onto base
708 turn_circumference = IAS * 0.514444 * turn_time;
709 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
710 //We'll leave it as a hack with IAS for now but it needs revisiting.
712 turn_radius = turn_circumference / (2.0 * DCL_PI);
713 if(fabs(orthopos.x()) < (turn_radius + 50)) {
720 track += (360.0 / turn_time) * dt * patternDirection;
721 Bank(25.0 * patternDirection);
722 if(fabs(track - rwy.hdg) < 0.6) {
724 vel = nominal_final_speed;
730 TransmitPatternPositionReport();
733 // Try and track the extended centreline
734 track = rwy.hdg - (0.2 * orthopos.x());
735 //cout << "orthopos.x() = " << orthopos.x() << " hdg = " << hdg << '\n';
736 if(pos.elev() < (rwy.threshold_pos.elev()+20.0+wheelOffset)) {
737 DoGroundElev(); // Need to call it here expicitly on final since it's only called
738 // for us in update(...) when the inAir flag is false.
740 if(pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) {
741 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
742 if((aip.getFGLocation()->get_cur_elev_m() + wheelOffset) > pos.elev()) {
748 } // else need a fallback position based on arpt elev in case ground elev determination fails?
753 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
754 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
757 double dveldt = -5.0;
759 // FIXME - differentiate between touch and go and full stops
761 //cout << "Vel <= 15.0, circuitsToFly = " << circuitsToFly << endl;
762 if(circuitsToFly <= 0) {
763 //cout << "Calling ExitRunway..." << endl;
764 ExitRunway(orthopos);
767 //cout << "Taking off again..." << endl;
776 // FIXME - at the moment this is a bit screwy
777 // The velocity correction is applied based on the relative headings.
778 // Then the heading is changed based on the velocity.
779 // Which comes first, the chicken or the egg?
780 // Does it really matter?
782 // Apply wind to ground-relative velocity if in the air
783 vel = IAS - (cos((hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
784 //crab = f(track, wind, vel);
785 // The vector we need to fly is our desired vector minus the wind vector
786 // TODO - we probably ought to use plib's built in vector types and operations for this
787 // ie. There's almost *certainly* a better way to do this!
788 double gxx = vel * sin(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity x component wrt ground
789 double gyy = vel * cos(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity y component wrt ground
790 double wxx = wind_speed * sin((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS); // Wind velocity x component
791 double wyy = wind_speed * cos((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS); // Wind velocity y component
792 double axx = gxx - wxx; // Plane in-air velocity x component
793 double ayy = gyy - wyy; // Plane in-air velocity y component
794 // Now we want the angle between gxx and axx (which is the crab)
795 double maga = sqrt(axx*axx + ayy*ayy);
796 double magg = sqrt(gxx*gxx + gyy*gyy);
797 crab = acos((axx*gxx + ayy*gyy) / (maga * magg));
798 // At this point this works except we're getting the modulus of the angle
799 //cout << "crab = " << crab << '\n';
801 // Make sure both headings are in the 0->360 circle in order to get sane differences
802 dclBoundHeading(wind_from);
803 dclBoundHeading(track);
804 if(track > wind_from) {
805 if((track - wind_from) <= 180) {
809 if((wind_from - track) >= 180) {
813 } else { // on the ground - crab dosen't apply
818 dist = vel * 0.514444 * dt;
819 pos = dclUpdatePosition(pos, track, slope, dist);
822 void FGAILocalTraffic::TransmitPatternPositionReport(void) {
823 // airport name + "traffic" + airplane callsign + pattern direction + pattern leg + rwy + ?
826 trns += tower->get_name();
828 trns += plane.callsign;
829 if(patternDirection == 1) {
835 // We could probably get rid of this whole switch statement and just pass a string containing the leg from the FlyPattern function.
836 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?
838 // Fall through to CROSSWIND
839 case CROSSWIND: // I don't think this case will be used here but it can't hurt to leave it in
840 trns += "crosswind ";
843 // Fall through to DOWNWIND
848 // Fall through to BASE
853 // Fall through to FINAL
854 case FINAL: // maybe this should include long/short final if appropriate?
857 default: // Hopefully this won't be used
861 // FIXME - I've hardwired the runway call as well!! (We could work this out from rwy heading and mag deviation)
862 trns += ConvertRwyNumToSpokenString(1);
864 // And add the airport name again
865 trns += tower->get_name();
870 void FGAILocalTraffic::ExitRunway(Point3D orthopos) {
871 //cout << "In ExitRunway" << endl;
872 //cout << "Runway ID is " << rwy.ID << endl;
873 node_array_type exitNodes = ground->GetExits(rwy.rwyID); //I suppose we ought to have some fallback for rwy with no defined exits?
875 cout << "Node ID's of exits are ";
876 for(unsigned int i=0; i<exitNodes.size(); ++i) {
877 cout << exitNodes[i]->nodeID << ' ';
881 if(exitNodes.size()) {
882 //Find the next exit from orthopos.y
884 double dist = 100000; //ie. longer than any runway in existance
885 double backdist = 100000;
886 node_array_iterator nItr = exitNodes.begin();
887 node* rwyExit = *(exitNodes.begin());
888 //int gateID; //This might want to be more persistant at some point
889 while(nItr != exitNodes.end()) {
890 d = ortho.ConvertToLocal((*nItr)->pos).y() - ortho.ConvertToLocal(pos).y(); //FIXME - consider making orthopos a class variable
897 if(fabs(d) < backdist) {
899 //TODO - need some logic here that if we don't get a forward exit we turn round and store the backwards one
904 ourGate = ground->GetGateNode();
905 if(ourGate == NULL) {
906 // Implies no available gates - what shall we do?
907 // For now just vanish the plane - possibly we can make this more elegant in the future
908 SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst landing at " << airportID << '\n');
909 aip.setVisible(false);
910 operatingState = PARKED;
913 path = ground->GetPath(rwyExit, ourGate);
915 cout << "path returned was:" << endl;
916 for(unsigned int i=0; i<path.size(); ++i) {
917 switch(path[i]->struct_type) {
919 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
927 taxiState = TD_INBOUND;
930 // Something must have gone wrong with the ground network file - or there is only a rwy here and no exits defined
931 SG_LOG(SG_ATC, SG_ALERT, "No exits found by FGAILocalTraffic from runway " << rwy.rwyID << " at " << airportID << '\n');
932 // What shall we do - just remove the plane from sight?
933 aip.setVisible(false);
934 operatingState = PARKED;
938 // Set the class variable nextTaxiNode to the next node in the path
939 // and update taxiPathPos, the class variable path iterator position
940 // TODO - maybe should return error codes to the calling function if we fail here
941 void FGAILocalTraffic::GetNextTaxiNode() {
942 //cout << "GetNextTaxiNode called " << endl;
943 //cout << "taxiPathPos = " << taxiPathPos << endl;
944 ground_network_path_iterator pathItr = path.begin() + taxiPathPos;
945 if(pathItr == path.end()) {
946 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - no more nodes in path\n");
948 if((*pathItr)->struct_type == NODE) {
949 //cout << "ITS A NODE" << endl;
950 //*pathItr = new node;
951 nextTaxiNode = (node*)*pathItr;
955 //cout << "ITS NOT A NODE" << endl;
956 //The first item in found must have been an arc
957 //Assume for now that it was straight
960 if(pathItr == path.end()) {
961 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - path ended with an arc\n");
962 } else if((*pathItr)->struct_type == NODE) {
963 nextTaxiNode = (node*)*pathItr;
966 //OOPS - two non-nodes in a row - that shouldn't happen ATM
967 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - two non-nodes in sequence\n");
973 // StartTaxi - set up the taxiing state - call only at the start of taxiing
974 void FGAILocalTraffic::StartTaxi() {
975 //cout << "StartTaxi called" << endl;
976 operatingState = TAXIING;
979 //Set the desired heading
980 //Assume we are aiming for first node on path
981 //Eventually we may need to consider the fact that we might start on a curved arc and
982 //not be able to head directly for the first node.
983 GetNextTaxiNode(); // sets the class variable nextTaxiNode to the next taxi node!
984 desiredTaxiHeading = GetHeadingFromTo(pos, nextTaxiNode->pos);
985 //cout << "First taxi heading is " << desiredTaxiHeading << endl;
988 // speed in knots, headings in degrees, radius in meters.
989 static double TaxiTurnTowardsHeading(double current_hdg, double desired_hdg, double speed, double radius, double dt) {
990 // wrap heading - this prevents a logic bug where the plane would just go round in circles!!
991 while(current_hdg < 0.0) {
992 current_hdg += 360.0;
994 while(current_hdg > 360.0) {
995 current_hdg -= 360.0;
997 if(fabs(current_hdg - desired_hdg) > 0.1) {
998 // Which is the quickest direction to turn onto heading?
999 if(desired_hdg > current_hdg) {
1000 if((desired_hdg - current_hdg) <= 180) {
1002 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1003 // TODO - check that increments are less than the delta that we check for the right direction
1004 // Probably need to reduce convergence speed as convergence is reached
1006 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1009 if((current_hdg - desired_hdg) <= 180) {
1011 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1012 // TODO - check that increments are less than the delta that we check for the right direction
1013 // Probably need to reduce convergence speed as convergence is reached
1015 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1019 return(current_hdg);
1022 void FGAILocalTraffic::Taxi(double dt) {
1023 //cout << "Taxi called" << endl;
1024 // Logic - if we are further away from next point than turn radius then head for it
1025 // If we have reached turning point then get next point and turn onto that heading
1026 // Look out for the finish!!
1028 //Point3D orthopos = ortho.ConvertToLocal(pos); // ortho position of the plane
1029 desiredTaxiHeading = GetHeadingFromTo(pos, nextTaxiNode->pos);
1031 bool lastNode = (taxiPathPos == path.size() ? true : false);
1033 //cout << "LAST NODE\n";
1036 // HACK ALERT! - for now we will taxi at constant speed for straights and turns
1038 // Remember that hdg is always equal to track when taxiing so we don't have to consider them both
1039 double dist_to_go = dclGetHorizontalSeparation(pos, nextTaxiNode->pos); // we may be able to do this more cheaply using orthopos
1040 //cout << "dist_to_go = " << dist_to_go << endl;
1041 if((nextTaxiNode->type == GATE) && (dist_to_go <= 0.1)) {
1042 // This might be more robust to outward paths starting with a gate if we check for either
1043 // last node or TD_INBOUND ?
1045 operatingState = PARKED;
1046 } else if(((dist_to_go > taxiTurnRadius) || (nextTaxiNode->type == GATE)) && (!liningUp)){
1047 // if the turn radius is r, and speed is s, then in a time dt we turn through
1048 // ((s.dt)/(PI.r)) x 180 degrees
1049 // or alternatively (s.dt)/r radians
1050 //cout << "hdg = " << hdg << " desired taxi heading = " << desiredTaxiHeading << '\n';
1051 hdg = TaxiTurnTowardsHeading(hdg, desiredTaxiHeading, nominalTaxiSpeed, taxiTurnRadius, dt);
1052 double vel = nominalTaxiSpeed;
1053 //cout << "vel = " << vel << endl;
1054 double dist = vel * 0.514444 * dt;
1055 //cout << "dist = " << dist << endl;
1057 //cout << "track = " << track << endl;
1059 pos = dclUpdatePosition(pos, track, slope, dist);
1060 //cout << "Updated position...\n";
1061 if(aip.getFGLocation()->get_cur_elev_m() > -9990) {
1062 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
1063 } // else don't change the elev until we get a valid ground elev again!
1064 } else if(lastNode) {
1065 if(taxiState == TD_LINING_UP) {
1066 if((!liningUp) && (dist_to_go <= taxiTurnRadius)) {
1070 hdg = TaxiTurnTowardsHeading(hdg, rwy.hdg, nominalTaxiSpeed, taxiTurnRadius, dt);
1071 double vel = nominalTaxiSpeed;
1072 //cout << "vel = " << vel << endl;
1073 double dist = vel * 0.514444 * dt;
1074 //cout << "dist = " << dist << endl;
1076 //cout << "track = " << track << endl;
1078 pos = dclUpdatePosition(pos, track, slope, dist);
1079 //cout << "Updated position...\n";
1080 if(aip.getFGLocation()->get_cur_elev_m() > -9990) {
1081 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
1082 } // else don't change the elev until we get a valid ground elev again!
1083 if(fabs(hdg - rwy.hdg) <= 1.0) {
1084 operatingState = IN_PATTERN;
1090 } else if(taxiState == TD_OUTBOUND) {
1091 // Pause awaiting further instructions
1092 // and for now assume we've reached the hold-short node
1093 holdingShort = true;
1094 } // else at the moment assume TD_INBOUND always ends in a gate in which case we can ignore it
1096 // Time to turn (we've already checked it's not the end we're heading for).
1097 // set the target node to be the next node which will prompt automatically turning onto
1098 // the right heading in the stuff above, with the usual provisos applied.
1100 // For now why not just recursively call this function?
1106 // Warning - ground elev determination is CPU intensive
1107 // Either this function or the logic of how often it is called
1108 // will almost certainly change.
1109 void FGAILocalTraffic::DoGroundElev() {
1111 // It would be nice if we could set the correct tile center here in order to get a correct
1112 // answer with one call to the function, but what I tried in the two commented-out lines
1113 // below only intermittently worked, and I haven't quite groked why yet.
1114 //SGBucket buck(pos.lon(), pos.lat());
1115 //aip.getFGLocation()->set_tile_center(Point3D(buck.get_center_lon(), buck.get_center_lat(), 0.0));
1117 double visibility_meters = fgGetDouble("/environment/visibility-m");
1118 //globals->get_tile_mgr()->prep_ssg_nodes( acmodel_location,
1119 globals->get_tile_mgr()->prep_ssg_nodes( aip.getFGLocation(), visibility_meters );
1120 globals->get_tile_mgr()->update( aip.getFGLocation(), visibility_meters, (aip.getFGLocation())->get_absolute_view_pos() );
1121 // save results of update in FGLocation for fdm...
1123 //if ( globals->get_scenery()->get_cur_elev() > -9990 ) {
1124 // acmodel_location->
1125 // set_cur_elev_m( globals->get_scenery()->get_cur_elev() );
1128 // The need for this here means that at least 2 consecutive passes are needed :-(
1129 aip.getFGLocation()->set_tile_center( globals->get_scenery()->get_next_center() );
1131 //cout << "Transform Elev is " << globals->get_scenery()->get_cur_elev() << '\n';
1132 aip.getFGLocation()->set_cur_elev_m(globals->get_scenery()->get_cur_elev());
1133 //return(globals->get_scenery()->get_cur_elev());