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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 /*==========================================================
26 Should get pattern direction from tower.
28 Need to continually monitor and adjust deviation from glideslope
29 during descent to avoid occasionally landing short or long.
31 ============================================================*/
37 #include <Airports/runways.hxx>
38 #include <Main/globals.hxx>
39 #include <Main/viewer.hxx>
40 #include <Scenery/scenery.hxx>
41 #include <Scenery/tilemgr.hxx>
42 #include <simgear/math/point3d.hxx>
43 #include <simgear/math/sg_geodesy.hxx>
44 #include <simgear/misc/sg_path.hxx>
51 #include "AILocalTraffic.hxx"
52 #include "ATCutils.hxx"
55 FGAILocalTraffic::FGAILocalTraffic() {
56 ATC = globals->get_ATC_mgr();
58 // TODO - unhardwire this
59 plane.type = GA_SINGLE;
65 //Hardwire initialisation for now - a lot of this should be read in from config eventually
67 best_rate_of_climb_speed = 70.0;
69 //nominal_climb_speed;
71 //nominal_circuit_speed;
74 nominal_descent_rate = 500.0;
75 nominal_final_speed = 65.0;
76 //nominal_approach_speed;
77 //stall_speed_landing_config;
78 nominalTaxiSpeed = 7.5;
80 wheelOffset = 1.45; // Warning - hardwired to the C172 - we need to read this in from file.
82 // Init the property nodes
83 wind_from_hdg = fgGetNode("/environment/wind-from-heading-deg", true);
84 wind_speed_knots = fgGetNode("/environment/wind-speed-kt", true);
87 taxiRequestPending = false;
88 taxiRequestCleared = false;
90 clearedToLineUp = false;
91 clearedToTakeOff = false;
92 _clearedToLand = false;
93 reportReadyForDeparture = false;
95 contactGround = false;
100 targetDescentRate = 0.0;
102 goAroundCalled = false;
114 FGAILocalTraffic::~FGAILocalTraffic() {
117 void FGAILocalTraffic::GetAirportDetails(const string& id) {
119 if(ATC->GetAirportATCDetails(airportID, &a)) {
120 if(a.tower_freq) { // Has a tower - TODO - check the opening hours!!!
121 tower = (FGTower*)ATC->GetATCPointer(airportID, TOWER);
123 // Something has gone wrong - abort or carry on with un-towered operation?
124 SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a tower pointer from tower control for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
130 ground = tower->GetGroundPtr();
132 // Something has gone wrong :-(
133 SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a ground pointer from tower control in FGAILocalTraffic::GetAirportDetails() :-(");
138 // TODO - Check CTAF, unicom etc
141 SG_LOG(SG_ATC, SG_ALERT, "Unable to find airport details in for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
144 // Get the airport elevation
145 aptElev = fgGetAirportElev(airportID.c_str());
146 //cout << "Airport elev in AILocalTraffic = " << aptElev << '\n';
147 // WARNING - we use this elev for the whole airport - some assumptions in the code
148 // might fall down with very slopey airports.
151 // Get details of the active runway
152 // It is assumed that by the time this is called the tower control and airport code will have been set up.
153 void FGAILocalTraffic::GetRwyDetails(const string& id) {
154 //cout << "GetRwyDetails called" << endl;
156 const FGAirport* apt = fgFindAirportID(id);
158 FGRunway* runway(apt->getActiveRunwayForUsage());
160 double hdg = runway->headingDeg();
161 double other_way = hdg - 180.0;
162 while(other_way <= 0.0) {
166 // move to the +l end/center of the runway
167 //cout << "Runway center is at " << runway._lon << ", " << runway._lat << '\n';
168 Point3D origin = Point3D(runway->longitude(), runway->latitude(), aptElev);
169 Point3D ref = origin;
170 double tshlon, tshlat, tshr;
171 double tolon, tolat, tor;
172 rwy.length = runway->lengthM();
173 rwy.width = runway->widthM();
174 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), other_way,
175 rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
176 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), hdg,
177 rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor );
178 // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user.
179 // now copy what we need out of runway into rwy
180 rwy.threshold_pos = Point3D(tshlon, tshlat, aptElev);
181 Point3D takeoff_end = Point3D(tolon, tolat, aptElev);
182 //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n';
183 //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
185 // Set the projection for the local area
186 //cout << "Initing ortho for airport " << id << '\n';
187 ortho.Init(rwy.threshold_pos, rwy.hdg);
188 rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos); // should come out as zero
189 rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
194 There are two possible scenarios during initialisation:
195 The first is that the user is flying towards the airport, and hence the traffic
196 could be initialised anywhere, as long as the AI planes are consistent with
198 The second is that the user has started the sim at or close to the airport, and
199 hence the traffic must be initialised with respect to the user as well as each other.
200 To a certain extent it's FGAIMgr that has to worry about this, but we need to provide
201 sufficient initialisation functionality within the plane classes to allow the manager
202 to initially position them where and how required.
204 bool FGAILocalTraffic::Init(const string& callsign, const string& ICAO, OperatingState initialState, PatternLeg initialLeg) {
205 //cout << "FGAILocalTraffic.Init(...) called" << endl;
208 plane.callsign = callsign;
210 if(initialState == EN_ROUTE) return(true);
212 // Get the ATC pointers and airport elev
213 GetAirportDetails(airportID);
215 // Get the active runway details (and copy them into rwy)
216 GetRwyDetails(airportID);
217 //cout << "Runway is " << rwy.rwyID << '\n';
219 // FIXME TODO - pattern direction is still hardwired
220 patternDirection = -1; // Left
221 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
222 if(rwy.rwyID.size() == 3) {
223 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
227 if((initialState == PARKED) || (initialState == TAXIING)) {
228 freq = (double)ground->get_freq() / 100.0;
230 freq = (double)tower->get_freq() / 100.0;
234 // TODO - find the proper freq if CTAF or unicom or after-hours.
237 //cout << "In Init(), initialState = " << initialState << endl;
238 operatingState = initialState;
240 switch(operatingState) {
242 tuned_station = ground;
243 ourGate = ground->GetGateNode();
244 if(ourGate == NULL) {
245 // Implies no available gates - what shall we do?
246 // For now just vanish the plane - possibly we can make this more elegant in the future
247 SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst attempting Init at " << airportID << '\n');
255 _pos.setelev(aptElev);
256 _hdg = ourGate->heading;
259 // Now we've set the position we can do the ground elev
260 elevInitGood = false;
266 //tuned_station = ground;
267 // FIXME - implement this case properly
268 // For now we'll assume that the plane should start at the hold short in this case
269 // and that we're working without ground network elements. Ie. an airport with no facility file.
271 tuned_station = tower;
273 tuned_station = NULL;
276 // Set a position and orientation in an approximate place for hold short.
277 //cout << "rwy.width = " << rwy.width << '\n';
278 orthopos = Point3D((rwy.width / 2.0 + 10.0) * -1.0, 0.0, 0.0);
279 // TODO - set the x pos to be +ve if a RH parallel rwy.
280 _pos = ortho.ConvertFromLocal(orthopos);
281 _pos.setelev(aptElev);
282 _hdg = rwy.hdg + 90.0;
283 // TODO - reset the heading if RH rwy.
288 elevInitGood = false;
294 responseCounter = 0.0;
295 contactTower = false;
298 clearedToLineUp = false;
299 changeFreqType = TOWER;
303 // For now we'll always start the in_pattern case on the threshold ready to take-off
304 // since we've got the implementation for this case already.
305 // TODO - implement proper generic in_pattern startup.
307 // 18/10/03 - adding the ability to start on downwind (mainly to speed testing of the go-around code!!)
309 //cout << "Starting in pattern...\n";
312 tuned_station = tower;
314 tuned_station = NULL;
317 circuitsToFly = 0; // ie just fly this circuit and then stop
320 if(initialLeg == DOWNWIND) {
321 _pos = ortho.ConvertFromLocal(Point3D(1000*patternDirection, 800, 0.0));
322 _pos.setelev(rwy.threshold_pos.elev() + 1000 * SG_FEET_TO_METER);
323 _hdg = rwy.hdg + 180.0;
325 elevInitGood = false;
327 SetTrack(rwy.hdg - (180 * patternDirection));
333 _aip.setVisible(true);
335 tower->RegisterAIPlane(plane, this, CIRCUIT, DOWNWIND);
339 // Default to initial position on threshold for now
340 _pos.setlat(rwy.threshold_pos.lat());
341 _pos.setlon(rwy.threshold_pos.lon());
342 _pos.setelev(rwy.threshold_pos.elev());
345 // Now we've set the position we can do the ground elev
346 // This might not always be necessary if we implement in-air start
347 elevInitGood = false;
360 operatingState = IN_PATTERN;
364 // This implies we're being init'd by AIGAVFRTraffic - simple return now
367 SG_LOG(SG_ATC, SG_ALERT, "Attempt to set unknown operating state in FGAILocalTraffic.Init(...)\n");
376 // Set up downwind state - this is designed to be called from derived classes who are already tuned to tower
377 void FGAILocalTraffic::DownwindEntry() {
378 circuitsToFly = 0; // ie just fly this circuit and then stop
380 operatingState = IN_PATTERN;
382 elevInitGood = false;
384 SetTrack(rwy.hdg - (180 * patternDirection));
392 void FGAILocalTraffic::StraightInEntry(bool des) {
393 //cout << "************ STRAIGHT-IN ********************\n";
394 circuitsToFly = 0; // ie just fly this circuit and then stop
396 operatingState = IN_PATTERN;
398 elevInitGood = false;
401 transmitted = true; // TODO - fix this hack.
402 // TODO - set up the next 5 properly for a descent!
411 // Return what type of landing we're doing on this circuit
412 LandingType FGAILocalTraffic::GetLandingOption() {
413 //cout << "circuitsToFly = " << circuitsToFly << '\n';
415 return(touchAndGo ? TOUCH_AND_GO : STOP_AND_GO);
422 // Commands to do something from higher level logic
423 void FGAILocalTraffic::FlyCircuits(int numCircuits, bool tag) {
424 //cout << "FlyCircuits called" << endl;
426 switch(operatingState) {
428 circuitsToFly += numCircuits;
432 // HACK - assume that we're taxiing out for now
433 circuitsToFly += numCircuits;
437 circuitsToFly = numCircuits; // Note that one too many circuits gets flown because we only test and decrement circuitsToFly after landing
438 // thus flying one too many circuits. TODO - Need to sort this out better!
446 // Run the internal calculations
447 void FGAILocalTraffic::Update(double dt) {
448 //cout << "U" << flush;
450 // we shouldn't really need this since there's a LOD of 10K on the whole plane anyway I think.
451 // At the moment though I need to to avoid DList overflows - the whole plane LOD obviously isn't getting picked up.
453 if(dclGetHorizontalSeparation(_pos, Point3D(fgGetDouble("/position/longitude-deg"), fgGetDouble("/position/latitude-deg"), 0.0)) > 8000) _aip.setVisible(false);
454 else _aip.setVisible(true);
456 _aip.setVisible(false);
459 //double responseTime = 10.0; // seconds - this should get more sophisticated at some point
460 responseCounter += dt;
461 if((contactTower) && (responseCounter >= 8.0)) {
462 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
463 string trns = "Tower ";
464 double f = globals->get_ATC_mgr()->GetFrequency(airportID, TOWER) / 100.0;
466 sprintf(buf, "%.2f", f);
469 trns += plane.callsign;
470 pending_transmission = trns;
471 ConditionalTransmit(30.0);
472 responseCounter = 0.0;
473 contactTower = false;
475 changeFreqType = TOWER;
478 if((contactGround) && (responseCounter >= 8.0)) {
479 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
480 string trns = "Ground ";
481 double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;
483 sprintf(buf, "%.2f", f);
487 pending_transmission = trns;
488 ConditionalTransmit(5.0);
489 responseCounter = 0.0;
490 contactGround = false;
492 changeFreqType = GROUND;
495 if((_taxiToGA) && (responseCounter >= 8.0)) {
496 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
497 string trns = "GA Parking, Thank you and Good Day";
498 //double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;
499 pending_transmission = trns;
500 ConditionalTransmit(5.0, 99);
503 tower->DeregisterAIPlane(plane.callsign);
505 // NOTE - we can't delete this instance yet since then the frequency won't get release when the message display finishes.
508 if((_removeSelf) && (responseCounter >= 8.0)) {
510 // MEGA HACK - check if we are at a simple airport or not first instead of simply hardwiring KEMT as the only non-simple airport.
511 // TODO FIXME TODO FIXME !!!!!!!
512 if(airportID != "KEMT") globals->get_AI_mgr()->ScheduleRemoval(plane.callsign);
515 if((changeFreq) && (responseCounter > 8.0)) {
516 switch(changeFreqType) {
519 SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to tower in FGAILocalTraffic, but tower is NULL!!!");
522 tuned_station = tower;
523 freq = (double)tower->get_freq() / 100.0;
525 // Contact the tower, even if only virtually
526 pending_transmission = plane.callsign;
527 pending_transmission += " at hold short for runway ";
528 pending_transmission += ConvertRwyNumToSpokenString(rwy.rwyID);
529 pending_transmission += " traffic pattern ";
531 pending_transmission += ConvertNumToSpokenDigits(circuitsToFly + 1);
532 pending_transmission += " circuits touch and go";
534 pending_transmission += " one circuit to full stop";
540 SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to ground in FGAILocalTraffic, but tower is NULL!!!");
544 SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to ground in FGAILocalTraffic, but ground is NULL!!!");
547 tower->DeregisterAIPlane(plane.callsign);
548 tuned_station = ground;
549 freq = (double)ground->get_freq() / 100.0;
551 // And to avoid compiler warnings...
552 case APPROACH: break;
555 case DEPARTURE: break;
561 //cout << "," << flush;
563 switch(operatingState) {
565 //cout << "In IN_PATTERN\n";
569 if(_ground_elevation_m > -9990.0) {
570 _pos.setelev(_ground_elevation_m + wheelOffset);
571 //cout << "TAKEOFF_ROLL, POS = " << pos.lon() << ", " << pos.lat() << ", " << pos.elev() << '\n';
573 _aip.setVisible(true);
574 //cout << "Making plane visible!\n";
579 FlyTrafficPattern(dt);
583 //cout << "In TAXIING\n";
584 //cout << "*" << flush;
587 if(_ground_elevation_m > -9990.0) {
588 _pos.setelev(_ground_elevation_m + wheelOffset);
590 _aip.setVisible(true);
592 //cout << "Making plane visible!\n";
597 //cout << "~" << flush;
598 if(!((holdingShort) && (!clearedToLineUp))) {
599 //cout << "|" << flush;
602 //cout << ";" << flush;
603 if((clearedToTakeOff) && (responseCounter >= 8.0)) {
604 // possible assumption that we're at the hold short here - may not always hold
605 // TODO - sort out the case where we're cleared to line-up first and then cleared to take-off on the rwy.
606 taxiState = TD_LINING_UP;
607 //cout << "A" << endl;
608 path = ground->GetPath(holdShortNode, rwy.rwyID);
609 //cout << "B" << endl;
610 if(!path.size()) { // Assume no facility file so we'll just taxi to a point on the runway near the threshold
611 //cout << "C" << endl;
613 np->struct_type = NODE;
614 np->pos = ortho.ConvertFromLocal(Point3D(0.0, 10.0, 0.0));
617 //cout << "D" << endl;
620 cout << "path returned was:" << endl;
621 for(unsigned int i=0; i<path.size(); ++i) {
622 switch(path[i]->struct_type) {
624 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
632 clearedToTakeOff = false; // We *are* still cleared - this simply stops the response recurring!!
633 holdingShort = false;
634 string trns = "Cleared for take-off ";
635 trns += plane.callsign;
636 pending_transmission = trns;
640 //cout << "^" << flush;
644 //cout << "In PARKED\n";
647 if(_ground_elevation_m > -9990.0) {
648 _pos.setelev(_ground_elevation_m + wheelOffset);
650 _aip.setVisible(true);
652 //cout << "Making plane visible!\n";
658 if((taxiRequestPending) && (taxiRequestCleared)) {
659 //cout << "&" << flush;
660 // Get the active runway details (in case they've changed since init)
661 GetRwyDetails(airportID);
663 // Get the takeoff node for the active runway, get a path to it and start taxiing
664 path = ground->GetPathToHoldShort(ourGate, rwy.rwyID);
665 if(path.size() < 2) {
666 // something has gone wrong
667 SG_LOG(SG_ATC, SG_ALERT, "Invalid path from gate to theshold in FGAILocalTraffic::FlyCircuits\n");
671 cout << "path returned was:\n";
672 for(unsigned int i=0; i<path.size(); ++i) {
673 switch(path[i]->struct_type) {
675 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
683 path.erase(path.begin()); // pop the gate - we're here already!
684 taxiState = TD_OUTBOUND;
685 taxiRequestPending = false;
686 holdShortNode = (node*)(*(path.begin() + path.size()));
688 } else if(!taxiRequestPending) {
689 //cout << "(" << flush;
690 // Do some communication
691 // airport name + tower + airplane callsign + location + request taxi for + operation type + ?
694 trns += tower->get_name();
698 // TODO - get the airport name somehow if uncontrolled
700 trns += plane.callsign;
701 trns += " on apron parking request taxi for traffic pattern";
702 //cout << "trns = " << trns << endl;
703 pending_transmission = trns;
705 taxiRequestCleared = false;
706 taxiRequestPending = true;
710 //cout << "!" << flush;
712 // Maybe the below should be set when we get to the threshold and prepare for TO?
713 // FIXME TODO - pattern direction is still hardwired
714 patternDirection = -1; // Left
715 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
716 if(rwy.rwyID.size() == 3) {
717 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
721 //cout << ")" << flush;
726 //cout << "I " << flush;
728 //cout << "Update _pos = " << _pos << ", vis = " << _aip.getVisible() << '\n';
730 // Convienience output for AI debugging using the property logger
731 //fgSetDouble("/AI/Local1/ortho-x", (ortho.ConvertToLocal(_pos)).x());
732 //fgSetDouble("/AI/Local1/ortho-y", (ortho.ConvertToLocal(_pos)).y());
733 //fgSetDouble("/AI/Local1/elev", _pos.elev() * SG_METER_TO_FEET);
735 // And finally, call parent.
736 FGAIPlane::Update(dt);
739 void FGAILocalTraffic::RegisterTransmission(int code) {
741 case 1: // taxi request cleared
742 taxiRequestCleared = true;
743 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to taxi...");
745 case 2: // contact tower
748 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact tower...");
750 case 3: // Cleared to line up
752 clearedToLineUp = true;
753 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to line-up...");
755 case 4: // cleared to take-off
757 clearedToTakeOff = true;
758 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to take-off...");
760 case 5: // contact ground
762 contactGround = true;
763 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact ground...");
765 // case 6 is a temporary mega-hack for controlled airports without separate ground control
766 case 6: // taxi to the GA parking
769 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to taxi to the GA parking...");
771 case 7: // Cleared to land (also implies cleared for the option
772 _clearedToLand = true;
773 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to land...");
775 case 13: // Go around!
778 _clearedToLand = false;
779 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to go-around!!");
786 // Fly a traffic pattern
787 // FIXME - far too much of the mechanics of turning, rolling, accellerating, descending etc is in here.
788 // Move it out to FGAIPlane and have FlyTrafficPattern just specify what to do, not the implementation.
789 void FGAILocalTraffic::FlyTrafficPattern(double dt) {
790 // Need to differentiate between in-air (IAS governed) and on-ground (vel governed)
791 // Take-off is an interesting case - we are on the ground but takeoff speed is IAS governed.
794 // Wind has two effects - a mechanical one in that IAS translates to a different vel, and the hdg != track,
795 // but also a piloting effect, in that the AI must be able to descend at a different rate in order to hit the threshold.
797 //cout << "dt = " << dt << '\n';
799 // ack - I can't remember how long a rate 1 turn is meant to take.
800 double turn_time = 60.0; // seconds - TODO - check this guess
801 double turn_circumference;
803 Point3D orthopos = ortho.ConvertToLocal(_pos); // ortho position of the plane
804 //cout << "runway elev = " << rwy.threshold_pos.elev() << ' ' << rwy.threshold_pos.elev() * SG_METER_TO_FEET << '\n';
805 //cout << "elev = " << _pos.elev() << ' ' << _pos.elev() * SG_METER_TO_FEET << '\n';
807 // HACK FOR TESTING - REMOVE
808 //cout << "Calling ExitRunway..." << endl;
809 //ExitRunway(orthopos);
814 double wind_from = wind_from_hdg->getDoubleValue();
815 double wind_speed = wind_speed_knots->getDoubleValue();
827 if(_ground_elevation_m > -9990.0) {
828 _pos.setelev(_ground_elevation_m + wheelOffset);
830 IAS = vel + (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
833 SetTrack(rwy.hdg); // Hands over control of turning to AIPlane
835 IAS = best_rate_of_climb_speed;
837 slope = 6.0; // Reduced it slightly since it's climbing a lot steeper than I can in the JSBSim C172.
842 // Turn to crosswind if above 700ft AND if other traffic allows
843 // (decided in FGTower and accessed through GetCrosswindConstraint(...)).
844 // According to AIM, traffic should climb to within 300ft of pattern altitude before commencing crosswind turn.
845 // TODO - At hot 'n high airports this may be 500ft AGL though - need to make this a variable.
846 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 700) {
848 if(tower->GetCrosswindConstraint(cc)) {
849 if(orthopos.y() > cc) {
850 //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
853 } else if(orthopos.y() > 1500.0) { // Added this constraint as a hack to prevent turning too early when going around.
854 // TODO - We should be doing it as a distance from takeoff end, not theshold end though.
855 //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
859 // Need to check for levelling off in case we can't turn crosswind as soon
860 // as we would like due to other traffic.
861 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
864 IAS = 80.0; // FIXME - use smooth transistion to new speed and attitude.
866 if(goAround && !goAroundCalled) {
867 if(responseCounter > 5.5) {
868 pending_transmission = plane.callsign;
869 pending_transmission += " going around";
871 goAroundCalled = true;
876 SetTrack(rwy.hdg + (90.0 * patternDirection));
877 if((track < (rwy.hdg - 89.0)) || (track > (rwy.hdg + 89.0))) {
883 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
886 IAS = 80.0; // FIXME - use smooth transistion to new speed
888 // turn 1000m out for now, taking other traffic into accout
889 if(fabs(orthopos.x()) > 900) {
891 if(tower->GetDownwindConstraint(dd)) {
892 if(fabs(orthopos.x()) > fabs(dd)) {
893 //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n';
897 //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n';
903 SetTrack(rwy.hdg - (180 * patternDirection));
904 // just in case we didn't make height on crosswind
905 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
908 IAS = 80.0; // FIXME - use smooth transistion to new speed
910 if((track < (rwy.hdg - 179.0)) || (track > (rwy.hdg + 179.0))) {
916 // just in case we didn't make height on crosswind
917 if(((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 995) && ((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET < 1015)) {
920 IAS = 90.0; // FIXME - use smooth transistion to new speed
922 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET >= 1015) {
925 IAS = 90.0; // FIXME - use smooth transistion to new speed
927 if((orthopos.y() < 0) && (!transmitted)) {
928 TransmitPatternPositionReport();
931 if((orthopos.y() < -100) && (!descending)) {
932 //cout << "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDdddd\n";
933 // Maybe we should think about when to start descending.
934 // For now we're assuming that we aim to follow the same glidepath regardless of wind.
937 CalculateSoD((tower->GetBaseConstraint(d1) ? d1 : -1000.0), (tower->GetDownwindConstraint(d2) ? d2 : 1000.0 * patternDirection), (patternDirection ? true : false));
938 if(SoD.leg == DOWNWIND) {
939 descending = (orthopos.y() < SoD.y ? true : false);
944 slope = -5.5; // FIXME - calculate to descent at 500fpm and hit the desired point on the runway (taking wind into account as well!!)
949 // Try and arrange to turn nicely onto base
950 turn_circumference = IAS * 0.514444 * turn_time;
951 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
952 //We'll leave it as a hack with IAS for now but it needs revisiting.
953 turn_radius = turn_circumference / (2.0 * DCL_PI);
954 if(orthopos.y() < -1000.0 + turn_radius) {
955 //if(orthopos.y() < -980) {
957 if(tower->GetBaseConstraint(bb)) {
958 if(fabs(orthopos.y()) > fabs(bb)) {
959 //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n';
965 //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n';
973 SetTrack(rwy.hdg - (90 * patternDirection));
974 if(fabs(rwy.hdg - track) < 91.0) {
980 // Base report should only be transmitted at uncontrolled airport - not towered.
981 if(!_controlled) TransmitPatternPositionReport();
987 // Make downwind leg position artifically large to avoid any chance of SoD being returned as
988 // on downwind when we are already on base.
989 CalculateSoD((tower->GetBaseConstraint(d1) ? d1 : -1000.0), (10000.0 * patternDirection), (patternDirection ? true : false));
990 if(SoD.leg == BASE) {
991 descending = (fabs(orthopos.y()) < fabs(SoD.y) ? true : false);
996 slope = -5.5; // FIXME - calculate to descent at 500fpm and hit the threshold (taking wind into account as well!!)
1001 // Try and arrange to turn nicely onto final
1002 turn_circumference = IAS * 0.514444 * turn_time;
1003 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
1004 //We'll leave it as a hack with IAS for now but it needs revisiting.
1005 turn_radius = turn_circumference / (2.0 * DCL_PI);
1006 if(fabs(orthopos.x()) < (turn_radius + 50)) {
1008 transmitted = false;
1014 if(fabs(track - rwy.hdg) < 0.6) {
1016 vel = nominal_final_speed;
1020 if(goAround && responseCounter > 2.0) {
1023 IAS = best_rate_of_climb_speed;
1024 slope = 5.0; // A bit less steep than the initial climbout.
1026 goAroundCalled = false;
1032 if((!_controlled) || (!_clearedToLand)) TransmitPatternPositionReport();
1036 // Make base leg position artifically large to avoid any chance of SoD being returned as
1037 // on base or downwind when we are already on final.
1038 CalculateSoD(-10000.0, (1000.0 * patternDirection), (patternDirection ? true : false));
1039 if(SoD.leg == FINAL) {
1040 descending = (fabs(orthopos.y()) < fabs(SoD.y) ? true : false);
1045 if(orthopos.y() < -50.0) {
1046 double thesh_offset = 30.0;
1047 slope = atan((_pos.elev() - fgGetAirportElev(airportID)) / (orthopos.y() - thesh_offset)) * DCL_RADIANS_TO_DEGREES;
1048 //cout << "slope = " << slope << ", elev = " << _pos.elev() << ", apt_elev = " << fgGetAirportElev(airportID) << ", op.y = " << orthopos.y() << '\n';
1049 if(slope < -10.0) slope = -10.0;
1050 _savedSlope = slope;
1054 if(_pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) {
1055 if(_ground_elevation_m > -9990.0) {
1056 if(_pos.elev() < (_ground_elevation_m + wheelOffset + 1.0)) {
1060 } else if(_pos.elev() < (_ground_elevation_m + wheelOffset + 5.0)) {
1065 slope = _savedSlope;
1070 // Elev not determined
1071 slope = _savedSlope;
1076 slope = _savedSlope;
1082 // Try and track the extended centreline
1083 SetTrack(rwy.hdg - (0.2 * orthopos.x()));
1084 //cout << "orthopos.x() = " << orthopos.x() << " hdg = " << hdg << '\n';
1085 if(_pos.elev() < (rwy.threshold_pos.elev()+20.0+wheelOffset)) {
1086 DoGroundElev(); // Need to call it here expicitly on final since it's only called
1087 // for us in update(...) when the inAir flag is false.
1089 if(_pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) {
1092 if(_ground_elevation_m > -9990.0) {
1093 if((_ground_elevation_m + wheelOffset) > _pos.elev()) {
1099 ClearTrack(); // Take over explicit track handling since AIPlane currently always banks when changing course
1101 } // else need a fallback position based on arpt elev in case ground elev determination fails?
1109 if(_ground_elevation_m > -9990.0) {
1110 _pos.setelev(_ground_elevation_m + wheelOffset);
1115 // FIXME - differentiate between touch and go and full stops
1117 //cout << "Vel <= 15.0, circuitsToFly = " << circuitsToFly << endl;
1118 if(circuitsToFly <= 0) {
1119 //cout << "Calling ExitRunway..." << endl;
1120 ExitRunway(orthopos);
1123 //cout << "Taking off again..." << endl;
1134 // FIXME - at the moment this is a bit screwy
1135 // The velocity correction is applied based on the relative headings.
1136 // Then the heading is changed based on the velocity.
1137 // Which comes first, the chicken or the egg?
1138 // Does it really matter?
1140 // Apply wind to ground-relative velocity if in the air
1141 vel = IAS - (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
1142 //crab = f(track, wind, vel);
1143 // The vector we need to fly is our desired vector minus the wind vector
1144 // TODO - we probably ought to use plib's built in vector types and operations for this
1145 // ie. There's almost *certainly* a better way to do this!
1146 double gxx = vel * sin(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity x component wrt ground
1147 double gyy = vel * cos(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity y component wrt ground
1148 double wxx = wind_speed * sin((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS); // Wind velocity x component
1149 double wyy = wind_speed * cos((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS); // Wind velocity y component
1150 double axx = gxx - wxx; // Plane in-air velocity x component
1151 double ayy = gyy - wyy; // Plane in-air velocity y component
1152 // Now we want the angle between gxx and axx (which is the crab)
1153 double maga = sqrt(axx*axx + ayy*ayy);
1154 double magg = sqrt(gxx*gxx + gyy*gyy);
1155 crab = acos((axx*gxx + ayy*gyy) / (maga * magg));
1156 // At this point this works except we're getting the modulus of the angle
1157 //cout << "crab = " << crab << '\n';
1159 // Make sure both headings are in the 0->360 circle in order to get sane differences
1160 dclBoundHeading(wind_from);
1161 dclBoundHeading(track);
1162 if(track > wind_from) {
1163 if((track - wind_from) <= 180) {
1167 if((wind_from - track) >= 180) {
1171 } else { // on the ground - crab dosen't apply
1175 //cout << "X " << orthopos.x() << " Y " << orthopos.y() << " SLOPE " << slope << " elev " << _pos.elev() * SG_METER_TO_FEET << '\n';
1177 _hdg = track + crab;
1178 dist = vel * 0.514444 * dt;
1179 _pos = dclUpdatePosition(_pos, track, slope, dist);
1182 // Pattern direction is true for right, false for left
1183 void FGAILocalTraffic::CalculateSoD(double base_leg_pos, double downwind_leg_pos, bool pattern_direction) {
1184 // For now we'll ignore wind and hardwire the glide angle.
1185 double ga = 5.5; //degrees
1186 double pa = 1000.0 * SG_FEET_TO_METER; // pattern altitude in meters
1187 // FIXME - get glideslope angle and pattern altitude agl from airport details if available
1189 // For convienience, we'll have +ve versions of the input distances
1190 double blp = fabs(base_leg_pos);
1191 double dlp = fabs(downwind_leg_pos);
1193 //double turn_allowance = 150.0; // Approximate distance in meters that a 90deg corner is shortened by turned in a light plane.
1195 double stod = pa / tan(ga * DCL_DEGREES_TO_RADIANS); // distance in meters from touchdown point to start descent
1196 //cout << "Descent to start = " << stod << " meters out\n";
1197 if(stod < blp) { // Start descending on final
1199 SoD.y = stod * -1.0;
1201 } else if(stod < (blp + dlp)) { // Start descending on base leg
1204 SoD.x = (pattern_direction ? (stod - dlp) : (stod - dlp) * -1.0);
1205 } else { // Start descending on downwind leg
1207 SoD.x = (pattern_direction ? dlp : dlp * -1.0);
1208 SoD.y = (blp - (stod - (blp + dlp))) * -1.0;
1212 void FGAILocalTraffic::TransmitPatternPositionReport(void) {
1213 // airport name + "traffic" + airplane callsign + pattern direction + pattern leg + rwy + ?
1217 trns += tower->get_name();
1218 trns += " Traffic ";
1219 trns += plane.callsign;
1220 if(patternDirection == 1) {
1226 // We could probably get rid of this whole switch statement and just pass a string containing the leg from the FlyPattern function.
1227 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?
1229 // Fall through to CROSSWIND
1230 case CROSSWIND: // I don't think this case will be used here but it can't hurt to leave it in
1231 trns += "crosswind ";
1234 // Fall through to DOWNWIND
1236 trns += "downwind ";
1240 // Fall through to BASE
1245 // Fall through to FINAL
1246 case FINAL: // maybe this should include long/short final if appropriate?
1250 default: // Hopefully this won't be used
1254 trns += ConvertRwyNumToSpokenString(rwy.rwyID);
1258 // And add the airport name again
1259 trns += tower->get_name();
1261 pending_transmission = trns;
1262 ConditionalTransmit(60.0, code); // Assume a report of this leg will be invalid if we can't transmit within a minute.
1266 // TODO - Really should enumerate these coded values.
1267 void FGAILocalTraffic::ProcessCallback(int code) {
1268 // 1 - Request Departure from ground
1269 // 2 - Report at hold short
1270 // 3 - Report runway vacated
1271 // 10 - report crosswind
1272 // 11 - report downwind
1274 // 13 - report final
1276 ground->RequestDeparture(plane, this);
1277 } else if(code == 2) {
1278 tower->ContactAtHoldShort(plane, this, CIRCUIT);
1279 } else if(code == 3) {
1280 tower->ReportRunwayVacated(plane.callsign);
1281 } else if(code == 11) {
1282 tower->ReportDownwind(plane.callsign);
1283 } else if(code == 13) {
1284 tower->ReportFinal(plane.callsign);
1285 } else if(code == 99) { // Flag this instance for deletion
1286 responseCounter = 0;
1288 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " delete instance callback called.");
1292 void FGAILocalTraffic::ExitRunway(const Point3D& orthopos) {
1293 //cout << "In ExitRunway" << endl;
1294 //cout << "Runway ID is " << rwy.ID << endl;
1296 _clearedToLand = false;
1298 node_array_type exitNodes = ground->GetExits(rwy.rwyID); //I suppose we ought to have some fallback for rwy with no defined exits?
1300 cout << "Node ID's of exits are ";
1301 for(unsigned int i=0; i<exitNodes.size(); ++i) {
1302 cout << exitNodes[i]->nodeID << ' ';
1306 if(exitNodes.size()) {
1307 //Find the next exit from orthopos.y
1309 double dist = 100000; //ie. longer than any runway in existance
1310 double backdist = 100000;
1311 node_array_iterator nItr = exitNodes.begin();
1312 node* rwyExit = *(exitNodes.begin());
1313 //int gateID; //This might want to be more persistant at some point
1314 while(nItr != exitNodes.end()) {
1315 d = ortho.ConvertToLocal((*nItr)->pos).y() - ortho.ConvertToLocal(_pos).y(); //FIXME - consider making orthopos a class variable
1322 if(fabs(d) < backdist) {
1324 //TODO - need some logic here that if we don't get a forward exit we turn round and store the backwards one
1329 ourGate = ground->GetGateNode();
1330 if(ourGate == NULL) {
1331 // Implies no available gates - what shall we do?
1332 // For now just vanish the plane - possibly we can make this more elegant in the future
1333 SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst landing at " << airportID << '\n');
1334 //_aip.setVisible(false);
1335 //cout << "Setting visible false\n";
1336 operatingState = PARKED;
1339 path = ground->GetPath(rwyExit, ourGate);
1341 cout << "path returned was:" << endl;
1342 for(unsigned int i=0; i<path.size(); ++i) {
1343 switch(path[i]->struct_type) {
1345 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
1353 taxiState = TD_INBOUND;
1356 // Something must have gone wrong with the ground network file - or there is only a rwy here and no exits defined
1357 SG_LOG(SG_ATC, SG_INFO, "No exits found by FGAILocalTraffic from runway " << rwy.rwyID << " at " << airportID << '\n');
1358 //if(airportID == "KRHV") cout << "No exits found by " << plane.callsign << " from runway " << rwy.rwyID << " at " << airportID << '\n';
1359 // What shall we do - just remove the plane from sight?
1360 _aip.setVisible(false);
1362 //cout << "Setting visible false\n";
1363 //tower->ReportRunwayVacated(plane.callsign);
1364 string trns = "Clear of the runway ";
1365 trns += plane.callsign;
1366 pending_transmission = trns;
1368 operatingState = PARKED;
1372 // Set the class variable nextTaxiNode to the next node in the path
1373 // and update taxiPathPos, the class variable path iterator position
1374 // TODO - maybe should return error codes to the calling function if we fail here
1375 void FGAILocalTraffic::GetNextTaxiNode() {
1376 //cout << "GetNextTaxiNode called " << endl;
1377 //cout << "taxiPathPos = " << taxiPathPos << endl;
1378 ground_network_path_iterator pathItr = path.begin() + taxiPathPos;
1379 if(pathItr == path.end()) {
1380 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - no more nodes in path\n");
1382 if((*pathItr)->struct_type == NODE) {
1383 //cout << "ITS A NODE" << endl;
1384 //*pathItr = new node;
1385 nextTaxiNode = (node*)*pathItr;
1389 //cout << "ITS NOT A NODE" << endl;
1390 //The first item in found must have been an arc
1391 //Assume for now that it was straight
1394 if(pathItr == path.end()) {
1395 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - path ended with an arc\n");
1396 } else if((*pathItr)->struct_type == NODE) {
1397 nextTaxiNode = (node*)*pathItr;
1400 //OOPS - two non-nodes in a row - that shouldn't happen ATM
1401 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - two non-nodes in sequence\n");
1407 // StartTaxi - set up the taxiing state - call only at the start of taxiing
1408 void FGAILocalTraffic::StartTaxi() {
1409 //cout << "StartTaxi called" << endl;
1410 operatingState = TAXIING;
1414 //Set the desired heading
1415 //Assume we are aiming for first node on path
1416 //Eventually we may need to consider the fact that we might start on a curved arc and
1417 //not be able to head directly for the first node.
1418 GetNextTaxiNode(); // sets the class variable nextTaxiNode to the next taxi node!
1419 desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos);
1420 //cout << "First taxi heading is " << desiredTaxiHeading << endl;
1423 // speed in knots, headings in degrees, radius in meters.
1424 static double TaxiTurnTowardsHeading(double current_hdg, double desired_hdg, double speed, double radius, double dt) {
1425 // wrap heading - this prevents a logic bug where the plane would just go round in circles!!
1426 while(current_hdg < 0.0) {
1427 current_hdg += 360.0;
1429 while(current_hdg > 360.0) {
1430 current_hdg -= 360.0;
1432 if(fabs(current_hdg - desired_hdg) > 0.1) {
1433 // Which is the quickest direction to turn onto heading?
1434 if(desired_hdg > current_hdg) {
1435 if((desired_hdg - current_hdg) <= 180) {
1437 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1438 // TODO - check that increments are less than the delta that we check for the right direction
1439 // Probably need to reduce convergence speed as convergence is reached
1441 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1444 if((current_hdg - desired_hdg) <= 180) {
1446 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1447 // TODO - check that increments are less than the delta that we check for the right direction
1448 // Probably need to reduce convergence speed as convergence is reached
1450 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1454 return(current_hdg);
1457 void FGAILocalTraffic::Taxi(double dt) {
1458 //cout << "Taxi called" << endl;
1459 // Logic - if we are further away from next point than turn radius then head for it
1460 // If we have reached turning point then get next point and turn onto that heading
1461 // Look out for the finish!!
1463 //Point3D orthopos = ortho.ConvertToLocal(pos); // ortho position of the plane
1464 desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos);
1466 bool lastNode = (taxiPathPos == path.size() ? true : false);
1468 //cout << "LAST NODE\n";
1471 // HACK ALERT! - for now we will taxi at constant speed for straights and turns
1473 // Remember that hdg is always equal to track when taxiing so we don't have to consider them both
1474 double dist_to_go = dclGetHorizontalSeparation(_pos, nextTaxiNode->pos); // we may be able to do this more cheaply using orthopos
1475 //cout << "dist_to_go = " << dist_to_go << endl;
1476 if((nextTaxiNode->type == GATE) && (dist_to_go <= 0.1)) {
1477 // This might be more robust to outward paths starting with a gate if we check for either
1478 // last node or TD_INBOUND ?
1480 operatingState = PARKED;
1481 } else if(((dist_to_go > taxiTurnRadius) || (nextTaxiNode->type == GATE)) && (!liningUp)){
1482 // if the turn radius is r, and speed is s, then in a time dt we turn through
1483 // ((s.dt)/(PI.r)) x 180 degrees
1484 // or alternatively (s.dt)/r radians
1485 //cout << "hdg = " << hdg << " desired taxi heading = " << desiredTaxiHeading << '\n';
1486 _hdg = TaxiTurnTowardsHeading(_hdg, desiredTaxiHeading, nominalTaxiSpeed, taxiTurnRadius, dt);
1487 double vel = nominalTaxiSpeed;
1488 //cout << "vel = " << vel << endl;
1489 double dist = vel * 0.514444 * dt;
1490 //cout << "dist = " << dist << endl;
1491 double track = _hdg;
1492 //cout << "track = " << track << endl;
1494 _pos = dclUpdatePosition(_pos, track, slope, dist);
1495 //cout << "Updated position...\n";
1496 if(_ground_elevation_m > -9990) {
1497 _pos.setelev(_ground_elevation_m + wheelOffset);
1498 } // else don't change the elev until we get a valid ground elev again!
1499 } else if(lastNode) {
1500 if(taxiState == TD_LINING_UP) {
1501 if((!liningUp) && (dist_to_go <= taxiTurnRadius)) {
1505 _hdg = TaxiTurnTowardsHeading(_hdg, rwy.hdg, nominalTaxiSpeed, taxiTurnRadius, dt);
1506 double vel = nominalTaxiSpeed;
1507 //cout << "vel = " << vel << endl;
1508 double dist = vel * 0.514444 * dt;
1509 //cout << "dist = " << dist << endl;
1510 double track = _hdg;
1511 //cout << "track = " << track << endl;
1513 _pos = dclUpdatePosition(_pos, track, slope, dist);
1514 //cout << "Updated position...\n";
1515 if(_ground_elevation_m > -9990) {
1516 _pos.setelev(_ground_elevation_m + wheelOffset);
1517 } // else don't change the elev until we get a valid ground elev again!
1518 if(fabs(_hdg - rwy.hdg) <= 1.0) {
1519 operatingState = IN_PATTERN;
1525 } else if(taxiState == TD_OUTBOUND) {
1526 // Pause awaiting further instructions
1527 // and for now assume we've reached the hold-short node
1528 holdingShort = true;
1529 } // else at the moment assume TD_INBOUND always ends in a gate in which case we can ignore it
1531 // Time to turn (we've already checked it's not the end we're heading for).
1532 // set the target node to be the next node which will prompt automatically turning onto
1533 // the right heading in the stuff above, with the usual provisos applied.
1535 // For now why not just recursively call this function?
1541 // Warning - ground elev determination is CPU intensive
1542 // Either this function or the logic of how often it is called
1543 // will almost certainly change.
1544 void FGAILocalTraffic::DoGroundElev() {
1545 // Only do the proper hitlist stuff if we are within visible range of the viewer.
1546 double visibility_meters = fgGetDouble("/environment/visibility-m");
1547 FGViewer* vw = globals->get_current_view();
1548 if(dclGetHorizontalSeparation(_pos, Point3D(vw->getLongitude_deg(), vw->getLatitude_deg(), 0.0)) > visibility_meters) {
1549 _ground_elevation_m = aptElev;
1553 // FIXME: make shure the pos.lat/pos.lon values are in degrees ...
1554 double range = 500.0;
1555 if (!globals->get_tile_mgr()->scenery_available(_aip.getPosition(), range)) {
1556 // Try to shedule tiles for that position.
1557 globals->get_tile_mgr()->update( _aip.getPosition(), range );
1560 // FIXME: make shure the pos.lat/pos.lon values are in degrees ...
1562 if (globals->get_scenery()->get_elevation_m(SGGeod::fromGeodM(_aip.getPosition(), 20000), alt, 0))
1563 _ground_elevation_m = alt;