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.
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 <simgear/scene/model/location.hxx>
39 #include <Airports/runways.hxx>
40 #include <Main/globals.hxx>
41 #include <Scenery/scenery.hxx>
42 #include <Scenery/tilemgr.hxx>
43 #include <simgear/math/point3d.hxx>
44 #include <simgear/math/sg_geodesy.hxx>
45 #include <simgear/misc/sg_path.hxx>
52 #include "AILocalTraffic.hxx"
53 #include "ATCutils.hxx"
56 FGAILocalTraffic::FGAILocalTraffic() {
57 /*ssgBranch *model = sgLoad3DModel( globals->get_fg_root(),
60 globals->get_sim_time_sec() );
68 ATC = globals->get_ATC_mgr();
70 // TODO - unhardwire this
71 plane.type = GA_SINGLE;
77 //Hardwire initialisation for now - a lot of this should be read in from config eventually
79 best_rate_of_climb_speed = 70.0;
81 //nominal_climb_speed;
83 //nominal_circuit_speed;
86 nominal_descent_rate = 500.0;
87 nominal_final_speed = 65.0;
88 //nominal_approach_speed;
89 //stall_speed_landing_config;
90 nominalTaxiSpeed = 7.5;
92 wheelOffset = 1.45; // Warning - hardwired to the C172 - we need to read this in from file.
94 // Init the property nodes
95 wind_from_hdg = fgGetNode("/environment/wind-from-heading-deg", true);
96 wind_speed_knots = fgGetNode("/environment/wind-speed-kt", true);
99 taxiRequestPending = false;
100 taxiRequestCleared = false;
101 holdingShort = false;
102 clearedToLineUp = false;
103 clearedToTakeOff = false;
104 _clearedToLand = false;
105 reportReadyForDeparture = false;
106 contactTower = false;
107 contactGround = false;
111 targetDescentRate = 0.0;
113 goAroundCalled = false;
123 FGAILocalTraffic::~FGAILocalTraffic() {
127 void FGAILocalTraffic::GetAirportDetails(string id) {
129 if(ATC->GetAirportATCDetails(airportID, &a)) {
130 if(a.tower_freq) { // Has a tower - TODO - check the opening hours!!!
131 tower = (FGTower*)ATC->GetATCPointer(airportID, TOWER); // Maybe need some error checking here
133 // Something has gone wrong - abort or carry on with un-towered operation?
134 SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a tower pointer from tower control for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
140 ground = tower->GetGroundPtr();
142 // Something has gone wrong :-(
143 SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a ground pointer from tower control in FGAILocalTraffic::GetAirportDetails() :-(");
148 // TODO - Check CTAF, unicom etc
151 SG_LOG(SG_ATC, SG_ALERT, "Unable to find airport details in for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
154 // Get the airport elevation
155 aptElev = dclGetAirportElev(airportID.c_str());
156 //cout << "Airport elev in AILocalTraffic = " << aptElev << '\n';
157 // WARNING - we use this elev for the whole airport - some assumptions in the code
158 // might fall down with very slopey airports.
161 // Get details of the active runway
162 // It is assumed that by the time this is called the tower control and airport code will have been set up.
163 void FGAILocalTraffic::GetRwyDetails(string id) {
164 //cout << "GetRwyDetails called" << endl;
166 rwy.rwyID = tower->GetActiveRunway();
167 //cout << "id = " << id << '\n';
168 //cout << "Returned id is " << tower->get_ident() << '\n';
169 //cout << "Returned name is " << tower->get_name() << '\n';
170 //cout << "rwy.rwyID = " << rwy.rwyID << '\n';
172 // Now we need to get the threshold position and rwy heading
175 bool rwyGood = globals->get_runways()->search(id, rwy.rwyID, &runway);
177 double hdg = runway.heading;
178 double other_way = hdg - 180.0;
179 while(other_way <= 0.0) {
183 // move to the +l end/center of the runway
184 //cout << "Runway center is at " << runway.lon << ", " << runway.lat << '\n';
185 Point3D origin = Point3D(runway.lon, runway.lat, aptElev);
186 Point3D ref = origin;
187 double tshlon, tshlat, tshr;
188 double tolon, tolat, tor;
189 rwy.length = runway.length * SG_FEET_TO_METER;
190 rwy.width = runway.width * SG_FEET_TO_METER;
191 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), other_way,
192 rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
193 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), hdg,
194 rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor );
195 // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user.
196 // now copy what we need out of runway into rwy
197 rwy.threshold_pos = Point3D(tshlon, tshlat, aptElev);
198 Point3D takeoff_end = Point3D(tolon, tolat, aptElev);
199 //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n';
200 //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
202 // Set the projection for the local area
203 //cout << "Initing ortho for airport " << id << '\n';
204 ortho.Init(rwy.threshold_pos, rwy.hdg);
205 rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos); // should come out as zero
206 rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
208 SG_LOG(SG_ATC, SG_ALERT, "Help - can't get good runway at airport " << id << " in FGAILocalTraffic!!");
214 There are two possible scenarios during initialisation:
215 The first is that the user is flying towards the airport, and hence the traffic
216 could be initialised anywhere, as long as the AI planes are consistent with
218 The second is that the user has started the sim at or close to the airport, and
219 hence the traffic must be initialised with respect to the user as well as each other.
220 To a certain extent it's FGAIMgr that has to worry about this, but we need to provide
221 sufficient initialisation functionality within the plane classes to allow the manager
222 to initially position them where and how required.
224 bool FGAILocalTraffic::Init(const string& callsign, string ICAO, OperatingState initialState, PatternLeg initialLeg) {
225 //cout << "FGAILocalTraffic.Init(...) called" << endl;
228 plane.callsign = callsign;
230 if(initialState == EN_ROUTE) return(true);
232 // Get the ATC pointers and airport elev
233 GetAirportDetails(airportID);
235 // Get the active runway details (and copy them into rwy)
236 GetRwyDetails(airportID);
237 //cout << "Runway is " << rwy.rwyID << '\n';
239 // FIXME TODO - pattern direction is still hardwired
240 patternDirection = -1; // Left
241 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
242 if(rwy.rwyID.size() == 3) {
243 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
246 // TODO - this assumes a controlled airport - make sure we revert to CTAF etc if uncontrolled or after-hours.
247 if((initialState == PARKED) || (initialState == TAXIING)) {
248 freq = (double)ground->get_freq() / 100.0;
250 freq = (double)tower->get_freq() / 100.0;
253 //cout << "In Init(), initialState = " << initialState << endl;
254 operatingState = initialState;
256 switch(operatingState) {
258 tuned_station = ground;
259 ourGate = ground->GetGateNode();
260 if(ourGate == NULL) {
261 // Implies no available gates - what shall we do?
262 // For now just vanish the plane - possibly we can make this more elegant in the future
263 SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst attempting Init at " << airportID << '\n');
271 _pos.setelev(aptElev);
272 _hdg = ourGate->heading;
275 // Now we've set the position we can do the ground elev
276 elevInitGood = false;
282 //tuned_station = ground;
283 // FIXME - implement this case properly
284 // For now we'll assume that the plane should start at the hold short in this case
285 // and that we're working without ground network elements. Ie. an airport with no facility file.
286 tuned_station = tower;
288 // Set a position and orientation in an approximate place for hold short.
289 //cout << "rwy.width = " << rwy.width << '\n';
290 orthopos = Point3D((rwy.width / 2.0 + 10.0) * -1.0, 0.0, 0.0);
291 // TODO - set the x pos to be +ve if a RH parallel rwy.
292 _pos = ortho.ConvertFromLocal(orthopos);
293 _pos.setelev(aptElev);
294 _hdg = rwy.hdg + 90.0;
295 // TODO - reset the heading if RH rwy.
300 elevInitGood = false;
306 responseCounter = 0.0;
307 contactTower = false;
310 clearedToLineUp = false;
311 changeFreqType = TOWER;
315 // For now we'll always start the in_pattern case on the threshold ready to take-off
316 // since we've got the implementation for this case already.
317 // TODO - implement proper generic in_pattern startup.
319 // 18/10/03 - adding the ability to start on downwind (mainly to speed testing of the go-around code!!)
321 //cout << "Starting in pattern...\n";
323 tuned_station = tower;
325 circuitsToFly = 0; // ie just fly this circuit and then stop
328 if(initialLeg == DOWNWIND) {
329 _pos = ortho.ConvertFromLocal(Point3D(1000*patternDirection, 800, 0.0));
330 _pos.setelev(rwy.threshold_pos.elev() + 1000 * SG_FEET_TO_METER);
331 _hdg = rwy.hdg + 180.0;
333 elevInitGood = false;
335 SetTrack(rwy.hdg - (180 * patternDirection));
341 _aip.setVisible(true);
342 tower->RegisterAIPlane(plane, this, CIRCUIT, DOWNWIND);
345 // Default to initial position on threshold for now
346 _pos.setlat(rwy.threshold_pos.lat());
347 _pos.setlon(rwy.threshold_pos.lon());
348 _pos.setelev(rwy.threshold_pos.elev());
351 // Now we've set the position we can do the ground elev
352 // This might not always be necessary if we implement in-air start
353 elevInitGood = false;
366 operatingState = IN_PATTERN;
370 // This implies we're being init'd by AIGAVFRTraffic - simple return now
373 SG_LOG(SG_ATC, SG_ALERT, "Attempt to set unknown operating state in FGAILocalTraffic.Init(...)\n");
382 // Set up downwind state - this is designed to be called from derived classes who are already tuned to tower
383 void FGAILocalTraffic::DownwindEntry() {
384 circuitsToFly = 0; // ie just fly this circuit and then stop
386 operatingState = IN_PATTERN;
388 elevInitGood = false;
390 SetTrack(rwy.hdg - (180 * patternDirection));
398 void FGAILocalTraffic::StraightInEntry(bool des) {
399 //cout << "************ STRAIGHT-IN ********************\n";
400 circuitsToFly = 0; // ie just fly this circuit and then stop
402 operatingState = IN_PATTERN;
404 elevInitGood = false;
407 transmitted = true; // TODO - fix this hack.
408 // TODO - set up the next 5 properly for a descent!
417 // Return what type of landing we're doing on this circuit
418 LandingType FGAILocalTraffic::GetLandingOption() {
419 //cout << "circuitsToFly = " << circuitsToFly << '\n';
421 return(touchAndGo ? TOUCH_AND_GO : STOP_AND_GO);
428 // Commands to do something from higher level logic
429 void FGAILocalTraffic::FlyCircuits(int numCircuits, bool tag) {
430 //cout << "FlyCircuits called" << endl;
432 switch(operatingState) {
434 circuitsToFly += numCircuits;
438 // HACK - assume that we're taxiing out for now
439 circuitsToFly += numCircuits;
443 circuitsToFly = numCircuits; // Note that one too many circuits gets flown because we only test and decrement circuitsToFly after landing
444 // thus flying one too many circuits. TODO - Need to sort this out better!
452 // Run the internal calculations
453 void FGAILocalTraffic::Update(double dt) {
454 //cout << "U" << flush;
455 //double responseTime = 10.0; // seconds - this should get more sophisticated at some point
456 responseCounter += dt;
457 if((contactTower) && (responseCounter >= 8.0)) {
458 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
459 string trns = "Tower ";
460 double f = globals->get_ATC_mgr()->GetFrequency(airportID, TOWER) / 100.0;
462 sprintf(buf, "%.2f", f);
465 trns += plane.callsign;
466 pending_transmission = trns;
467 ConditionalTransmit(30.0);
468 responseCounter = 0.0;
469 contactTower = false;
471 changeFreqType = TOWER;
474 if((contactGround) && (responseCounter >= 8.0)) {
475 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
476 string trns = "Ground ";
477 double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;
479 sprintf(buf, "%.2f", f);
483 pending_transmission = trns;
484 ConditionalTransmit(5.0);
485 responseCounter = 0.0;
486 contactGround = false;
488 changeFreqType = GROUND;
491 if((_taxiToGA) && (responseCounter >= 8.0)) {
492 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
493 string trns = "GA Parking, Thank you and Good Day";
494 //double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;
495 pending_transmission = trns;
496 ConditionalTransmit(5.0);
497 tower->DeregisterAIPlane(plane.callsign);
499 // HACK - check if we are at a simple airport or not first
500 globals->get_AI_mgr()->ScheduleRemoval(plane.callsign);
503 if((changeFreq) && (responseCounter > 8.0)) {
504 switch(changeFreqType) {
506 tuned_station = tower;
507 freq = (double)tower->get_freq() / 100.0;
509 // Contact the tower, even if only virtually
510 pending_transmission = plane.callsign;
511 pending_transmission += " at hold short for runway ";
512 pending_transmission += ConvertRwyNumToSpokenString(rwy.rwyID);
513 pending_transmission += " traffic pattern ";
515 pending_transmission += ConvertNumToSpokenDigits(circuitsToFly + 1);
516 pending_transmission += " circuits touch and go";
518 pending_transmission += " one circuit to full stop";
523 tower->DeregisterAIPlane(plane.callsign);
524 tuned_station = ground;
525 freq = (double)ground->get_freq() / 100.0;
526 // HACK - check if we are at a simple airport or not first
527 // TODO FIXME TODO FIXME !!!!!!!
528 if(airportID != "KEMT") globals->get_AI_mgr()->ScheduleRemoval(plane.callsign);
530 // And to avoid compiler warnings...
531 case APPROACH: break;
534 case DEPARTURE: break;
540 //cout << "," << flush;
542 switch(operatingState) {
544 //cout << "In IN_PATTERN\n";
548 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
549 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
550 //cout << "TAKEOFF_ROLL, POS = " << pos.lon() << ", " << pos.lat() << ", " << pos.elev() << '\n';
552 _aip.setVisible(true);
553 //cout << "Making plane visible!\n";
558 FlyTrafficPattern(dt);
562 //cout << "In TAXIING\n";
563 //cout << "*" << flush;
566 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
567 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
569 _aip.setVisible(true);
571 //cout << "Making plane visible!\n";
576 //cout << "~" << flush;
577 if(!((holdingShort) && (!clearedToLineUp))) {
578 //cout << "|" << flush;
581 //cout << ";" << flush;
582 if((clearedToTakeOff) && (responseCounter >= 8.0)) {
583 // possible assumption that we're at the hold short here - may not always hold
584 // TODO - sort out the case where we're cleared to line-up first and then cleared to take-off on the rwy.
585 taxiState = TD_LINING_UP;
586 //cout << "A" << endl;
587 path = ground->GetPath(holdShortNode, rwy.rwyID);
588 //cout << "B" << endl;
589 if(!path.size()) { // Assume no facility file so we'll just taxi to a point on the runway near the threshold
590 //cout << "C" << endl;
592 np->struct_type = NODE;
593 np->pos = ortho.ConvertFromLocal(Point3D(0.0, 10.0, 0.0));
596 //cout << "D" << endl;
599 cout << "path returned was:" << endl;
600 for(unsigned int i=0; i<path.size(); ++i) {
601 switch(path[i]->struct_type) {
603 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
611 clearedToTakeOff = false; // We *are* still cleared - this simply stops the response recurring!!
612 holdingShort = false;
613 string trns = "Cleared for take-off ";
614 trns += plane.callsign;
615 pending_transmission = trns;
619 //cout << "^" << flush;
623 //cout << "In PARKED\n";
626 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
627 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
629 _aip.setVisible(true);
631 //cout << "Making plane visible!\n";
637 if((taxiRequestPending) && (taxiRequestCleared)) {
638 //cout << "&" << flush;
639 // Get the active runway details (in case they've changed since init)
640 GetRwyDetails(airportID);
642 // Get the takeoff node for the active runway, get a path to it and start taxiing
643 path = ground->GetPathToHoldShort(ourGate, rwy.rwyID);
644 if(path.size() < 2) {
645 // something has gone wrong
646 SG_LOG(SG_ATC, SG_ALERT, "Invalid path from gate to theshold in FGAILocalTraffic::FlyCircuits\n");
650 cout << "path returned was:\n";
651 for(unsigned int i=0; i<path.size(); ++i) {
652 switch(path[i]->struct_type) {
654 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
662 path.erase(path.begin()); // pop the gate - we're here already!
663 taxiState = TD_OUTBOUND;
664 taxiRequestPending = false;
665 holdShortNode = (node*)(*(path.begin() + path.size()));
667 } else if(!taxiRequestPending) {
668 //cout << "(" << flush;
669 // Do some communication
670 // airport name + tower + airplane callsign + location + request taxi for + operation type + ?
672 trns += tower->get_name();
674 trns += plane.callsign;
675 trns += " on apron parking request taxi for traffic pattern";
676 //cout << "trns = " << trns << endl;
677 pending_transmission = trns;
679 taxiRequestCleared = false;
680 taxiRequestPending = true;
684 //cout << "!" << flush;
686 // Maybe the below should be set when we get to the threshold and prepare for TO?
687 // FIXME TODO - pattern direction is still hardwired
688 patternDirection = -1; // Left
689 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
690 if(rwy.rwyID.size() == 3) {
691 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
695 //cout << ")" << flush;
700 //cout << "I " << flush;
702 //cout << "Update _pos = " << _pos << ", vis = " << _aip.getVisible() << '\n';
704 // Convienience output for AI debugging using the property logger
705 //fgSetDouble("/AI/Local1/ortho-x", (ortho.ConvertToLocal(_pos)).x());
706 //fgSetDouble("/AI/Local1/ortho-y", (ortho.ConvertToLocal(_pos)).y());
707 //fgSetDouble("/AI/Local1/elev", _pos.elev() * SG_METER_TO_FEET);
709 // And finally, call parent.
710 FGAIPlane::Update(dt);
713 void FGAILocalTraffic::RegisterTransmission(int code) {
715 case 1: // taxi request cleared
716 taxiRequestCleared = true;
717 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to taxi...");
719 case 2: // contact tower
722 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact tower...");
724 case 3: // Cleared to line up
726 clearedToLineUp = true;
727 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to line-up...");
729 case 4: // cleared to take-off
731 clearedToTakeOff = true;
732 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to take-off...");
734 case 5: // contact ground
736 contactGround = true;
737 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact ground...");
739 // case 6 is a temporary mega-hack for controlled airports without separate ground control
740 case 6: // taxi to the GA parking
743 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to taxi to the GA parking...");
745 case 7: // Cleared to land (also implies cleared for the option
746 _clearedToLand = true;
747 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to land...");
749 case 13: // Go around!
752 _clearedToLand = false;
753 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to go-around!!");
760 // Fly a traffic pattern
761 // FIXME - far too much of the mechanics of turning, rolling, accellerating, descending etc is in here.
762 // Move it out to FGAIPlane and have FlyTrafficPattern just specify what to do, not the implementation.
763 void FGAILocalTraffic::FlyTrafficPattern(double dt) {
764 // Need to differentiate between in-air (IAS governed) and on-ground (vel governed)
765 // Take-off is an interesting case - we are on the ground but takeoff speed is IAS governed.
768 // Wind has two effects - a mechanical one in that IAS translates to a different vel, and the hdg != track,
769 // but also a piloting effect, in that the AI must be able to descend at a different rate in order to hit the threshold.
771 //cout << "dt = " << dt << '\n';
773 // ack - I can't remember how long a rate 1 turn is meant to take.
774 double turn_time = 60.0; // seconds - TODO - check this guess
775 double turn_circumference;
777 Point3D orthopos = ortho.ConvertToLocal(_pos); // ortho position of the plane
778 //cout << "runway elev = " << rwy.threshold_pos.elev() << ' ' << rwy.threshold_pos.elev() * SG_METER_TO_FEET << '\n';
779 //cout << "elev = " << _pos.elev() << ' ' << _pos.elev() * SG_METER_TO_FEET << '\n';
781 // HACK FOR TESTING - REMOVE
782 //cout << "Calling ExitRunway..." << endl;
783 //ExitRunway(orthopos);
788 double wind_from = wind_from_hdg->getDoubleValue();
789 double wind_speed = wind_speed_knots->getDoubleValue();
801 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
802 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
804 IAS = vel + (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
807 SetTrack(rwy.hdg); // Hands over control of turning to AIPlane
809 IAS = best_rate_of_climb_speed;
811 slope = 6.0; // Reduced it slightly since it's climbing a lot steeper than I can in the JSBSim C172.
816 // Turn to crosswind if above 700ft AND if other traffic allows
817 // (decided in FGTower and accessed through GetCrosswindConstraint(...)).
818 // According to AIM, traffic should climb to within 300ft of pattern altitude before commencing crosswind turn.
819 // TODO - At hot 'n high airports this may be 500ft AGL though - need to make this a variable.
820 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 700) {
822 if(tower->GetCrosswindConstraint(cc)) {
823 if(orthopos.y() > cc) {
824 //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
827 } else if(orthopos.y() > 1500.0) { // Added this constraint as a hack to prevent turning too early when going around.
828 // TODO - We should be doing it as a distance from takeoff end, not theshold end though.
829 //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
833 // Need to check for levelling off in case we can't turn crosswind as soon
834 // as we would like due to other traffic.
835 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
838 IAS = 80.0; // FIXME - use smooth transistion to new speed and attitude.
840 if(goAround && !goAroundCalled) {
841 if(responseCounter > 5.5) {
842 pending_transmission = plane.callsign;
843 pending_transmission += " going around";
845 goAroundCalled = true;
850 SetTrack(rwy.hdg + (90.0 * patternDirection));
851 if((track < (rwy.hdg - 89.0)) || (track > (rwy.hdg + 89.0))) {
857 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
860 IAS = 80.0; // FIXME - use smooth transistion to new speed
862 // turn 1000m out for now, taking other traffic into accout
863 if(fabs(orthopos.x()) > 900) {
865 if(tower->GetDownwindConstraint(dd)) {
866 if(fabs(orthopos.x()) > fabs(dd)) {
867 //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n';
871 //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n';
877 SetTrack(rwy.hdg - (180 * patternDirection));
878 // just in case we didn't make height on crosswind
879 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
882 IAS = 80.0; // FIXME - use smooth transistion to new speed
884 if((track < (rwy.hdg - 179.0)) || (track > (rwy.hdg + 179.0))) {
890 // just in case we didn't make height on crosswind
891 if(((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 995) && ((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET < 1015)) {
894 IAS = 90.0; // FIXME - use smooth transistion to new speed
896 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET >= 1015) {
899 IAS = 90.0; // FIXME - use smooth transistion to new speed
901 if((orthopos.y() < 0) && (!transmitted)) {
902 TransmitPatternPositionReport();
905 if((orthopos.y() < -100) && (!descending)) {
906 //cout << "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDdddd\n";
907 // Maybe we should think about when to start descending.
908 // For now we're assuming that we aim to follow the same glidepath regardless of wind.
911 CalculateSoD((tower->GetBaseConstraint(d1) ? d1 : -1000.0), (tower->GetDownwindConstraint(d2) ? d2 : 1000.0 * patternDirection), (patternDirection ? true : false));
912 if(SoD.leg == DOWNWIND) {
913 descending = (orthopos.y() < SoD.y ? true : false);
918 slope = -5.5; // FIXME - calculate to descent at 500fpm and hit the desired point on the runway (taking wind into account as well!!)
923 // Try and arrange to turn nicely onto base
924 turn_circumference = IAS * 0.514444 * turn_time;
925 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
926 //We'll leave it as a hack with IAS for now but it needs revisiting.
927 turn_radius = turn_circumference / (2.0 * DCL_PI);
928 if(orthopos.y() < -1000.0 + turn_radius) {
929 //if(orthopos.y() < -980) {
931 if(tower->GetBaseConstraint(bb)) {
932 if(fabs(orthopos.y()) > fabs(bb)) {
933 //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n';
939 //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n';
947 SetTrack(rwy.hdg - (90 * patternDirection));
948 if(fabs(rwy.hdg - track) < 91.0) {
954 // Base report should only be transmitted at uncontrolled airport - not towered.
955 if(!_controlled) TransmitPatternPositionReport();
961 // Make downwind leg position artifically large to avoid any chance of SoD being returned as
962 // on downwind when we are already on base.
963 CalculateSoD((tower->GetBaseConstraint(d1) ? d1 : -1000.0), (10000.0 * patternDirection), (patternDirection ? true : false));
964 if(SoD.leg == BASE) {
965 descending = (fabs(orthopos.y()) < fabs(SoD.y) ? true : false);
970 slope = -5.5; // FIXME - calculate to descent at 500fpm and hit the threshold (taking wind into account as well!!)
975 // Try and arrange to turn nicely onto final
976 turn_circumference = IAS * 0.514444 * turn_time;
977 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
978 //We'll leave it as a hack with IAS for now but it needs revisiting.
979 turn_radius = turn_circumference / (2.0 * DCL_PI);
980 if(fabs(orthopos.x()) < (turn_radius + 50)) {
988 if(fabs(track - rwy.hdg) < 0.6) {
990 vel = nominal_final_speed;
994 if(goAround && responseCounter > 2.0) {
997 IAS = best_rate_of_climb_speed;
998 slope = 5.0; // A bit less steep than the initial climbout.
1000 goAroundCalled = false;
1006 if((!_controlled) || (!_clearedToLand)) TransmitPatternPositionReport();
1010 // Make base leg position artifically large to avoid any chance of SoD being returned as
1011 // on base or downwind when we are already on final.
1012 CalculateSoD(-10000.0, (1000.0 * patternDirection), (patternDirection ? true : false));
1013 if(SoD.leg == FINAL) {
1014 descending = (fabs(orthopos.y()) < fabs(SoD.y) ? true : false);
1019 if(orthopos.y() < -50.0) {
1020 double thesh_offset = 30.0;
1021 slope = atan((_pos.elev() - dclGetAirportElev(airportID)) / (orthopos.y() - thesh_offset)) * DCL_RADIANS_TO_DEGREES;
1022 //cout << "slope = " << slope << ", elev = " << _pos.elev() << ", apt_elev = " << dclGetAirportElev(airportID) << ", op.y = " << orthopos.y() << '\n';
1023 if(slope < -10.0) slope = -10.0;
1024 _savedSlope = slope;
1028 if(_pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) {
1029 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
1030 if(_pos.elev() < (_aip.getSGLocation()->get_cur_elev_m() + wheelOffset + 1.0)) {
1034 } else if(_pos.elev() < (_aip.getSGLocation()->get_cur_elev_m() + wheelOffset + 5.0)) {
1039 slope = _savedSlope;
1044 // Elev not determined
1045 slope = _savedSlope;
1050 slope = _savedSlope;
1056 // Try and track the extended centreline
1057 SetTrack(rwy.hdg - (0.2 * orthopos.x()));
1058 //cout << "orthopos.x() = " << orthopos.x() << " hdg = " << hdg << '\n';
1059 if(_pos.elev() < (rwy.threshold_pos.elev()+20.0+wheelOffset)) {
1060 DoGroundElev(); // Need to call it here expicitly on final since it's only called
1061 // for us in update(...) when the inAir flag is false.
1063 if(_pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) {
1066 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
1067 if((_aip.getSGLocation()->get_cur_elev_m() + wheelOffset) > _pos.elev()) {
1073 ClearTrack(); // Take over explicit track handling since AIPlane currently always banks when changing course
1075 } // else need a fallback position based on arpt elev in case ground elev determination fails?
1083 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
1084 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
1089 // FIXME - differentiate between touch and go and full stops
1091 //cout << "Vel <= 15.0, circuitsToFly = " << circuitsToFly << endl;
1092 if(circuitsToFly <= 0) {
1093 //cout << "Calling ExitRunway..." << endl;
1094 ExitRunway(orthopos);
1097 //cout << "Taking off again..." << endl;
1108 // FIXME - at the moment this is a bit screwy
1109 // The velocity correction is applied based on the relative headings.
1110 // Then the heading is changed based on the velocity.
1111 // Which comes first, the chicken or the egg?
1112 // Does it really matter?
1114 // Apply wind to ground-relative velocity if in the air
1115 vel = IAS - (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
1116 //crab = f(track, wind, vel);
1117 // The vector we need to fly is our desired vector minus the wind vector
1118 // TODO - we probably ought to use plib's built in vector types and operations for this
1119 // ie. There's almost *certainly* a better way to do this!
1120 double gxx = vel * sin(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity x component wrt ground
1121 double gyy = vel * cos(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity y component wrt ground
1122 double wxx = wind_speed * sin((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS); // Wind velocity x component
1123 double wyy = wind_speed * cos((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS); // Wind velocity y component
1124 double axx = gxx - wxx; // Plane in-air velocity x component
1125 double ayy = gyy - wyy; // Plane in-air velocity y component
1126 // Now we want the angle between gxx and axx (which is the crab)
1127 double maga = sqrt(axx*axx + ayy*ayy);
1128 double magg = sqrt(gxx*gxx + gyy*gyy);
1129 crab = acos((axx*gxx + ayy*gyy) / (maga * magg));
1130 // At this point this works except we're getting the modulus of the angle
1131 //cout << "crab = " << crab << '\n';
1133 // Make sure both headings are in the 0->360 circle in order to get sane differences
1134 dclBoundHeading(wind_from);
1135 dclBoundHeading(track);
1136 if(track > wind_from) {
1137 if((track - wind_from) <= 180) {
1141 if((wind_from - track) >= 180) {
1145 } else { // on the ground - crab dosen't apply
1149 //cout << "X " << orthopos.x() << " Y " << orthopos.y() << " SLOPE " << slope << " elev " << _pos.elev() * SG_METER_TO_FEET << '\n';
1151 _hdg = track + crab;
1152 dist = vel * 0.514444 * dt;
1153 _pos = dclUpdatePosition(_pos, track, slope, dist);
1156 // Pattern direction is true for right, false for left
1157 void FGAILocalTraffic::CalculateSoD(double base_leg_pos, double downwind_leg_pos, bool pattern_direction) {
1158 // For now we'll ignore wind and hardwire the glide angle.
1159 double ga = 5.5; //degrees
1160 double pa = 1000.0 * SG_FEET_TO_METER; // pattern altitude in meters
1161 // FIXME - get glideslope angle and pattern altitude agl from airport details if available
1163 // For convienience, we'll have +ve versions of the input distances
1164 double blp = fabs(base_leg_pos);
1165 double dlp = fabs(downwind_leg_pos);
1167 //double turn_allowance = 150.0; // Approximate distance in meters that a 90deg corner is shortened by turned in a light plane.
1169 double stod = pa / tan(ga * DCL_DEGREES_TO_RADIANS); // distance in meters from touchdown point to start descent
1170 //cout << "Descent to start = " << stod << " meters out\n";
1171 if(stod < blp) { // Start descending on final
1173 SoD.y = stod * -1.0;
1175 } else if(stod < (blp + dlp)) { // Start descending on base leg
1178 SoD.x = (pattern_direction ? (stod - dlp) : (stod - dlp) * -1.0);
1179 } else { // Start descending on downwind leg
1181 SoD.x = (pattern_direction ? dlp : dlp * -1.0);
1182 SoD.y = (blp - (stod - (blp + dlp))) * -1.0;
1186 void FGAILocalTraffic::TransmitPatternPositionReport(void) {
1187 // airport name + "traffic" + airplane callsign + pattern direction + pattern leg + rwy + ?
1191 trns += tower->get_name();
1192 trns += " Traffic ";
1193 trns += plane.callsign;
1194 if(patternDirection == 1) {
1200 // We could probably get rid of this whole switch statement and just pass a string containing the leg from the FlyPattern function.
1201 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?
1203 // Fall through to CROSSWIND
1204 case CROSSWIND: // I don't think this case will be used here but it can't hurt to leave it in
1205 trns += "crosswind ";
1208 // Fall through to DOWNWIND
1210 trns += "downwind ";
1214 // Fall through to BASE
1219 // Fall through to FINAL
1220 case FINAL: // maybe this should include long/short final if appropriate?
1224 default: // Hopefully this won't be used
1228 trns += ConvertRwyNumToSpokenString(rwy.rwyID);
1232 // And add the airport name again
1233 trns += tower->get_name();
1235 pending_transmission = trns;
1236 ConditionalTransmit(60.0, code); // Assume a report of this leg will be invalid if we can't transmit within a minute.
1240 // TODO - Really should enumerate these coded values.
1241 void FGAILocalTraffic::ProcessCallback(int code) {
1242 // 1 - Request Departure from ground
1243 // 2 - Report at hold short
1244 // 3 - Report runway vacated
1245 // 10 - report crosswind
1246 // 11 - report downwind
1248 // 13 - report final
1250 ground->RequestDeparture(plane, this);
1251 } else if(code == 2) {
1252 tower->ContactAtHoldShort(plane, this, CIRCUIT);
1253 } else if(code == 3) {
1254 tower->ReportRunwayVacated(plane.callsign);
1255 } else if(code == 11) {
1256 tower->ReportDownwind(plane.callsign);
1257 } else if(code == 13) {
1258 tower->ReportFinal(plane.callsign);
1262 void FGAILocalTraffic::ExitRunway(Point3D orthopos) {
1263 //cout << "In ExitRunway" << endl;
1264 //cout << "Runway ID is " << rwy.ID << endl;
1266 _clearedToLand = false;
1268 node_array_type exitNodes = ground->GetExits(rwy.rwyID); //I suppose we ought to have some fallback for rwy with no defined exits?
1270 cout << "Node ID's of exits are ";
1271 for(unsigned int i=0; i<exitNodes.size(); ++i) {
1272 cout << exitNodes[i]->nodeID << ' ';
1276 if(exitNodes.size()) {
1277 //Find the next exit from orthopos.y
1279 double dist = 100000; //ie. longer than any runway in existance
1280 double backdist = 100000;
1281 node_array_iterator nItr = exitNodes.begin();
1282 node* rwyExit = *(exitNodes.begin());
1283 //int gateID; //This might want to be more persistant at some point
1284 while(nItr != exitNodes.end()) {
1285 d = ortho.ConvertToLocal((*nItr)->pos).y() - ortho.ConvertToLocal(_pos).y(); //FIXME - consider making orthopos a class variable
1292 if(fabs(d) < backdist) {
1294 //TODO - need some logic here that if we don't get a forward exit we turn round and store the backwards one
1299 ourGate = ground->GetGateNode();
1300 if(ourGate == NULL) {
1301 // Implies no available gates - what shall we do?
1302 // For now just vanish the plane - possibly we can make this more elegant in the future
1303 SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst landing at " << airportID << '\n');
1304 //_aip.setVisible(false);
1305 //cout << "Setting visible false\n";
1306 operatingState = PARKED;
1309 path = ground->GetPath(rwyExit, ourGate);
1311 cout << "path returned was:" << endl;
1312 for(unsigned int i=0; i<path.size(); ++i) {
1313 switch(path[i]->struct_type) {
1315 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
1323 taxiState = TD_INBOUND;
1326 // Something must have gone wrong with the ground network file - or there is only a rwy here and no exits defined
1327 SG_LOG(SG_ATC, SG_INFO, "No exits found by FGAILocalTraffic from runway " << rwy.rwyID << " at " << airportID << '\n');
1328 //cout << "No exits found by " << plane.callsign << " from runway " << rwy.rwyID << " at " << airportID << '\n';
1329 // What shall we do - just remove the plane from sight?
1330 _aip.setVisible(false);
1331 //cout << "Setting visible false\n";
1332 //tower->ReportRunwayVacated(plane.callsign);
1333 string trns = "Clear of the runway ";
1334 trns += plane.callsign;
1335 pending_transmission = trns;
1337 operatingState = PARKED;
1341 // Set the class variable nextTaxiNode to the next node in the path
1342 // and update taxiPathPos, the class variable path iterator position
1343 // TODO - maybe should return error codes to the calling function if we fail here
1344 void FGAILocalTraffic::GetNextTaxiNode() {
1345 //cout << "GetNextTaxiNode called " << endl;
1346 //cout << "taxiPathPos = " << taxiPathPos << endl;
1347 ground_network_path_iterator pathItr = path.begin() + taxiPathPos;
1348 if(pathItr == path.end()) {
1349 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - no more nodes in path\n");
1351 if((*pathItr)->struct_type == NODE) {
1352 //cout << "ITS A NODE" << endl;
1353 //*pathItr = new node;
1354 nextTaxiNode = (node*)*pathItr;
1358 //cout << "ITS NOT A NODE" << endl;
1359 //The first item in found must have been an arc
1360 //Assume for now that it was straight
1363 if(pathItr == path.end()) {
1364 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - path ended with an arc\n");
1365 } else if((*pathItr)->struct_type == NODE) {
1366 nextTaxiNode = (node*)*pathItr;
1369 //OOPS - two non-nodes in a row - that shouldn't happen ATM
1370 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - two non-nodes in sequence\n");
1376 // StartTaxi - set up the taxiing state - call only at the start of taxiing
1377 void FGAILocalTraffic::StartTaxi() {
1378 //cout << "StartTaxi called" << endl;
1379 operatingState = TAXIING;
1383 //Set the desired heading
1384 //Assume we are aiming for first node on path
1385 //Eventually we may need to consider the fact that we might start on a curved arc and
1386 //not be able to head directly for the first node.
1387 GetNextTaxiNode(); // sets the class variable nextTaxiNode to the next taxi node!
1388 desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos);
1389 //cout << "First taxi heading is " << desiredTaxiHeading << endl;
1392 // speed in knots, headings in degrees, radius in meters.
1393 static double TaxiTurnTowardsHeading(double current_hdg, double desired_hdg, double speed, double radius, double dt) {
1394 // wrap heading - this prevents a logic bug where the plane would just go round in circles!!
1395 while(current_hdg < 0.0) {
1396 current_hdg += 360.0;
1398 while(current_hdg > 360.0) {
1399 current_hdg -= 360.0;
1401 if(fabs(current_hdg - desired_hdg) > 0.1) {
1402 // Which is the quickest direction to turn onto heading?
1403 if(desired_hdg > current_hdg) {
1404 if((desired_hdg - current_hdg) <= 180) {
1406 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1407 // TODO - check that increments are less than the delta that we check for the right direction
1408 // Probably need to reduce convergence speed as convergence is reached
1410 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1413 if((current_hdg - desired_hdg) <= 180) {
1415 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1416 // TODO - check that increments are less than the delta that we check for the right direction
1417 // Probably need to reduce convergence speed as convergence is reached
1419 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1423 return(current_hdg);
1426 void FGAILocalTraffic::Taxi(double dt) {
1427 //cout << "Taxi called" << endl;
1428 // Logic - if we are further away from next point than turn radius then head for it
1429 // If we have reached turning point then get next point and turn onto that heading
1430 // Look out for the finish!!
1432 //Point3D orthopos = ortho.ConvertToLocal(pos); // ortho position of the plane
1433 desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos);
1435 bool lastNode = (taxiPathPos == path.size() ? true : false);
1437 //cout << "LAST NODE\n";
1440 // HACK ALERT! - for now we will taxi at constant speed for straights and turns
1442 // Remember that hdg is always equal to track when taxiing so we don't have to consider them both
1443 double dist_to_go = dclGetHorizontalSeparation(_pos, nextTaxiNode->pos); // we may be able to do this more cheaply using orthopos
1444 //cout << "dist_to_go = " << dist_to_go << endl;
1445 if((nextTaxiNode->type == GATE) && (dist_to_go <= 0.1)) {
1446 // This might be more robust to outward paths starting with a gate if we check for either
1447 // last node or TD_INBOUND ?
1449 operatingState = PARKED;
1450 } else if(((dist_to_go > taxiTurnRadius) || (nextTaxiNode->type == GATE)) && (!liningUp)){
1451 // if the turn radius is r, and speed is s, then in a time dt we turn through
1452 // ((s.dt)/(PI.r)) x 180 degrees
1453 // or alternatively (s.dt)/r radians
1454 //cout << "hdg = " << hdg << " desired taxi heading = " << desiredTaxiHeading << '\n';
1455 _hdg = TaxiTurnTowardsHeading(_hdg, desiredTaxiHeading, nominalTaxiSpeed, taxiTurnRadius, dt);
1456 double vel = nominalTaxiSpeed;
1457 //cout << "vel = " << vel << endl;
1458 double dist = vel * 0.514444 * dt;
1459 //cout << "dist = " << dist << endl;
1460 double track = _hdg;
1461 //cout << "track = " << track << endl;
1463 _pos = dclUpdatePosition(_pos, track, slope, dist);
1464 //cout << "Updated position...\n";
1465 if(_aip.getSGLocation()->get_cur_elev_m() > -9990) {
1466 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
1467 } // else don't change the elev until we get a valid ground elev again!
1468 } else if(lastNode) {
1469 if(taxiState == TD_LINING_UP) {
1470 if((!liningUp) && (dist_to_go <= taxiTurnRadius)) {
1474 _hdg = TaxiTurnTowardsHeading(_hdg, rwy.hdg, nominalTaxiSpeed, taxiTurnRadius, dt);
1475 double vel = nominalTaxiSpeed;
1476 //cout << "vel = " << vel << endl;
1477 double dist = vel * 0.514444 * dt;
1478 //cout << "dist = " << dist << endl;
1479 double track = _hdg;
1480 //cout << "track = " << track << endl;
1482 _pos = dclUpdatePosition(_pos, track, slope, dist);
1483 //cout << "Updated position...\n";
1484 if(_aip.getSGLocation()->get_cur_elev_m() > -9990) {
1485 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
1486 } // else don't change the elev until we get a valid ground elev again!
1487 if(fabs(_hdg - rwy.hdg) <= 1.0) {
1488 operatingState = IN_PATTERN;
1494 } else if(taxiState == TD_OUTBOUND) {
1495 // Pause awaiting further instructions
1496 // and for now assume we've reached the hold-short node
1497 holdingShort = true;
1498 } // else at the moment assume TD_INBOUND always ends in a gate in which case we can ignore it
1500 // Time to turn (we've already checked it's not the end we're heading for).
1501 // set the target node to be the next node which will prompt automatically turning onto
1502 // the right heading in the stuff above, with the usual provisos applied.
1504 // For now why not just recursively call this function?
1510 // Warning - ground elev determination is CPU intensive
1511 // Either this function or the logic of how often it is called
1512 // will almost certainly change.
1513 void FGAILocalTraffic::DoGroundElev() {
1515 // It would be nice if we could set the correct tile center here in order to get a correct
1516 // answer with one call to the function, but what I tried in the two commented-out lines
1517 // below only intermittently worked, and I haven't quite groked why yet.
1518 //SGBucket buck(pos.lon(), pos.lat());
1519 //aip.getSGLocation()->set_tile_center(Point3D(buck.get_center_lon(), buck.get_center_lat(), 0.0));
1521 double visibility_meters = fgGetDouble("/environment/visibility-m");
1522 //globals->get_tile_mgr()->prep_ssg_nodes( acmodel_location,
1523 globals->get_tile_mgr()->prep_ssg_nodes( _aip.getSGLocation(), visibility_meters );
1524 Point3D scenery_center = globals->get_scenery()->get_center();
1525 globals->get_tile_mgr()->update( _aip.getSGLocation(), visibility_meters, (_aip.getSGLocation())->get_absolute_view_pos( scenery_center ) );
1526 // save results of update in SGLocation for fdm...
1528 //if ( globals->get_scenery()->get_cur_elev() > -9990 ) {
1529 // acmodel_location->
1530 // set_cur_elev_m( globals->get_scenery()->get_cur_elev() );
1533 // The need for this here means that at least 2 consecutive passes are needed :-(
1534 _aip.getSGLocation()->set_tile_center( globals->get_scenery()->get_next_center() );
1536 //cout << "Transform Elev is " << globals->get_scenery()->get_cur_elev() << '\n';
1537 _aip.getSGLocation()->set_cur_elev_m(globals->get_scenery()->get_cur_elev());
1538 //return(globals->get_scenery()->get_cur_elev());