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;
113 targetDescentRate = 0.0;
115 goAroundCalled = false;
127 FGAILocalTraffic::~FGAILocalTraffic() {
131 void FGAILocalTraffic::GetAirportDetails(string id) {
133 if(ATC->GetAirportATCDetails(airportID, &a)) {
134 if(a.tower_freq) { // Has a tower - TODO - check the opening hours!!!
135 tower = (FGTower*)ATC->GetATCPointer(airportID, TOWER);
137 // Something has gone wrong - abort or carry on with un-towered operation?
138 SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a tower pointer from tower control for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
144 ground = tower->GetGroundPtr();
146 // Something has gone wrong :-(
147 SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a ground pointer from tower control in FGAILocalTraffic::GetAirportDetails() :-(");
152 // TODO - Check CTAF, unicom etc
155 SG_LOG(SG_ATC, SG_ALERT, "Unable to find airport details in for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
158 // Get the airport elevation
159 aptElev = dclGetAirportElev(airportID.c_str());
160 //cout << "Airport elev in AILocalTraffic = " << aptElev << '\n';
161 // WARNING - we use this elev for the whole airport - some assumptions in the code
162 // might fall down with very slopey airports.
165 // Get details of the active runway
166 // It is assumed that by the time this is called the tower control and airport code will have been set up.
167 void FGAILocalTraffic::GetRwyDetails(string id) {
168 //cout << "GetRwyDetails called" << endl;
171 rwy.rwyID = tower->GetActiveRunway();
173 // TODO - get a proper runway ID from uncontrolled airports
177 // Now we need to get the threshold position and rwy heading
180 bool rwyGood = globals->get_runways()->search(id, rwy.rwyID, &runway);
182 double hdg = runway._heading;
183 double other_way = hdg - 180.0;
184 while(other_way <= 0.0) {
188 // move to the +l end/center of the runway
189 //cout << "Runway center is at " << runway._lon << ", " << runway._lat << '\n';
190 Point3D origin = Point3D(runway._lon, runway._lat, aptElev);
191 Point3D ref = origin;
192 double tshlon, tshlat, tshr;
193 double tolon, tolat, tor;
194 rwy.length = runway._length * SG_FEET_TO_METER;
195 rwy.width = runway._width * SG_FEET_TO_METER;
196 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), other_way,
197 rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
198 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), hdg,
199 rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor );
200 // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user.
201 // now copy what we need out of runway into rwy
202 rwy.threshold_pos = Point3D(tshlon, tshlat, aptElev);
203 Point3D takeoff_end = Point3D(tolon, tolat, aptElev);
204 //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n';
205 //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
207 // Set the projection for the local area
208 //cout << "Initing ortho for airport " << id << '\n';
209 ortho.Init(rwy.threshold_pos, rwy.hdg);
210 rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos); // should come out as zero
211 rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
213 SG_LOG(SG_ATC, SG_ALERT, "Help - can't get good runway at airport " << id << " in FGAILocalTraffic!!");
219 There are two possible scenarios during initialisation:
220 The first is that the user is flying towards the airport, and hence the traffic
221 could be initialised anywhere, as long as the AI planes are consistent with
223 The second is that the user has started the sim at or close to the airport, and
224 hence the traffic must be initialised with respect to the user as well as each other.
225 To a certain extent it's FGAIMgr that has to worry about this, but we need to provide
226 sufficient initialisation functionality within the plane classes to allow the manager
227 to initially position them where and how required.
229 bool FGAILocalTraffic::Init(const string& callsign, string ICAO, OperatingState initialState, PatternLeg initialLeg) {
230 //cout << "FGAILocalTraffic.Init(...) called" << endl;
233 plane.callsign = callsign;
235 if(initialState == EN_ROUTE) return(true);
237 // Get the ATC pointers and airport elev
238 GetAirportDetails(airportID);
240 // Get the active runway details (and copy them into rwy)
241 GetRwyDetails(airportID);
242 //cout << "Runway is " << rwy.rwyID << '\n';
244 // FIXME TODO - pattern direction is still hardwired
245 patternDirection = -1; // Left
246 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
247 if(rwy.rwyID.size() == 3) {
248 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
252 if((initialState == PARKED) || (initialState == TAXIING)) {
253 freq = (double)ground->get_freq() / 100.0;
255 freq = (double)tower->get_freq() / 100.0;
259 // TODO - find the proper freq if CTAF or unicom or after-hours.
262 //cout << "In Init(), initialState = " << initialState << endl;
263 operatingState = initialState;
265 switch(operatingState) {
267 tuned_station = ground;
268 ourGate = ground->GetGateNode();
269 if(ourGate == NULL) {
270 // Implies no available gates - what shall we do?
271 // For now just vanish the plane - possibly we can make this more elegant in the future
272 SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst attempting Init at " << airportID << '\n');
280 _pos.setelev(aptElev);
281 _hdg = ourGate->heading;
284 // Now we've set the position we can do the ground elev
285 elevInitGood = false;
291 //tuned_station = ground;
292 // FIXME - implement this case properly
293 // For now we'll assume that the plane should start at the hold short in this case
294 // and that we're working without ground network elements. Ie. an airport with no facility file.
296 tuned_station = tower;
298 tuned_station = NULL;
301 // Set a position and orientation in an approximate place for hold short.
302 //cout << "rwy.width = " << rwy.width << '\n';
303 orthopos = Point3D((rwy.width / 2.0 + 10.0) * -1.0, 0.0, 0.0);
304 // TODO - set the x pos to be +ve if a RH parallel rwy.
305 _pos = ortho.ConvertFromLocal(orthopos);
306 _pos.setelev(aptElev);
307 _hdg = rwy.hdg + 90.0;
308 // TODO - reset the heading if RH rwy.
313 elevInitGood = false;
319 responseCounter = 0.0;
320 contactTower = false;
323 clearedToLineUp = false;
324 changeFreqType = TOWER;
328 // For now we'll always start the in_pattern case on the threshold ready to take-off
329 // since we've got the implementation for this case already.
330 // TODO - implement proper generic in_pattern startup.
332 // 18/10/03 - adding the ability to start on downwind (mainly to speed testing of the go-around code!!)
334 //cout << "Starting in pattern...\n";
337 tuned_station = tower;
339 tuned_station = NULL;
342 circuitsToFly = 0; // ie just fly this circuit and then stop
345 if(initialLeg == DOWNWIND) {
346 _pos = ortho.ConvertFromLocal(Point3D(1000*patternDirection, 800, 0.0));
347 _pos.setelev(rwy.threshold_pos.elev() + 1000 * SG_FEET_TO_METER);
348 _hdg = rwy.hdg + 180.0;
350 elevInitGood = false;
352 SetTrack(rwy.hdg - (180 * patternDirection));
358 _aip.setVisible(true);
360 tower->RegisterAIPlane(plane, this, CIRCUIT, DOWNWIND);
364 // Default to initial position on threshold for now
365 _pos.setlat(rwy.threshold_pos.lat());
366 _pos.setlon(rwy.threshold_pos.lon());
367 _pos.setelev(rwy.threshold_pos.elev());
370 // Now we've set the position we can do the ground elev
371 // This might not always be necessary if we implement in-air start
372 elevInitGood = false;
385 operatingState = IN_PATTERN;
389 // This implies we're being init'd by AIGAVFRTraffic - simple return now
392 SG_LOG(SG_ATC, SG_ALERT, "Attempt to set unknown operating state in FGAILocalTraffic.Init(...)\n");
401 // Set up downwind state - this is designed to be called from derived classes who are already tuned to tower
402 void FGAILocalTraffic::DownwindEntry() {
403 circuitsToFly = 0; // ie just fly this circuit and then stop
405 operatingState = IN_PATTERN;
407 elevInitGood = false;
409 SetTrack(rwy.hdg - (180 * patternDirection));
417 void FGAILocalTraffic::StraightInEntry(bool des) {
418 //cout << "************ STRAIGHT-IN ********************\n";
419 circuitsToFly = 0; // ie just fly this circuit and then stop
421 operatingState = IN_PATTERN;
423 elevInitGood = false;
426 transmitted = true; // TODO - fix this hack.
427 // TODO - set up the next 5 properly for a descent!
436 // Return what type of landing we're doing on this circuit
437 LandingType FGAILocalTraffic::GetLandingOption() {
438 //cout << "circuitsToFly = " << circuitsToFly << '\n';
440 return(touchAndGo ? TOUCH_AND_GO : STOP_AND_GO);
447 // Commands to do something from higher level logic
448 void FGAILocalTraffic::FlyCircuits(int numCircuits, bool tag) {
449 //cout << "FlyCircuits called" << endl;
451 switch(operatingState) {
453 circuitsToFly += numCircuits;
457 // HACK - assume that we're taxiing out for now
458 circuitsToFly += numCircuits;
462 circuitsToFly = numCircuits; // Note that one too many circuits gets flown because we only test and decrement circuitsToFly after landing
463 // thus flying one too many circuits. TODO - Need to sort this out better!
471 // Run the internal calculations
472 void FGAILocalTraffic::Update(double dt) {
473 //cout << "U" << flush;
475 // we shouldn't really need this since there's a LOD of 10K on the whole plane anyway I think.
476 // At the moment though I need to to avoid DList overflows - the whole plane LOD obviously isn't getting picked up.
478 if(dclGetHorizontalSeparation(_pos, Point3D(fgGetDouble("/position/longitude-deg"), fgGetDouble("/position/latitude-deg"), 0.0)) > 8000) _aip.setVisible(false);
479 else _aip.setVisible(true);
481 _aip.setVisible(false);
484 //double responseTime = 10.0; // seconds - this should get more sophisticated at some point
485 responseCounter += dt;
486 if((contactTower) && (responseCounter >= 8.0)) {
487 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
488 string trns = "Tower ";
489 double f = globals->get_ATC_mgr()->GetFrequency(airportID, TOWER) / 100.0;
491 sprintf(buf, "%.2f", f);
494 trns += plane.callsign;
495 pending_transmission = trns;
496 ConditionalTransmit(30.0);
497 responseCounter = 0.0;
498 contactTower = false;
500 changeFreqType = TOWER;
503 if((contactGround) && (responseCounter >= 8.0)) {
504 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
505 string trns = "Ground ";
506 double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;
508 sprintf(buf, "%.2f", f);
512 pending_transmission = trns;
513 ConditionalTransmit(5.0);
514 responseCounter = 0.0;
515 contactGround = false;
517 changeFreqType = GROUND;
520 if((_taxiToGA) && (responseCounter >= 8.0)) {
521 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
522 string trns = "GA Parking, Thank you and Good Day";
523 //double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;
524 pending_transmission = trns;
525 ConditionalTransmit(5.0, 99);
528 tower->DeregisterAIPlane(plane.callsign);
530 // NOTE - we can't delete this instance yet since then the frequency won't get release when the message display finishes.
533 if((_removeSelf) && (responseCounter >= 8.0)) {
535 // MEGA HACK - check if we are at a simple airport or not first instead of simply hardwiring KEMT as the only non-simple airport.
536 // TODO FIXME TODO FIXME !!!!!!!
537 if(airportID != "KEMT") globals->get_AI_mgr()->ScheduleRemoval(plane.callsign);
540 if((changeFreq) && (responseCounter > 8.0)) {
541 switch(changeFreqType) {
544 SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to tower in FGAILocalTraffic, but tower is NULL!!!");
547 tuned_station = tower;
548 freq = (double)tower->get_freq() / 100.0;
550 // Contact the tower, even if only virtually
551 pending_transmission = plane.callsign;
552 pending_transmission += " at hold short for runway ";
553 pending_transmission += ConvertRwyNumToSpokenString(rwy.rwyID);
554 pending_transmission += " traffic pattern ";
556 pending_transmission += ConvertNumToSpokenDigits(circuitsToFly + 1);
557 pending_transmission += " circuits touch and go";
559 pending_transmission += " one circuit to full stop";
565 SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to ground in FGAILocalTraffic, but tower is NULL!!!");
569 SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to ground in FGAILocalTraffic, but ground is NULL!!!");
572 tower->DeregisterAIPlane(plane.callsign);
573 tuned_station = ground;
574 freq = (double)ground->get_freq() / 100.0;
576 // And to avoid compiler warnings...
577 case APPROACH: break;
580 case DEPARTURE: break;
586 //cout << "," << flush;
588 switch(operatingState) {
590 //cout << "In IN_PATTERN\n";
594 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
595 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
596 //cout << "TAKEOFF_ROLL, POS = " << pos.lon() << ", " << pos.lat() << ", " << pos.elev() << '\n';
598 _aip.setVisible(true);
599 //cout << "Making plane visible!\n";
604 FlyTrafficPattern(dt);
608 //cout << "In TAXIING\n";
609 //cout << "*" << flush;
612 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
613 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
615 _aip.setVisible(true);
617 //cout << "Making plane visible!\n";
622 //cout << "~" << flush;
623 if(!((holdingShort) && (!clearedToLineUp))) {
624 //cout << "|" << flush;
627 //cout << ";" << flush;
628 if((clearedToTakeOff) && (responseCounter >= 8.0)) {
629 // possible assumption that we're at the hold short here - may not always hold
630 // TODO - sort out the case where we're cleared to line-up first and then cleared to take-off on the rwy.
631 taxiState = TD_LINING_UP;
632 //cout << "A" << endl;
633 path = ground->GetPath(holdShortNode, rwy.rwyID);
634 //cout << "B" << endl;
635 if(!path.size()) { // Assume no facility file so we'll just taxi to a point on the runway near the threshold
636 //cout << "C" << endl;
638 np->struct_type = NODE;
639 np->pos = ortho.ConvertFromLocal(Point3D(0.0, 10.0, 0.0));
642 //cout << "D" << endl;
645 cout << "path returned was:" << endl;
646 for(unsigned int i=0; i<path.size(); ++i) {
647 switch(path[i]->struct_type) {
649 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
657 clearedToTakeOff = false; // We *are* still cleared - this simply stops the response recurring!!
658 holdingShort = false;
659 string trns = "Cleared for take-off ";
660 trns += plane.callsign;
661 pending_transmission = trns;
665 //cout << "^" << flush;
669 //cout << "In PARKED\n";
672 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
673 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
675 _aip.setVisible(true);
677 //cout << "Making plane visible!\n";
683 if((taxiRequestPending) && (taxiRequestCleared)) {
684 //cout << "&" << flush;
685 // Get the active runway details (in case they've changed since init)
686 GetRwyDetails(airportID);
688 // Get the takeoff node for the active runway, get a path to it and start taxiing
689 path = ground->GetPathToHoldShort(ourGate, rwy.rwyID);
690 if(path.size() < 2) {
691 // something has gone wrong
692 SG_LOG(SG_ATC, SG_ALERT, "Invalid path from gate to theshold in FGAILocalTraffic::FlyCircuits\n");
696 cout << "path returned was:\n";
697 for(unsigned int i=0; i<path.size(); ++i) {
698 switch(path[i]->struct_type) {
700 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
708 path.erase(path.begin()); // pop the gate - we're here already!
709 taxiState = TD_OUTBOUND;
710 taxiRequestPending = false;
711 holdShortNode = (node*)(*(path.begin() + path.size()));
713 } else if(!taxiRequestPending) {
714 //cout << "(" << flush;
715 // Do some communication
716 // airport name + tower + airplane callsign + location + request taxi for + operation type + ?
719 trns += tower->get_name();
723 // TODO - get the airport name somehow if uncontrolled
725 trns += plane.callsign;
726 trns += " on apron parking request taxi for traffic pattern";
727 //cout << "trns = " << trns << endl;
728 pending_transmission = trns;
730 taxiRequestCleared = false;
731 taxiRequestPending = true;
735 //cout << "!" << flush;
737 // Maybe the below should be set when we get to the threshold and prepare for TO?
738 // FIXME TODO - pattern direction is still hardwired
739 patternDirection = -1; // Left
740 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
741 if(rwy.rwyID.size() == 3) {
742 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
746 //cout << ")" << flush;
751 //cout << "I " << flush;
753 //cout << "Update _pos = " << _pos << ", vis = " << _aip.getVisible() << '\n';
755 // Convienience output for AI debugging using the property logger
756 //fgSetDouble("/AI/Local1/ortho-x", (ortho.ConvertToLocal(_pos)).x());
757 //fgSetDouble("/AI/Local1/ortho-y", (ortho.ConvertToLocal(_pos)).y());
758 //fgSetDouble("/AI/Local1/elev", _pos.elev() * SG_METER_TO_FEET);
760 // And finally, call parent.
761 FGAIPlane::Update(dt);
764 void FGAILocalTraffic::RegisterTransmission(int code) {
766 case 1: // taxi request cleared
767 taxiRequestCleared = true;
768 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to taxi...");
770 case 2: // contact tower
773 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact tower...");
775 case 3: // Cleared to line up
777 clearedToLineUp = true;
778 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to line-up...");
780 case 4: // cleared to take-off
782 clearedToTakeOff = true;
783 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to take-off...");
785 case 5: // contact ground
787 contactGround = true;
788 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact ground...");
790 // case 6 is a temporary mega-hack for controlled airports without separate ground control
791 case 6: // taxi to the GA parking
794 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to taxi to the GA parking...");
796 case 7: // Cleared to land (also implies cleared for the option
797 _clearedToLand = true;
798 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to land...");
800 case 13: // Go around!
803 _clearedToLand = false;
804 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to go-around!!");
811 // Fly a traffic pattern
812 // FIXME - far too much of the mechanics of turning, rolling, accellerating, descending etc is in here.
813 // Move it out to FGAIPlane and have FlyTrafficPattern just specify what to do, not the implementation.
814 void FGAILocalTraffic::FlyTrafficPattern(double dt) {
815 // Need to differentiate between in-air (IAS governed) and on-ground (vel governed)
816 // Take-off is an interesting case - we are on the ground but takeoff speed is IAS governed.
819 // Wind has two effects - a mechanical one in that IAS translates to a different vel, and the hdg != track,
820 // but also a piloting effect, in that the AI must be able to descend at a different rate in order to hit the threshold.
822 //cout << "dt = " << dt << '\n';
824 // ack - I can't remember how long a rate 1 turn is meant to take.
825 double turn_time = 60.0; // seconds - TODO - check this guess
826 double turn_circumference;
828 Point3D orthopos = ortho.ConvertToLocal(_pos); // ortho position of the plane
829 //cout << "runway elev = " << rwy.threshold_pos.elev() << ' ' << rwy.threshold_pos.elev() * SG_METER_TO_FEET << '\n';
830 //cout << "elev = " << _pos.elev() << ' ' << _pos.elev() * SG_METER_TO_FEET << '\n';
832 // HACK FOR TESTING - REMOVE
833 //cout << "Calling ExitRunway..." << endl;
834 //ExitRunway(orthopos);
839 double wind_from = wind_from_hdg->getDoubleValue();
840 double wind_speed = wind_speed_knots->getDoubleValue();
852 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
853 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
855 IAS = vel + (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
858 SetTrack(rwy.hdg); // Hands over control of turning to AIPlane
860 IAS = best_rate_of_climb_speed;
862 slope = 6.0; // Reduced it slightly since it's climbing a lot steeper than I can in the JSBSim C172.
867 // Turn to crosswind if above 700ft AND if other traffic allows
868 // (decided in FGTower and accessed through GetCrosswindConstraint(...)).
869 // According to AIM, traffic should climb to within 300ft of pattern altitude before commencing crosswind turn.
870 // TODO - At hot 'n high airports this may be 500ft AGL though - need to make this a variable.
871 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 700) {
873 if(tower->GetCrosswindConstraint(cc)) {
874 if(orthopos.y() > cc) {
875 //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
878 } else if(orthopos.y() > 1500.0) { // Added this constraint as a hack to prevent turning too early when going around.
879 // TODO - We should be doing it as a distance from takeoff end, not theshold end though.
880 //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
884 // Need to check for levelling off in case we can't turn crosswind as soon
885 // as we would like due to other traffic.
886 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
889 IAS = 80.0; // FIXME - use smooth transistion to new speed and attitude.
891 if(goAround && !goAroundCalled) {
892 if(responseCounter > 5.5) {
893 pending_transmission = plane.callsign;
894 pending_transmission += " going around";
896 goAroundCalled = true;
901 SetTrack(rwy.hdg + (90.0 * patternDirection));
902 if((track < (rwy.hdg - 89.0)) || (track > (rwy.hdg + 89.0))) {
908 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
911 IAS = 80.0; // FIXME - use smooth transistion to new speed
913 // turn 1000m out for now, taking other traffic into accout
914 if(fabs(orthopos.x()) > 900) {
916 if(tower->GetDownwindConstraint(dd)) {
917 if(fabs(orthopos.x()) > fabs(dd)) {
918 //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n';
922 //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n';
928 SetTrack(rwy.hdg - (180 * patternDirection));
929 // just in case we didn't make height on crosswind
930 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
933 IAS = 80.0; // FIXME - use smooth transistion to new speed
935 if((track < (rwy.hdg - 179.0)) || (track > (rwy.hdg + 179.0))) {
941 // just in case we didn't make height on crosswind
942 if(((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 995) && ((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET < 1015)) {
945 IAS = 90.0; // FIXME - use smooth transistion to new speed
947 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET >= 1015) {
950 IAS = 90.0; // FIXME - use smooth transistion to new speed
952 if((orthopos.y() < 0) && (!transmitted)) {
953 TransmitPatternPositionReport();
956 if((orthopos.y() < -100) && (!descending)) {
957 //cout << "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDdddd\n";
958 // Maybe we should think about when to start descending.
959 // For now we're assuming that we aim to follow the same glidepath regardless of wind.
962 CalculateSoD((tower->GetBaseConstraint(d1) ? d1 : -1000.0), (tower->GetDownwindConstraint(d2) ? d2 : 1000.0 * patternDirection), (patternDirection ? true : false));
963 if(SoD.leg == DOWNWIND) {
964 descending = (orthopos.y() < SoD.y ? true : false);
969 slope = -5.5; // FIXME - calculate to descent at 500fpm and hit the desired point on the runway (taking wind into account as well!!)
974 // Try and arrange to turn nicely onto base
975 turn_circumference = IAS * 0.514444 * turn_time;
976 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
977 //We'll leave it as a hack with IAS for now but it needs revisiting.
978 turn_radius = turn_circumference / (2.0 * DCL_PI);
979 if(orthopos.y() < -1000.0 + turn_radius) {
980 //if(orthopos.y() < -980) {
982 if(tower->GetBaseConstraint(bb)) {
983 if(fabs(orthopos.y()) > fabs(bb)) {
984 //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n';
990 //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n';
998 SetTrack(rwy.hdg - (90 * patternDirection));
999 if(fabs(rwy.hdg - track) < 91.0) {
1005 // Base report should only be transmitted at uncontrolled airport - not towered.
1006 if(!_controlled) TransmitPatternPositionReport();
1012 // Make downwind leg position artifically large to avoid any chance of SoD being returned as
1013 // on downwind when we are already on base.
1014 CalculateSoD((tower->GetBaseConstraint(d1) ? d1 : -1000.0), (10000.0 * patternDirection), (patternDirection ? true : false));
1015 if(SoD.leg == BASE) {
1016 descending = (fabs(orthopos.y()) < fabs(SoD.y) ? true : false);
1021 slope = -5.5; // FIXME - calculate to descent at 500fpm and hit the threshold (taking wind into account as well!!)
1026 // Try and arrange to turn nicely onto final
1027 turn_circumference = IAS * 0.514444 * turn_time;
1028 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
1029 //We'll leave it as a hack with IAS for now but it needs revisiting.
1030 turn_radius = turn_circumference / (2.0 * DCL_PI);
1031 if(fabs(orthopos.x()) < (turn_radius + 50)) {
1033 transmitted = false;
1039 if(fabs(track - rwy.hdg) < 0.6) {
1041 vel = nominal_final_speed;
1045 if(goAround && responseCounter > 2.0) {
1048 IAS = best_rate_of_climb_speed;
1049 slope = 5.0; // A bit less steep than the initial climbout.
1051 goAroundCalled = false;
1057 if((!_controlled) || (!_clearedToLand)) TransmitPatternPositionReport();
1061 // Make base leg position artifically large to avoid any chance of SoD being returned as
1062 // on base or downwind when we are already on final.
1063 CalculateSoD(-10000.0, (1000.0 * patternDirection), (patternDirection ? true : false));
1064 if(SoD.leg == FINAL) {
1065 descending = (fabs(orthopos.y()) < fabs(SoD.y) ? true : false);
1070 if(orthopos.y() < -50.0) {
1071 double thesh_offset = 30.0;
1072 slope = atan((_pos.elev() - dclGetAirportElev(airportID)) / (orthopos.y() - thesh_offset)) * DCL_RADIANS_TO_DEGREES;
1073 //cout << "slope = " << slope << ", elev = " << _pos.elev() << ", apt_elev = " << dclGetAirportElev(airportID) << ", op.y = " << orthopos.y() << '\n';
1074 if(slope < -10.0) slope = -10.0;
1075 _savedSlope = slope;
1079 if(_pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) {
1080 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
1081 if(_pos.elev() < (_aip.getSGLocation()->get_cur_elev_m() + wheelOffset + 1.0)) {
1085 } else if(_pos.elev() < (_aip.getSGLocation()->get_cur_elev_m() + wheelOffset + 5.0)) {
1090 slope = _savedSlope;
1095 // Elev not determined
1096 slope = _savedSlope;
1101 slope = _savedSlope;
1107 // Try and track the extended centreline
1108 SetTrack(rwy.hdg - (0.2 * orthopos.x()));
1109 //cout << "orthopos.x() = " << orthopos.x() << " hdg = " << hdg << '\n';
1110 if(_pos.elev() < (rwy.threshold_pos.elev()+20.0+wheelOffset)) {
1111 DoGroundElev(); // Need to call it here expicitly on final since it's only called
1112 // for us in update(...) when the inAir flag is false.
1114 if(_pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) {
1117 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
1118 if((_aip.getSGLocation()->get_cur_elev_m() + wheelOffset) > _pos.elev()) {
1124 ClearTrack(); // Take over explicit track handling since AIPlane currently always banks when changing course
1126 } // else need a fallback position based on arpt elev in case ground elev determination fails?
1134 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
1135 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
1140 // FIXME - differentiate between touch and go and full stops
1142 //cout << "Vel <= 15.0, circuitsToFly = " << circuitsToFly << endl;
1143 if(circuitsToFly <= 0) {
1144 //cout << "Calling ExitRunway..." << endl;
1145 ExitRunway(orthopos);
1148 //cout << "Taking off again..." << endl;
1159 // FIXME - at the moment this is a bit screwy
1160 // The velocity correction is applied based on the relative headings.
1161 // Then the heading is changed based on the velocity.
1162 // Which comes first, the chicken or the egg?
1163 // Does it really matter?
1165 // Apply wind to ground-relative velocity if in the air
1166 vel = IAS - (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
1167 //crab = f(track, wind, vel);
1168 // The vector we need to fly is our desired vector minus the wind vector
1169 // TODO - we probably ought to use plib's built in vector types and operations for this
1170 // ie. There's almost *certainly* a better way to do this!
1171 double gxx = vel * sin(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity x component wrt ground
1172 double gyy = vel * cos(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity y component wrt ground
1173 double wxx = wind_speed * sin((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS); // Wind velocity x component
1174 double wyy = wind_speed * cos((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS); // Wind velocity y component
1175 double axx = gxx - wxx; // Plane in-air velocity x component
1176 double ayy = gyy - wyy; // Plane in-air velocity y component
1177 // Now we want the angle between gxx and axx (which is the crab)
1178 double maga = sqrt(axx*axx + ayy*ayy);
1179 double magg = sqrt(gxx*gxx + gyy*gyy);
1180 crab = acos((axx*gxx + ayy*gyy) / (maga * magg));
1181 // At this point this works except we're getting the modulus of the angle
1182 //cout << "crab = " << crab << '\n';
1184 // Make sure both headings are in the 0->360 circle in order to get sane differences
1185 dclBoundHeading(wind_from);
1186 dclBoundHeading(track);
1187 if(track > wind_from) {
1188 if((track - wind_from) <= 180) {
1192 if((wind_from - track) >= 180) {
1196 } else { // on the ground - crab dosen't apply
1200 //cout << "X " << orthopos.x() << " Y " << orthopos.y() << " SLOPE " << slope << " elev " << _pos.elev() * SG_METER_TO_FEET << '\n';
1202 _hdg = track + crab;
1203 dist = vel * 0.514444 * dt;
1204 _pos = dclUpdatePosition(_pos, track, slope, dist);
1207 // Pattern direction is true for right, false for left
1208 void FGAILocalTraffic::CalculateSoD(double base_leg_pos, double downwind_leg_pos, bool pattern_direction) {
1209 // For now we'll ignore wind and hardwire the glide angle.
1210 double ga = 5.5; //degrees
1211 double pa = 1000.0 * SG_FEET_TO_METER; // pattern altitude in meters
1212 // FIXME - get glideslope angle and pattern altitude agl from airport details if available
1214 // For convienience, we'll have +ve versions of the input distances
1215 double blp = fabs(base_leg_pos);
1216 double dlp = fabs(downwind_leg_pos);
1218 //double turn_allowance = 150.0; // Approximate distance in meters that a 90deg corner is shortened by turned in a light plane.
1220 double stod = pa / tan(ga * DCL_DEGREES_TO_RADIANS); // distance in meters from touchdown point to start descent
1221 //cout << "Descent to start = " << stod << " meters out\n";
1222 if(stod < blp) { // Start descending on final
1224 SoD.y = stod * -1.0;
1226 } else if(stod < (blp + dlp)) { // Start descending on base leg
1229 SoD.x = (pattern_direction ? (stod - dlp) : (stod - dlp) * -1.0);
1230 } else { // Start descending on downwind leg
1232 SoD.x = (pattern_direction ? dlp : dlp * -1.0);
1233 SoD.y = (blp - (stod - (blp + dlp))) * -1.0;
1237 void FGAILocalTraffic::TransmitPatternPositionReport(void) {
1238 // airport name + "traffic" + airplane callsign + pattern direction + pattern leg + rwy + ?
1242 trns += tower->get_name();
1243 trns += " Traffic ";
1244 trns += plane.callsign;
1245 if(patternDirection == 1) {
1251 // We could probably get rid of this whole switch statement and just pass a string containing the leg from the FlyPattern function.
1252 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?
1254 // Fall through to CROSSWIND
1255 case CROSSWIND: // I don't think this case will be used here but it can't hurt to leave it in
1256 trns += "crosswind ";
1259 // Fall through to DOWNWIND
1261 trns += "downwind ";
1265 // Fall through to BASE
1270 // Fall through to FINAL
1271 case FINAL: // maybe this should include long/short final if appropriate?
1275 default: // Hopefully this won't be used
1279 trns += ConvertRwyNumToSpokenString(rwy.rwyID);
1283 // And add the airport name again
1284 trns += tower->get_name();
1286 pending_transmission = trns;
1287 ConditionalTransmit(60.0, code); // Assume a report of this leg will be invalid if we can't transmit within a minute.
1291 // TODO - Really should enumerate these coded values.
1292 void FGAILocalTraffic::ProcessCallback(int code) {
1293 // 1 - Request Departure from ground
1294 // 2 - Report at hold short
1295 // 3 - Report runway vacated
1296 // 10 - report crosswind
1297 // 11 - report downwind
1299 // 13 - report final
1301 ground->RequestDeparture(plane, this);
1302 } else if(code == 2) {
1303 tower->ContactAtHoldShort(plane, this, CIRCUIT);
1304 } else if(code == 3) {
1305 tower->ReportRunwayVacated(plane.callsign);
1306 } else if(code == 11) {
1307 tower->ReportDownwind(plane.callsign);
1308 } else if(code == 13) {
1309 tower->ReportFinal(plane.callsign);
1310 } else if(code == 99) { // Flag this instance for deletion
1311 responseCounter = 0;
1313 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " delete instance callback called.");
1317 void FGAILocalTraffic::ExitRunway(Point3D orthopos) {
1318 //cout << "In ExitRunway" << endl;
1319 //cout << "Runway ID is " << rwy.ID << endl;
1321 _clearedToLand = false;
1323 node_array_type exitNodes = ground->GetExits(rwy.rwyID); //I suppose we ought to have some fallback for rwy with no defined exits?
1325 cout << "Node ID's of exits are ";
1326 for(unsigned int i=0; i<exitNodes.size(); ++i) {
1327 cout << exitNodes[i]->nodeID << ' ';
1331 if(exitNodes.size()) {
1332 //Find the next exit from orthopos.y
1334 double dist = 100000; //ie. longer than any runway in existance
1335 double backdist = 100000;
1336 node_array_iterator nItr = exitNodes.begin();
1337 node* rwyExit = *(exitNodes.begin());
1338 //int gateID; //This might want to be more persistant at some point
1339 while(nItr != exitNodes.end()) {
1340 d = ortho.ConvertToLocal((*nItr)->pos).y() - ortho.ConvertToLocal(_pos).y(); //FIXME - consider making orthopos a class variable
1347 if(fabs(d) < backdist) {
1349 //TODO - need some logic here that if we don't get a forward exit we turn round and store the backwards one
1354 ourGate = ground->GetGateNode();
1355 if(ourGate == NULL) {
1356 // Implies no available gates - what shall we do?
1357 // For now just vanish the plane - possibly we can make this more elegant in the future
1358 SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst landing at " << airportID << '\n');
1359 //_aip.setVisible(false);
1360 //cout << "Setting visible false\n";
1361 operatingState = PARKED;
1364 path = ground->GetPath(rwyExit, ourGate);
1366 cout << "path returned was:" << endl;
1367 for(unsigned int i=0; i<path.size(); ++i) {
1368 switch(path[i]->struct_type) {
1370 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
1378 taxiState = TD_INBOUND;
1381 // Something must have gone wrong with the ground network file - or there is only a rwy here and no exits defined
1382 SG_LOG(SG_ATC, SG_INFO, "No exits found by FGAILocalTraffic from runway " << rwy.rwyID << " at " << airportID << '\n');
1383 //if(airportID == "KRHV") cout << "No exits found by " << plane.callsign << " from runway " << rwy.rwyID << " at " << airportID << '\n';
1384 // What shall we do - just remove the plane from sight?
1385 _aip.setVisible(false);
1387 //cout << "Setting visible false\n";
1388 //tower->ReportRunwayVacated(plane.callsign);
1389 string trns = "Clear of the runway ";
1390 trns += plane.callsign;
1391 pending_transmission = trns;
1393 operatingState = PARKED;
1397 // Set the class variable nextTaxiNode to the next node in the path
1398 // and update taxiPathPos, the class variable path iterator position
1399 // TODO - maybe should return error codes to the calling function if we fail here
1400 void FGAILocalTraffic::GetNextTaxiNode() {
1401 //cout << "GetNextTaxiNode called " << endl;
1402 //cout << "taxiPathPos = " << taxiPathPos << endl;
1403 ground_network_path_iterator pathItr = path.begin() + taxiPathPos;
1404 if(pathItr == path.end()) {
1405 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - no more nodes in path\n");
1407 if((*pathItr)->struct_type == NODE) {
1408 //cout << "ITS A NODE" << endl;
1409 //*pathItr = new node;
1410 nextTaxiNode = (node*)*pathItr;
1414 //cout << "ITS NOT A NODE" << endl;
1415 //The first item in found must have been an arc
1416 //Assume for now that it was straight
1419 if(pathItr == path.end()) {
1420 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - path ended with an arc\n");
1421 } else if((*pathItr)->struct_type == NODE) {
1422 nextTaxiNode = (node*)*pathItr;
1425 //OOPS - two non-nodes in a row - that shouldn't happen ATM
1426 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - two non-nodes in sequence\n");
1432 // StartTaxi - set up the taxiing state - call only at the start of taxiing
1433 void FGAILocalTraffic::StartTaxi() {
1434 //cout << "StartTaxi called" << endl;
1435 operatingState = TAXIING;
1439 //Set the desired heading
1440 //Assume we are aiming for first node on path
1441 //Eventually we may need to consider the fact that we might start on a curved arc and
1442 //not be able to head directly for the first node.
1443 GetNextTaxiNode(); // sets the class variable nextTaxiNode to the next taxi node!
1444 desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos);
1445 //cout << "First taxi heading is " << desiredTaxiHeading << endl;
1448 // speed in knots, headings in degrees, radius in meters.
1449 static double TaxiTurnTowardsHeading(double current_hdg, double desired_hdg, double speed, double radius, double dt) {
1450 // wrap heading - this prevents a logic bug where the plane would just go round in circles!!
1451 while(current_hdg < 0.0) {
1452 current_hdg += 360.0;
1454 while(current_hdg > 360.0) {
1455 current_hdg -= 360.0;
1457 if(fabs(current_hdg - desired_hdg) > 0.1) {
1458 // Which is the quickest direction to turn onto heading?
1459 if(desired_hdg > current_hdg) {
1460 if((desired_hdg - current_hdg) <= 180) {
1462 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1463 // TODO - check that increments are less than the delta that we check for the right direction
1464 // Probably need to reduce convergence speed as convergence is reached
1466 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1469 if((current_hdg - desired_hdg) <= 180) {
1471 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1472 // TODO - check that increments are less than the delta that we check for the right direction
1473 // Probably need to reduce convergence speed as convergence is reached
1475 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1479 return(current_hdg);
1482 void FGAILocalTraffic::Taxi(double dt) {
1483 //cout << "Taxi called" << endl;
1484 // Logic - if we are further away from next point than turn radius then head for it
1485 // If we have reached turning point then get next point and turn onto that heading
1486 // Look out for the finish!!
1488 //Point3D orthopos = ortho.ConvertToLocal(pos); // ortho position of the plane
1489 desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos);
1491 bool lastNode = (taxiPathPos == path.size() ? true : false);
1493 //cout << "LAST NODE\n";
1496 // HACK ALERT! - for now we will taxi at constant speed for straights and turns
1498 // Remember that hdg is always equal to track when taxiing so we don't have to consider them both
1499 double dist_to_go = dclGetHorizontalSeparation(_pos, nextTaxiNode->pos); // we may be able to do this more cheaply using orthopos
1500 //cout << "dist_to_go = " << dist_to_go << endl;
1501 if((nextTaxiNode->type == GATE) && (dist_to_go <= 0.1)) {
1502 // This might be more robust to outward paths starting with a gate if we check for either
1503 // last node or TD_INBOUND ?
1505 operatingState = PARKED;
1506 } else if(((dist_to_go > taxiTurnRadius) || (nextTaxiNode->type == GATE)) && (!liningUp)){
1507 // if the turn radius is r, and speed is s, then in a time dt we turn through
1508 // ((s.dt)/(PI.r)) x 180 degrees
1509 // or alternatively (s.dt)/r radians
1510 //cout << "hdg = " << hdg << " desired taxi heading = " << desiredTaxiHeading << '\n';
1511 _hdg = TaxiTurnTowardsHeading(_hdg, desiredTaxiHeading, nominalTaxiSpeed, taxiTurnRadius, dt);
1512 double vel = nominalTaxiSpeed;
1513 //cout << "vel = " << vel << endl;
1514 double dist = vel * 0.514444 * dt;
1515 //cout << "dist = " << dist << endl;
1516 double track = _hdg;
1517 //cout << "track = " << track << endl;
1519 _pos = dclUpdatePosition(_pos, track, slope, dist);
1520 //cout << "Updated position...\n";
1521 if(_aip.getSGLocation()->get_cur_elev_m() > -9990) {
1522 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
1523 } // else don't change the elev until we get a valid ground elev again!
1524 } else if(lastNode) {
1525 if(taxiState == TD_LINING_UP) {
1526 if((!liningUp) && (dist_to_go <= taxiTurnRadius)) {
1530 _hdg = TaxiTurnTowardsHeading(_hdg, rwy.hdg, nominalTaxiSpeed, taxiTurnRadius, dt);
1531 double vel = nominalTaxiSpeed;
1532 //cout << "vel = " << vel << endl;
1533 double dist = vel * 0.514444 * dt;
1534 //cout << "dist = " << dist << endl;
1535 double track = _hdg;
1536 //cout << "track = " << track << endl;
1538 _pos = dclUpdatePosition(_pos, track, slope, dist);
1539 //cout << "Updated position...\n";
1540 if(_aip.getSGLocation()->get_cur_elev_m() > -9990) {
1541 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
1542 } // else don't change the elev until we get a valid ground elev again!
1543 if(fabs(_hdg - rwy.hdg) <= 1.0) {
1544 operatingState = IN_PATTERN;
1550 } else if(taxiState == TD_OUTBOUND) {
1551 // Pause awaiting further instructions
1552 // and for now assume we've reached the hold-short node
1553 holdingShort = true;
1554 } // else at the moment assume TD_INBOUND always ends in a gate in which case we can ignore it
1556 // Time to turn (we've already checked it's not the end we're heading for).
1557 // set the target node to be the next node which will prompt automatically turning onto
1558 // the right heading in the stuff above, with the usual provisos applied.
1560 // For now why not just recursively call this function?
1566 // Warning - ground elev determination is CPU intensive
1567 // Either this function or the logic of how often it is called
1568 // will almost certainly change.
1569 void FGAILocalTraffic::DoGroundElev() {
1570 // It would be nice if we could set the correct tile center here in order to get a correct
1571 // answer with one call to the function, but what I tried in the two commented-out lines
1572 // below only intermittently worked, and I haven't quite groked why yet.
1573 //SGBucket buck(pos.lon(), pos.lat());
1574 //aip.getSGLocation()->set_tile_center(Point3D(buck.get_center_lon(), buck.get_center_lat(), 0.0));
1576 // Only do the proper hitlist stuff if we are within visible range of the viewer.
1577 double visibility_meters = fgGetDouble("/environment/visibility-m");
1578 FGViewer* vw = globals->get_current_view();
1579 if(dclGetHorizontalSeparation(_pos, Point3D(vw->getLongitude_deg(), vw->getLatitude_deg(), 0.0)) > visibility_meters) {
1580 _aip.getSGLocation()->set_cur_elev_m(aptElev);
1585 //globals->get_tile_mgr()->prep_ssg_nodes( acmodel_location,
1586 globals->get_tile_mgr()->prep_ssg_nodes( _aip.getSGLocation(), visibility_meters );
1587 Point3D scenery_center = globals->get_scenery()->get_center();
1588 globals->get_tile_mgr()->update( _aip.getSGLocation(), visibility_meters, (_aip.getSGLocation())->get_absolute_view_pos( scenery_center ) );
1589 // save results of update in SGLocation for fdm...
1591 //if ( globals->get_scenery()->get_cur_elev() > -9990 ) {
1592 // acmodel_location->
1593 // set_cur_elev_m( globals->get_scenery()->get_cur_elev() );
1596 // The need for this here means that at least 2 consecutive passes are needed :-(
1597 _aip.getSGLocation()->set_tile_center( globals->get_scenery()->get_next_center() );
1599 //cout << "Transform Elev is " << globals->get_scenery()->get_cur_elev() << '\n';
1600 _aip.getSGLocation()->set_cur_elev_m(globals->get_scenery()->get_cur_elev());
1601 //return(globals->get_scenery()->get_cur_elev());