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 <Main/viewer.hxx>
42 #include <Scenery/scenery.hxx>
43 #include <Scenery/tilemgr.hxx>
44 #include <simgear/math/point3d.hxx>
45 #include <simgear/math/sg_geodesy.hxx>
46 #include <simgear/misc/sg_path.hxx>
53 #include "AILocalTraffic.hxx"
54 #include "ATCutils.hxx"
57 FGAILocalTraffic::FGAILocalTraffic() {
58 /*ssgBranch *model = sgLoad3DModel( globals->get_fg_root(),
61 globals->get_sim_time_sec() );
69 ATC = globals->get_ATC_mgr();
71 // TODO - unhardwire this
72 plane.type = GA_SINGLE;
78 //Hardwire initialisation for now - a lot of this should be read in from config eventually
80 best_rate_of_climb_speed = 70.0;
82 //nominal_climb_speed;
84 //nominal_circuit_speed;
87 nominal_descent_rate = 500.0;
88 nominal_final_speed = 65.0;
89 //nominal_approach_speed;
90 //stall_speed_landing_config;
91 nominalTaxiSpeed = 7.5;
93 wheelOffset = 1.45; // Warning - hardwired to the C172 - we need to read this in from file.
95 // Init the property nodes
96 wind_from_hdg = fgGetNode("/environment/wind-from-heading-deg", true);
97 wind_speed_knots = fgGetNode("/environment/wind-speed-kt", true);
100 taxiRequestPending = false;
101 taxiRequestCleared = false;
102 holdingShort = false;
103 clearedToLineUp = false;
104 clearedToTakeOff = false;
105 _clearedToLand = false;
106 reportReadyForDeparture = false;
107 contactTower = false;
108 contactGround = false;
112 targetDescentRate = 0.0;
114 goAroundCalled = false;
124 FGAILocalTraffic::~FGAILocalTraffic() {
128 void FGAILocalTraffic::GetAirportDetails(string id) {
130 if(ATC->GetAirportATCDetails(airportID, &a)) {
131 if(a.tower_freq) { // Has a tower - TODO - check the opening hours!!!
132 tower = (FGTower*)ATC->GetATCPointer(airportID, TOWER); // Maybe need some error checking here
134 // Something has gone wrong - abort or carry on with un-towered operation?
135 SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a tower pointer from tower control for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
141 ground = tower->GetGroundPtr();
143 // Something has gone wrong :-(
144 SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a ground pointer from tower control in FGAILocalTraffic::GetAirportDetails() :-(");
149 // TODO - Check CTAF, unicom etc
152 SG_LOG(SG_ATC, SG_ALERT, "Unable to find airport details in for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
155 // Get the airport elevation
156 aptElev = dclGetAirportElev(airportID.c_str());
157 //cout << "Airport elev in AILocalTraffic = " << aptElev << '\n';
158 // WARNING - we use this elev for the whole airport - some assumptions in the code
159 // might fall down with very slopey airports.
162 // Get details of the active runway
163 // It is assumed that by the time this is called the tower control and airport code will have been set up.
164 void FGAILocalTraffic::GetRwyDetails(string id) {
165 //cout << "GetRwyDetails called" << endl;
167 rwy.rwyID = tower->GetActiveRunway();
168 //cout << "id = " << id << '\n';
169 //cout << "Returned id is " << tower->get_ident() << '\n';
170 //cout << "Returned name is " << tower->get_name() << '\n';
171 //cout << "rwy.rwyID = " << rwy.rwyID << '\n';
173 // Now we need to get the threshold position and rwy heading
176 bool rwyGood = globals->get_runways()->search(id, rwy.rwyID, &runway);
178 double hdg = runway.heading;
179 double other_way = hdg - 180.0;
180 while(other_way <= 0.0) {
184 // move to the +l end/center of the runway
185 //cout << "Runway center is at " << runway.lon << ", " << runway.lat << '\n';
186 Point3D origin = Point3D(runway.lon, runway.lat, aptElev);
187 Point3D ref = origin;
188 double tshlon, tshlat, tshr;
189 double tolon, tolat, tor;
190 rwy.length = runway.length * SG_FEET_TO_METER;
191 rwy.width = runway.width * SG_FEET_TO_METER;
192 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), other_way,
193 rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
194 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), hdg,
195 rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor );
196 // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user.
197 // now copy what we need out of runway into rwy
198 rwy.threshold_pos = Point3D(tshlon, tshlat, aptElev);
199 Point3D takeoff_end = Point3D(tolon, tolat, aptElev);
200 //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n';
201 //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
203 // Set the projection for the local area
204 //cout << "Initing ortho for airport " << id << '\n';
205 ortho.Init(rwy.threshold_pos, rwy.hdg);
206 rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos); // should come out as zero
207 rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
209 SG_LOG(SG_ATC, SG_ALERT, "Help - can't get good runway at airport " << id << " in FGAILocalTraffic!!");
215 There are two possible scenarios during initialisation:
216 The first is that the user is flying towards the airport, and hence the traffic
217 could be initialised anywhere, as long as the AI planes are consistent with
219 The second is that the user has started the sim at or close to the airport, and
220 hence the traffic must be initialised with respect to the user as well as each other.
221 To a certain extent it's FGAIMgr that has to worry about this, but we need to provide
222 sufficient initialisation functionality within the plane classes to allow the manager
223 to initially position them where and how required.
225 bool FGAILocalTraffic::Init(const string& callsign, string ICAO, OperatingState initialState, PatternLeg initialLeg) {
226 //cout << "FGAILocalTraffic.Init(...) called" << endl;
229 plane.callsign = callsign;
231 if(initialState == EN_ROUTE) return(true);
233 // Get the ATC pointers and airport elev
234 GetAirportDetails(airportID);
236 // Get the active runway details (and copy them into rwy)
237 GetRwyDetails(airportID);
238 //cout << "Runway is " << rwy.rwyID << '\n';
240 // FIXME TODO - pattern direction is still hardwired
241 patternDirection = -1; // Left
242 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
243 if(rwy.rwyID.size() == 3) {
244 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
247 // TODO - this assumes a controlled airport - make sure we revert to CTAF etc if uncontrolled or after-hours.
248 if((initialState == PARKED) || (initialState == TAXIING)) {
249 freq = (double)ground->get_freq() / 100.0;
251 freq = (double)tower->get_freq() / 100.0;
254 //cout << "In Init(), initialState = " << initialState << endl;
255 operatingState = initialState;
257 switch(operatingState) {
259 tuned_station = ground;
260 ourGate = ground->GetGateNode();
261 if(ourGate == NULL) {
262 // Implies no available gates - what shall we do?
263 // For now just vanish the plane - possibly we can make this more elegant in the future
264 SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst attempting Init at " << airportID << '\n');
272 _pos.setelev(aptElev);
273 _hdg = ourGate->heading;
276 // Now we've set the position we can do the ground elev
277 elevInitGood = false;
283 //tuned_station = ground;
284 // FIXME - implement this case properly
285 // For now we'll assume that the plane should start at the hold short in this case
286 // and that we're working without ground network elements. Ie. an airport with no facility file.
287 tuned_station = tower;
289 // Set a position and orientation in an approximate place for hold short.
290 //cout << "rwy.width = " << rwy.width << '\n';
291 orthopos = Point3D((rwy.width / 2.0 + 10.0) * -1.0, 0.0, 0.0);
292 // TODO - set the x pos to be +ve if a RH parallel rwy.
293 _pos = ortho.ConvertFromLocal(orthopos);
294 _pos.setelev(aptElev);
295 _hdg = rwy.hdg + 90.0;
296 // TODO - reset the heading if RH rwy.
301 elevInitGood = false;
307 responseCounter = 0.0;
308 contactTower = false;
311 clearedToLineUp = false;
312 changeFreqType = TOWER;
316 // For now we'll always start the in_pattern case on the threshold ready to take-off
317 // since we've got the implementation for this case already.
318 // TODO - implement proper generic in_pattern startup.
320 // 18/10/03 - adding the ability to start on downwind (mainly to speed testing of the go-around code!!)
322 //cout << "Starting in pattern...\n";
324 tuned_station = tower;
326 circuitsToFly = 0; // ie just fly this circuit and then stop
329 if(initialLeg == DOWNWIND) {
330 _pos = ortho.ConvertFromLocal(Point3D(1000*patternDirection, 800, 0.0));
331 _pos.setelev(rwy.threshold_pos.elev() + 1000 * SG_FEET_TO_METER);
332 _hdg = rwy.hdg + 180.0;
334 elevInitGood = false;
336 SetTrack(rwy.hdg - (180 * patternDirection));
342 _aip.setVisible(true);
343 tower->RegisterAIPlane(plane, this, CIRCUIT, DOWNWIND);
346 // Default to initial position on threshold for now
347 _pos.setlat(rwy.threshold_pos.lat());
348 _pos.setlon(rwy.threshold_pos.lon());
349 _pos.setelev(rwy.threshold_pos.elev());
352 // Now we've set the position we can do the ground elev
353 // This might not always be necessary if we implement in-air start
354 elevInitGood = false;
367 operatingState = IN_PATTERN;
371 // This implies we're being init'd by AIGAVFRTraffic - simple return now
374 SG_LOG(SG_ATC, SG_ALERT, "Attempt to set unknown operating state in FGAILocalTraffic.Init(...)\n");
383 // Set up downwind state - this is designed to be called from derived classes who are already tuned to tower
384 void FGAILocalTraffic::DownwindEntry() {
385 circuitsToFly = 0; // ie just fly this circuit and then stop
387 operatingState = IN_PATTERN;
389 elevInitGood = false;
391 SetTrack(rwy.hdg - (180 * patternDirection));
399 void FGAILocalTraffic::StraightInEntry(bool des) {
400 //cout << "************ STRAIGHT-IN ********************\n";
401 circuitsToFly = 0; // ie just fly this circuit and then stop
403 operatingState = IN_PATTERN;
405 elevInitGood = false;
408 transmitted = true; // TODO - fix this hack.
409 // TODO - set up the next 5 properly for a descent!
418 // Return what type of landing we're doing on this circuit
419 LandingType FGAILocalTraffic::GetLandingOption() {
420 //cout << "circuitsToFly = " << circuitsToFly << '\n';
422 return(touchAndGo ? TOUCH_AND_GO : STOP_AND_GO);
429 // Commands to do something from higher level logic
430 void FGAILocalTraffic::FlyCircuits(int numCircuits, bool tag) {
431 //cout << "FlyCircuits called" << endl;
433 switch(operatingState) {
435 circuitsToFly += numCircuits;
439 // HACK - assume that we're taxiing out for now
440 circuitsToFly += numCircuits;
444 circuitsToFly = numCircuits; // Note that one too many circuits gets flown because we only test and decrement circuitsToFly after landing
445 // thus flying one too many circuits. TODO - Need to sort this out better!
453 // Run the internal calculations
454 void FGAILocalTraffic::Update(double dt) {
455 //cout << "U" << flush;
456 //double responseTime = 10.0; // seconds - this should get more sophisticated at some point
457 responseCounter += dt;
458 if((contactTower) && (responseCounter >= 8.0)) {
459 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
460 string trns = "Tower ";
461 double f = globals->get_ATC_mgr()->GetFrequency(airportID, TOWER) / 100.0;
463 sprintf(buf, "%.2f", f);
466 trns += plane.callsign;
467 pending_transmission = trns;
468 ConditionalTransmit(30.0);
469 responseCounter = 0.0;
470 contactTower = false;
472 changeFreqType = TOWER;
475 if((contactGround) && (responseCounter >= 8.0)) {
476 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
477 string trns = "Ground ";
478 double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;
480 sprintf(buf, "%.2f", f);
484 pending_transmission = trns;
485 ConditionalTransmit(5.0);
486 responseCounter = 0.0;
487 contactGround = false;
489 changeFreqType = GROUND;
492 if((_taxiToGA) && (responseCounter >= 8.0)) {
493 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
494 string trns = "GA Parking, Thank you and Good Day";
495 //double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;
496 pending_transmission = trns;
497 ConditionalTransmit(5.0);
498 tower->DeregisterAIPlane(plane.callsign);
500 // HACK - check if we are at a simple airport or not first
501 globals->get_AI_mgr()->ScheduleRemoval(plane.callsign);
504 if((changeFreq) && (responseCounter > 8.0)) {
505 switch(changeFreqType) {
507 tuned_station = tower;
508 freq = (double)tower->get_freq() / 100.0;
510 // Contact the tower, even if only virtually
511 pending_transmission = plane.callsign;
512 pending_transmission += " at hold short for runway ";
513 pending_transmission += ConvertRwyNumToSpokenString(rwy.rwyID);
514 pending_transmission += " traffic pattern ";
516 pending_transmission += ConvertNumToSpokenDigits(circuitsToFly + 1);
517 pending_transmission += " circuits touch and go";
519 pending_transmission += " one circuit to full stop";
524 tower->DeregisterAIPlane(plane.callsign);
525 tuned_station = ground;
526 freq = (double)ground->get_freq() / 100.0;
527 // HACK - check if we are at a simple airport or not first
528 // TODO FIXME TODO FIXME !!!!!!!
529 if(airportID != "KEMT") globals->get_AI_mgr()->ScheduleRemoval(plane.callsign);
531 // And to avoid compiler warnings...
532 case APPROACH: break;
535 case DEPARTURE: break;
541 //cout << "," << flush;
543 switch(operatingState) {
545 //cout << "In IN_PATTERN\n";
549 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
550 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
551 //cout << "TAKEOFF_ROLL, POS = " << pos.lon() << ", " << pos.lat() << ", " << pos.elev() << '\n';
553 _aip.setVisible(true);
554 //cout << "Making plane visible!\n";
559 FlyTrafficPattern(dt);
563 //cout << "In TAXIING\n";
564 //cout << "*" << flush;
567 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
568 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
570 _aip.setVisible(true);
572 //cout << "Making plane visible!\n";
577 //cout << "~" << flush;
578 if(!((holdingShort) && (!clearedToLineUp))) {
579 //cout << "|" << flush;
582 //cout << ";" << flush;
583 if((clearedToTakeOff) && (responseCounter >= 8.0)) {
584 // possible assumption that we're at the hold short here - may not always hold
585 // TODO - sort out the case where we're cleared to line-up first and then cleared to take-off on the rwy.
586 taxiState = TD_LINING_UP;
587 //cout << "A" << endl;
588 path = ground->GetPath(holdShortNode, rwy.rwyID);
589 //cout << "B" << endl;
590 if(!path.size()) { // Assume no facility file so we'll just taxi to a point on the runway near the threshold
591 //cout << "C" << endl;
593 np->struct_type = NODE;
594 np->pos = ortho.ConvertFromLocal(Point3D(0.0, 10.0, 0.0));
597 //cout << "D" << endl;
600 cout << "path returned was:" << endl;
601 for(unsigned int i=0; i<path.size(); ++i) {
602 switch(path[i]->struct_type) {
604 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
612 clearedToTakeOff = false; // We *are* still cleared - this simply stops the response recurring!!
613 holdingShort = false;
614 string trns = "Cleared for take-off ";
615 trns += plane.callsign;
616 pending_transmission = trns;
620 //cout << "^" << flush;
624 //cout << "In PARKED\n";
627 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
628 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
630 _aip.setVisible(true);
632 //cout << "Making plane visible!\n";
638 if((taxiRequestPending) && (taxiRequestCleared)) {
639 //cout << "&" << flush;
640 // Get the active runway details (in case they've changed since init)
641 GetRwyDetails(airportID);
643 // Get the takeoff node for the active runway, get a path to it and start taxiing
644 path = ground->GetPathToHoldShort(ourGate, rwy.rwyID);
645 if(path.size() < 2) {
646 // something has gone wrong
647 SG_LOG(SG_ATC, SG_ALERT, "Invalid path from gate to theshold in FGAILocalTraffic::FlyCircuits\n");
651 cout << "path returned was:\n";
652 for(unsigned int i=0; i<path.size(); ++i) {
653 switch(path[i]->struct_type) {
655 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
663 path.erase(path.begin()); // pop the gate - we're here already!
664 taxiState = TD_OUTBOUND;
665 taxiRequestPending = false;
666 holdShortNode = (node*)(*(path.begin() + path.size()));
668 } else if(!taxiRequestPending) {
669 //cout << "(" << flush;
670 // Do some communication
671 // airport name + tower + airplane callsign + location + request taxi for + operation type + ?
673 trns += tower->get_name();
675 trns += plane.callsign;
676 trns += " on apron parking request taxi for traffic pattern";
677 //cout << "trns = " << trns << endl;
678 pending_transmission = trns;
680 taxiRequestCleared = false;
681 taxiRequestPending = true;
685 //cout << "!" << flush;
687 // Maybe the below should be set when we get to the threshold and prepare for TO?
688 // FIXME TODO - pattern direction is still hardwired
689 patternDirection = -1; // Left
690 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
691 if(rwy.rwyID.size() == 3) {
692 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
696 //cout << ")" << flush;
701 //cout << "I " << flush;
703 //cout << "Update _pos = " << _pos << ", vis = " << _aip.getVisible() << '\n';
705 // Convienience output for AI debugging using the property logger
706 //fgSetDouble("/AI/Local1/ortho-x", (ortho.ConvertToLocal(_pos)).x());
707 //fgSetDouble("/AI/Local1/ortho-y", (ortho.ConvertToLocal(_pos)).y());
708 //fgSetDouble("/AI/Local1/elev", _pos.elev() * SG_METER_TO_FEET);
710 // And finally, call parent.
711 FGAIPlane::Update(dt);
714 void FGAILocalTraffic::RegisterTransmission(int code) {
716 case 1: // taxi request cleared
717 taxiRequestCleared = true;
718 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to taxi...");
720 case 2: // contact tower
723 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact tower...");
725 case 3: // Cleared to line up
727 clearedToLineUp = true;
728 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to line-up...");
730 case 4: // cleared to take-off
732 clearedToTakeOff = true;
733 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to take-off...");
735 case 5: // contact ground
737 contactGround = true;
738 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact ground...");
740 // case 6 is a temporary mega-hack for controlled airports without separate ground control
741 case 6: // taxi to the GA parking
744 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to taxi to the GA parking...");
746 case 7: // Cleared to land (also implies cleared for the option
747 _clearedToLand = true;
748 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to land...");
750 case 13: // Go around!
753 _clearedToLand = false;
754 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to go-around!!");
761 // Fly a traffic pattern
762 // FIXME - far too much of the mechanics of turning, rolling, accellerating, descending etc is in here.
763 // Move it out to FGAIPlane and have FlyTrafficPattern just specify what to do, not the implementation.
764 void FGAILocalTraffic::FlyTrafficPattern(double dt) {
765 // Need to differentiate between in-air (IAS governed) and on-ground (vel governed)
766 // Take-off is an interesting case - we are on the ground but takeoff speed is IAS governed.
769 // Wind has two effects - a mechanical one in that IAS translates to a different vel, and the hdg != track,
770 // but also a piloting effect, in that the AI must be able to descend at a different rate in order to hit the threshold.
772 //cout << "dt = " << dt << '\n';
774 // ack - I can't remember how long a rate 1 turn is meant to take.
775 double turn_time = 60.0; // seconds - TODO - check this guess
776 double turn_circumference;
778 Point3D orthopos = ortho.ConvertToLocal(_pos); // ortho position of the plane
779 //cout << "runway elev = " << rwy.threshold_pos.elev() << ' ' << rwy.threshold_pos.elev() * SG_METER_TO_FEET << '\n';
780 //cout << "elev = " << _pos.elev() << ' ' << _pos.elev() * SG_METER_TO_FEET << '\n';
782 // HACK FOR TESTING - REMOVE
783 //cout << "Calling ExitRunway..." << endl;
784 //ExitRunway(orthopos);
789 double wind_from = wind_from_hdg->getDoubleValue();
790 double wind_speed = wind_speed_knots->getDoubleValue();
802 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
803 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
805 IAS = vel + (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
808 SetTrack(rwy.hdg); // Hands over control of turning to AIPlane
810 IAS = best_rate_of_climb_speed;
812 slope = 6.0; // Reduced it slightly since it's climbing a lot steeper than I can in the JSBSim C172.
817 // Turn to crosswind if above 700ft AND if other traffic allows
818 // (decided in FGTower and accessed through GetCrosswindConstraint(...)).
819 // According to AIM, traffic should climb to within 300ft of pattern altitude before commencing crosswind turn.
820 // TODO - At hot 'n high airports this may be 500ft AGL though - need to make this a variable.
821 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 700) {
823 if(tower->GetCrosswindConstraint(cc)) {
824 if(orthopos.y() > cc) {
825 //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
828 } else if(orthopos.y() > 1500.0) { // Added this constraint as a hack to prevent turning too early when going around.
829 // TODO - We should be doing it as a distance from takeoff end, not theshold end though.
830 //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
834 // Need to check for levelling off in case we can't turn crosswind as soon
835 // as we would like due to other traffic.
836 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
839 IAS = 80.0; // FIXME - use smooth transistion to new speed and attitude.
841 if(goAround && !goAroundCalled) {
842 if(responseCounter > 5.5) {
843 pending_transmission = plane.callsign;
844 pending_transmission += " going around";
846 goAroundCalled = true;
851 SetTrack(rwy.hdg + (90.0 * patternDirection));
852 if((track < (rwy.hdg - 89.0)) || (track > (rwy.hdg + 89.0))) {
858 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
861 IAS = 80.0; // FIXME - use smooth transistion to new speed
863 // turn 1000m out for now, taking other traffic into accout
864 if(fabs(orthopos.x()) > 900) {
866 if(tower->GetDownwindConstraint(dd)) {
867 if(fabs(orthopos.x()) > fabs(dd)) {
868 //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n';
872 //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n';
878 SetTrack(rwy.hdg - (180 * patternDirection));
879 // just in case we didn't make height on crosswind
880 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
883 IAS = 80.0; // FIXME - use smooth transistion to new speed
885 if((track < (rwy.hdg - 179.0)) || (track > (rwy.hdg + 179.0))) {
891 // just in case we didn't make height on crosswind
892 if(((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 995) && ((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET < 1015)) {
895 IAS = 90.0; // FIXME - use smooth transistion to new speed
897 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET >= 1015) {
900 IAS = 90.0; // FIXME - use smooth transistion to new speed
902 if((orthopos.y() < 0) && (!transmitted)) {
903 TransmitPatternPositionReport();
906 if((orthopos.y() < -100) && (!descending)) {
907 //cout << "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDdddd\n";
908 // Maybe we should think about when to start descending.
909 // For now we're assuming that we aim to follow the same glidepath regardless of wind.
912 CalculateSoD((tower->GetBaseConstraint(d1) ? d1 : -1000.0), (tower->GetDownwindConstraint(d2) ? d2 : 1000.0 * patternDirection), (patternDirection ? true : false));
913 if(SoD.leg == DOWNWIND) {
914 descending = (orthopos.y() < SoD.y ? true : false);
919 slope = -5.5; // FIXME - calculate to descent at 500fpm and hit the desired point on the runway (taking wind into account as well!!)
924 // Try and arrange to turn nicely onto base
925 turn_circumference = IAS * 0.514444 * turn_time;
926 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
927 //We'll leave it as a hack with IAS for now but it needs revisiting.
928 turn_radius = turn_circumference / (2.0 * DCL_PI);
929 if(orthopos.y() < -1000.0 + turn_radius) {
930 //if(orthopos.y() < -980) {
932 if(tower->GetBaseConstraint(bb)) {
933 if(fabs(orthopos.y()) > fabs(bb)) {
934 //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n';
940 //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n';
948 SetTrack(rwy.hdg - (90 * patternDirection));
949 if(fabs(rwy.hdg - track) < 91.0) {
955 // Base report should only be transmitted at uncontrolled airport - not towered.
956 if(!_controlled) TransmitPatternPositionReport();
962 // Make downwind leg position artifically large to avoid any chance of SoD being returned as
963 // on downwind when we are already on base.
964 CalculateSoD((tower->GetBaseConstraint(d1) ? d1 : -1000.0), (10000.0 * patternDirection), (patternDirection ? true : false));
965 if(SoD.leg == BASE) {
966 descending = (fabs(orthopos.y()) < fabs(SoD.y) ? true : false);
971 slope = -5.5; // FIXME - calculate to descent at 500fpm and hit the threshold (taking wind into account as well!!)
976 // Try and arrange to turn nicely onto final
977 turn_circumference = IAS * 0.514444 * turn_time;
978 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
979 //We'll leave it as a hack with IAS for now but it needs revisiting.
980 turn_radius = turn_circumference / (2.0 * DCL_PI);
981 if(fabs(orthopos.x()) < (turn_radius + 50)) {
989 if(fabs(track - rwy.hdg) < 0.6) {
991 vel = nominal_final_speed;
995 if(goAround && responseCounter > 2.0) {
998 IAS = best_rate_of_climb_speed;
999 slope = 5.0; // A bit less steep than the initial climbout.
1001 goAroundCalled = false;
1007 if((!_controlled) || (!_clearedToLand)) TransmitPatternPositionReport();
1011 // Make base leg position artifically large to avoid any chance of SoD being returned as
1012 // on base or downwind when we are already on final.
1013 CalculateSoD(-10000.0, (1000.0 * patternDirection), (patternDirection ? true : false));
1014 if(SoD.leg == FINAL) {
1015 descending = (fabs(orthopos.y()) < fabs(SoD.y) ? true : false);
1020 if(orthopos.y() < -50.0) {
1021 double thesh_offset = 30.0;
1022 slope = atan((_pos.elev() - dclGetAirportElev(airportID)) / (orthopos.y() - thesh_offset)) * DCL_RADIANS_TO_DEGREES;
1023 //cout << "slope = " << slope << ", elev = " << _pos.elev() << ", apt_elev = " << dclGetAirportElev(airportID) << ", op.y = " << orthopos.y() << '\n';
1024 if(slope < -10.0) slope = -10.0;
1025 _savedSlope = slope;
1029 if(_pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) {
1030 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
1031 if(_pos.elev() < (_aip.getSGLocation()->get_cur_elev_m() + wheelOffset + 1.0)) {
1035 } else if(_pos.elev() < (_aip.getSGLocation()->get_cur_elev_m() + wheelOffset + 5.0)) {
1040 slope = _savedSlope;
1045 // Elev not determined
1046 slope = _savedSlope;
1051 slope = _savedSlope;
1057 // Try and track the extended centreline
1058 SetTrack(rwy.hdg - (0.2 * orthopos.x()));
1059 //cout << "orthopos.x() = " << orthopos.x() << " hdg = " << hdg << '\n';
1060 if(_pos.elev() < (rwy.threshold_pos.elev()+20.0+wheelOffset)) {
1061 DoGroundElev(); // Need to call it here expicitly on final since it's only called
1062 // for us in update(...) when the inAir flag is false.
1064 if(_pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) {
1067 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
1068 if((_aip.getSGLocation()->get_cur_elev_m() + wheelOffset) > _pos.elev()) {
1074 ClearTrack(); // Take over explicit track handling since AIPlane currently always banks when changing course
1076 } // else need a fallback position based on arpt elev in case ground elev determination fails?
1084 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
1085 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
1090 // FIXME - differentiate between touch and go and full stops
1092 //cout << "Vel <= 15.0, circuitsToFly = " << circuitsToFly << endl;
1093 if(circuitsToFly <= 0) {
1094 //cout << "Calling ExitRunway..." << endl;
1095 ExitRunway(orthopos);
1098 //cout << "Taking off again..." << endl;
1109 // FIXME - at the moment this is a bit screwy
1110 // The velocity correction is applied based on the relative headings.
1111 // Then the heading is changed based on the velocity.
1112 // Which comes first, the chicken or the egg?
1113 // Does it really matter?
1115 // Apply wind to ground-relative velocity if in the air
1116 vel = IAS - (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
1117 //crab = f(track, wind, vel);
1118 // The vector we need to fly is our desired vector minus the wind vector
1119 // TODO - we probably ought to use plib's built in vector types and operations for this
1120 // ie. There's almost *certainly* a better way to do this!
1121 double gxx = vel * sin(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity x component wrt ground
1122 double gyy = vel * cos(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity y component wrt ground
1123 double wxx = wind_speed * sin((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS); // Wind velocity x component
1124 double wyy = wind_speed * cos((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS); // Wind velocity y component
1125 double axx = gxx - wxx; // Plane in-air velocity x component
1126 double ayy = gyy - wyy; // Plane in-air velocity y component
1127 // Now we want the angle between gxx and axx (which is the crab)
1128 double maga = sqrt(axx*axx + ayy*ayy);
1129 double magg = sqrt(gxx*gxx + gyy*gyy);
1130 crab = acos((axx*gxx + ayy*gyy) / (maga * magg));
1131 // At this point this works except we're getting the modulus of the angle
1132 //cout << "crab = " << crab << '\n';
1134 // Make sure both headings are in the 0->360 circle in order to get sane differences
1135 dclBoundHeading(wind_from);
1136 dclBoundHeading(track);
1137 if(track > wind_from) {
1138 if((track - wind_from) <= 180) {
1142 if((wind_from - track) >= 180) {
1146 } else { // on the ground - crab dosen't apply
1150 //cout << "X " << orthopos.x() << " Y " << orthopos.y() << " SLOPE " << slope << " elev " << _pos.elev() * SG_METER_TO_FEET << '\n';
1152 _hdg = track + crab;
1153 dist = vel * 0.514444 * dt;
1154 _pos = dclUpdatePosition(_pos, track, slope, dist);
1157 // Pattern direction is true for right, false for left
1158 void FGAILocalTraffic::CalculateSoD(double base_leg_pos, double downwind_leg_pos, bool pattern_direction) {
1159 // For now we'll ignore wind and hardwire the glide angle.
1160 double ga = 5.5; //degrees
1161 double pa = 1000.0 * SG_FEET_TO_METER; // pattern altitude in meters
1162 // FIXME - get glideslope angle and pattern altitude agl from airport details if available
1164 // For convienience, we'll have +ve versions of the input distances
1165 double blp = fabs(base_leg_pos);
1166 double dlp = fabs(downwind_leg_pos);
1168 //double turn_allowance = 150.0; // Approximate distance in meters that a 90deg corner is shortened by turned in a light plane.
1170 double stod = pa / tan(ga * DCL_DEGREES_TO_RADIANS); // distance in meters from touchdown point to start descent
1171 //cout << "Descent to start = " << stod << " meters out\n";
1172 if(stod < blp) { // Start descending on final
1174 SoD.y = stod * -1.0;
1176 } else if(stod < (blp + dlp)) { // Start descending on base leg
1179 SoD.x = (pattern_direction ? (stod - dlp) : (stod - dlp) * -1.0);
1180 } else { // Start descending on downwind leg
1182 SoD.x = (pattern_direction ? dlp : dlp * -1.0);
1183 SoD.y = (blp - (stod - (blp + dlp))) * -1.0;
1187 void FGAILocalTraffic::TransmitPatternPositionReport(void) {
1188 // airport name + "traffic" + airplane callsign + pattern direction + pattern leg + rwy + ?
1192 trns += tower->get_name();
1193 trns += " Traffic ";
1194 trns += plane.callsign;
1195 if(patternDirection == 1) {
1201 // We could probably get rid of this whole switch statement and just pass a string containing the leg from the FlyPattern function.
1202 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?
1204 // Fall through to CROSSWIND
1205 case CROSSWIND: // I don't think this case will be used here but it can't hurt to leave it in
1206 trns += "crosswind ";
1209 // Fall through to DOWNWIND
1211 trns += "downwind ";
1215 // Fall through to BASE
1220 // Fall through to FINAL
1221 case FINAL: // maybe this should include long/short final if appropriate?
1225 default: // Hopefully this won't be used
1229 trns += ConvertRwyNumToSpokenString(rwy.rwyID);
1233 // And add the airport name again
1234 trns += tower->get_name();
1236 pending_transmission = trns;
1237 ConditionalTransmit(60.0, code); // Assume a report of this leg will be invalid if we can't transmit within a minute.
1241 // TODO - Really should enumerate these coded values.
1242 void FGAILocalTraffic::ProcessCallback(int code) {
1243 // 1 - Request Departure from ground
1244 // 2 - Report at hold short
1245 // 3 - Report runway vacated
1246 // 10 - report crosswind
1247 // 11 - report downwind
1249 // 13 - report final
1251 ground->RequestDeparture(plane, this);
1252 } else if(code == 2) {
1253 tower->ContactAtHoldShort(plane, this, CIRCUIT);
1254 } else if(code == 3) {
1255 tower->ReportRunwayVacated(plane.callsign);
1256 } else if(code == 11) {
1257 tower->ReportDownwind(plane.callsign);
1258 } else if(code == 13) {
1259 tower->ReportFinal(plane.callsign);
1263 void FGAILocalTraffic::ExitRunway(Point3D orthopos) {
1264 //cout << "In ExitRunway" << endl;
1265 //cout << "Runway ID is " << rwy.ID << endl;
1267 _clearedToLand = false;
1269 node_array_type exitNodes = ground->GetExits(rwy.rwyID); //I suppose we ought to have some fallback for rwy with no defined exits?
1271 cout << "Node ID's of exits are ";
1272 for(unsigned int i=0; i<exitNodes.size(); ++i) {
1273 cout << exitNodes[i]->nodeID << ' ';
1277 if(exitNodes.size()) {
1278 //Find the next exit from orthopos.y
1280 double dist = 100000; //ie. longer than any runway in existance
1281 double backdist = 100000;
1282 node_array_iterator nItr = exitNodes.begin();
1283 node* rwyExit = *(exitNodes.begin());
1284 //int gateID; //This might want to be more persistant at some point
1285 while(nItr != exitNodes.end()) {
1286 d = ortho.ConvertToLocal((*nItr)->pos).y() - ortho.ConvertToLocal(_pos).y(); //FIXME - consider making orthopos a class variable
1293 if(fabs(d) < backdist) {
1295 //TODO - need some logic here that if we don't get a forward exit we turn round and store the backwards one
1300 ourGate = ground->GetGateNode();
1301 if(ourGate == NULL) {
1302 // Implies no available gates - what shall we do?
1303 // For now just vanish the plane - possibly we can make this more elegant in the future
1304 SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst landing at " << airportID << '\n');
1305 //_aip.setVisible(false);
1306 //cout << "Setting visible false\n";
1307 operatingState = PARKED;
1310 path = ground->GetPath(rwyExit, ourGate);
1312 cout << "path returned was:" << endl;
1313 for(unsigned int i=0; i<path.size(); ++i) {
1314 switch(path[i]->struct_type) {
1316 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
1324 taxiState = TD_INBOUND;
1327 // Something must have gone wrong with the ground network file - or there is only a rwy here and no exits defined
1328 SG_LOG(SG_ATC, SG_INFO, "No exits found by FGAILocalTraffic from runway " << rwy.rwyID << " at " << airportID << '\n');
1329 //cout << "No exits found by " << plane.callsign << " from runway " << rwy.rwyID << " at " << airportID << '\n';
1330 // What shall we do - just remove the plane from sight?
1331 _aip.setVisible(false);
1332 //cout << "Setting visible false\n";
1333 //tower->ReportRunwayVacated(plane.callsign);
1334 string trns = "Clear of the runway ";
1335 trns += plane.callsign;
1336 pending_transmission = trns;
1338 operatingState = PARKED;
1342 // Set the class variable nextTaxiNode to the next node in the path
1343 // and update taxiPathPos, the class variable path iterator position
1344 // TODO - maybe should return error codes to the calling function if we fail here
1345 void FGAILocalTraffic::GetNextTaxiNode() {
1346 //cout << "GetNextTaxiNode called " << endl;
1347 //cout << "taxiPathPos = " << taxiPathPos << endl;
1348 ground_network_path_iterator pathItr = path.begin() + taxiPathPos;
1349 if(pathItr == path.end()) {
1350 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - no more nodes in path\n");
1352 if((*pathItr)->struct_type == NODE) {
1353 //cout << "ITS A NODE" << endl;
1354 //*pathItr = new node;
1355 nextTaxiNode = (node*)*pathItr;
1359 //cout << "ITS NOT A NODE" << endl;
1360 //The first item in found must have been an arc
1361 //Assume for now that it was straight
1364 if(pathItr == path.end()) {
1365 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - path ended with an arc\n");
1366 } else if((*pathItr)->struct_type == NODE) {
1367 nextTaxiNode = (node*)*pathItr;
1370 //OOPS - two non-nodes in a row - that shouldn't happen ATM
1371 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - two non-nodes in sequence\n");
1377 // StartTaxi - set up the taxiing state - call only at the start of taxiing
1378 void FGAILocalTraffic::StartTaxi() {
1379 //cout << "StartTaxi called" << endl;
1380 operatingState = TAXIING;
1384 //Set the desired heading
1385 //Assume we are aiming for first node on path
1386 //Eventually we may need to consider the fact that we might start on a curved arc and
1387 //not be able to head directly for the first node.
1388 GetNextTaxiNode(); // sets the class variable nextTaxiNode to the next taxi node!
1389 desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos);
1390 //cout << "First taxi heading is " << desiredTaxiHeading << endl;
1393 // speed in knots, headings in degrees, radius in meters.
1394 static double TaxiTurnTowardsHeading(double current_hdg, double desired_hdg, double speed, double radius, double dt) {
1395 // wrap heading - this prevents a logic bug where the plane would just go round in circles!!
1396 while(current_hdg < 0.0) {
1397 current_hdg += 360.0;
1399 while(current_hdg > 360.0) {
1400 current_hdg -= 360.0;
1402 if(fabs(current_hdg - desired_hdg) > 0.1) {
1403 // Which is the quickest direction to turn onto heading?
1404 if(desired_hdg > current_hdg) {
1405 if((desired_hdg - current_hdg) <= 180) {
1407 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1408 // TODO - check that increments are less than the delta that we check for the right direction
1409 // Probably need to reduce convergence speed as convergence is reached
1411 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1414 if((current_hdg - desired_hdg) <= 180) {
1416 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1417 // TODO - check that increments are less than the delta that we check for the right direction
1418 // Probably need to reduce convergence speed as convergence is reached
1420 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1424 return(current_hdg);
1427 void FGAILocalTraffic::Taxi(double dt) {
1428 //cout << "Taxi called" << endl;
1429 // Logic - if we are further away from next point than turn radius then head for it
1430 // If we have reached turning point then get next point and turn onto that heading
1431 // Look out for the finish!!
1433 //Point3D orthopos = ortho.ConvertToLocal(pos); // ortho position of the plane
1434 desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos);
1436 bool lastNode = (taxiPathPos == path.size() ? true : false);
1438 //cout << "LAST NODE\n";
1441 // HACK ALERT! - for now we will taxi at constant speed for straights and turns
1443 // Remember that hdg is always equal to track when taxiing so we don't have to consider them both
1444 double dist_to_go = dclGetHorizontalSeparation(_pos, nextTaxiNode->pos); // we may be able to do this more cheaply using orthopos
1445 //cout << "dist_to_go = " << dist_to_go << endl;
1446 if((nextTaxiNode->type == GATE) && (dist_to_go <= 0.1)) {
1447 // This might be more robust to outward paths starting with a gate if we check for either
1448 // last node or TD_INBOUND ?
1450 operatingState = PARKED;
1451 } else if(((dist_to_go > taxiTurnRadius) || (nextTaxiNode->type == GATE)) && (!liningUp)){
1452 // if the turn radius is r, and speed is s, then in a time dt we turn through
1453 // ((s.dt)/(PI.r)) x 180 degrees
1454 // or alternatively (s.dt)/r radians
1455 //cout << "hdg = " << hdg << " desired taxi heading = " << desiredTaxiHeading << '\n';
1456 _hdg = TaxiTurnTowardsHeading(_hdg, desiredTaxiHeading, nominalTaxiSpeed, taxiTurnRadius, dt);
1457 double vel = nominalTaxiSpeed;
1458 //cout << "vel = " << vel << endl;
1459 double dist = vel * 0.514444 * dt;
1460 //cout << "dist = " << dist << endl;
1461 double track = _hdg;
1462 //cout << "track = " << track << endl;
1464 _pos = dclUpdatePosition(_pos, track, slope, dist);
1465 //cout << "Updated position...\n";
1466 if(_aip.getSGLocation()->get_cur_elev_m() > -9990) {
1467 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
1468 } // else don't change the elev until we get a valid ground elev again!
1469 } else if(lastNode) {
1470 if(taxiState == TD_LINING_UP) {
1471 if((!liningUp) && (dist_to_go <= taxiTurnRadius)) {
1475 _hdg = TaxiTurnTowardsHeading(_hdg, rwy.hdg, nominalTaxiSpeed, taxiTurnRadius, dt);
1476 double vel = nominalTaxiSpeed;
1477 //cout << "vel = " << vel << endl;
1478 double dist = vel * 0.514444 * dt;
1479 //cout << "dist = " << dist << endl;
1480 double track = _hdg;
1481 //cout << "track = " << track << endl;
1483 _pos = dclUpdatePosition(_pos, track, slope, dist);
1484 //cout << "Updated position...\n";
1485 if(_aip.getSGLocation()->get_cur_elev_m() > -9990) {
1486 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
1487 } // else don't change the elev until we get a valid ground elev again!
1488 if(fabs(_hdg - rwy.hdg) <= 1.0) {
1489 operatingState = IN_PATTERN;
1495 } else if(taxiState == TD_OUTBOUND) {
1496 // Pause awaiting further instructions
1497 // and for now assume we've reached the hold-short node
1498 holdingShort = true;
1499 } // else at the moment assume TD_INBOUND always ends in a gate in which case we can ignore it
1501 // Time to turn (we've already checked it's not the end we're heading for).
1502 // set the target node to be the next node which will prompt automatically turning onto
1503 // the right heading in the stuff above, with the usual provisos applied.
1505 // For now why not just recursively call this function?
1511 // Warning - ground elev determination is CPU intensive
1512 // Either this function or the logic of how often it is called
1513 // will almost certainly change.
1514 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 // Only do the proper hitlist stuff if we are within visible range of the viewer.
1522 double visibility_meters = fgGetDouble("/environment/visibility-m");
1523 FGViewer* vw = globals->get_current_view();
1524 if(dclGetHorizontalSeparation(_pos, Point3D(vw->getLongitude_deg(), vw->getLatitude_deg(), 0.0)) > visibility_meters) {
1525 _aip.getSGLocation()->set_cur_elev_m(aptElev);
1530 //globals->get_tile_mgr()->prep_ssg_nodes( acmodel_location,
1531 globals->get_tile_mgr()->prep_ssg_nodes( _aip.getSGLocation(), visibility_meters );
1532 Point3D scenery_center = globals->get_scenery()->get_center();
1533 globals->get_tile_mgr()->update( _aip.getSGLocation(), visibility_meters, (_aip.getSGLocation())->get_absolute_view_pos( scenery_center ) );
1534 // save results of update in SGLocation for fdm...
1536 //if ( globals->get_scenery()->get_cur_elev() > -9990 ) {
1537 // acmodel_location->
1538 // set_cur_elev_m( globals->get_scenery()->get_cur_elev() );
1541 // The need for this here means that at least 2 consecutive passes are needed :-(
1542 _aip.getSGLocation()->set_tile_center( globals->get_scenery()->get_next_center() );
1544 //cout << "Transform Elev is " << globals->get_scenery()->get_cur_elev() << '\n';
1545 _aip.getSGLocation()->set_cur_elev_m(globals->get_scenery()->get_cur_elev());
1546 //return(globals->get_scenery()->get_cur_elev());