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;
126 FGAILocalTraffic::~FGAILocalTraffic() {
130 void FGAILocalTraffic::GetAirportDetails(string id) {
132 if(ATC->GetAirportATCDetails(airportID, &a)) {
133 if(a.tower_freq) { // Has a tower - TODO - check the opening hours!!!
134 tower = (FGTower*)ATC->GetATCPointer(airportID, TOWER);
136 // Something has gone wrong - abort or carry on with un-towered operation?
137 SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a tower pointer from tower control for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
143 ground = tower->GetGroundPtr();
145 // Something has gone wrong :-(
146 SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a ground pointer from tower control in FGAILocalTraffic::GetAirportDetails() :-(");
151 // TODO - Check CTAF, unicom etc
154 SG_LOG(SG_ATC, SG_ALERT, "Unable to find airport details in for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
157 // Get the airport elevation
158 aptElev = dclGetAirportElev(airportID.c_str());
159 //cout << "Airport elev in AILocalTraffic = " << aptElev << '\n';
160 // WARNING - we use this elev for the whole airport - some assumptions in the code
161 // might fall down with very slopey airports.
164 // Get details of the active runway
165 // It is assumed that by the time this is called the tower control and airport code will have been set up.
166 void FGAILocalTraffic::GetRwyDetails(string id) {
167 //cout << "GetRwyDetails called" << endl;
170 rwy.rwyID = tower->GetActiveRunway();
172 // TODO - get a proper runway ID from uncontrolled airports
176 // Now we need to get the threshold position and rwy heading
179 bool rwyGood = globals->get_runways()->search(id, rwy.rwyID, &runway);
181 double hdg = runway.heading;
182 double other_way = hdg - 180.0;
183 while(other_way <= 0.0) {
187 // move to the +l end/center of the runway
188 //cout << "Runway center is at " << runway.lon << ", " << runway.lat << '\n';
189 Point3D origin = Point3D(runway.lon, runway.lat, aptElev);
190 Point3D ref = origin;
191 double tshlon, tshlat, tshr;
192 double tolon, tolat, tor;
193 rwy.length = runway.length * SG_FEET_TO_METER;
194 rwy.width = runway.width * SG_FEET_TO_METER;
195 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), other_way,
196 rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
197 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), hdg,
198 rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor );
199 // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user.
200 // now copy what we need out of runway into rwy
201 rwy.threshold_pos = Point3D(tshlon, tshlat, aptElev);
202 Point3D takeoff_end = Point3D(tolon, tolat, aptElev);
203 //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n';
204 //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
206 // Set the projection for the local area
207 //cout << "Initing ortho for airport " << id << '\n';
208 ortho.Init(rwy.threshold_pos, rwy.hdg);
209 rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos); // should come out as zero
210 rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
212 SG_LOG(SG_ATC, SG_ALERT, "Help - can't get good runway at airport " << id << " in FGAILocalTraffic!!");
218 There are two possible scenarios during initialisation:
219 The first is that the user is flying towards the airport, and hence the traffic
220 could be initialised anywhere, as long as the AI planes are consistent with
222 The second is that the user has started the sim at or close to the airport, and
223 hence the traffic must be initialised with respect to the user as well as each other.
224 To a certain extent it's FGAIMgr that has to worry about this, but we need to provide
225 sufficient initialisation functionality within the plane classes to allow the manager
226 to initially position them where and how required.
228 bool FGAILocalTraffic::Init(const string& callsign, string ICAO, OperatingState initialState, PatternLeg initialLeg) {
229 //cout << "FGAILocalTraffic.Init(...) called" << endl;
232 plane.callsign = callsign;
234 if(initialState == EN_ROUTE) return(true);
236 // Get the ATC pointers and airport elev
237 GetAirportDetails(airportID);
239 // Get the active runway details (and copy them into rwy)
240 GetRwyDetails(airportID);
241 //cout << "Runway is " << rwy.rwyID << '\n';
243 // FIXME TODO - pattern direction is still hardwired
244 patternDirection = -1; // Left
245 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
246 if(rwy.rwyID.size() == 3) {
247 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
251 if((initialState == PARKED) || (initialState == TAXIING)) {
252 freq = (double)ground->get_freq() / 100.0;
254 freq = (double)tower->get_freq() / 100.0;
258 // TODO - find the proper freq if CTAF or unicom or after-hours.
261 //cout << "In Init(), initialState = " << initialState << endl;
262 operatingState = initialState;
264 switch(operatingState) {
266 tuned_station = ground;
267 ourGate = ground->GetGateNode();
268 if(ourGate == NULL) {
269 // Implies no available gates - what shall we do?
270 // For now just vanish the plane - possibly we can make this more elegant in the future
271 SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst attempting Init at " << airportID << '\n');
279 _pos.setelev(aptElev);
280 _hdg = ourGate->heading;
283 // Now we've set the position we can do the ground elev
284 elevInitGood = false;
290 //tuned_station = ground;
291 // FIXME - implement this case properly
292 // For now we'll assume that the plane should start at the hold short in this case
293 // and that we're working without ground network elements. Ie. an airport with no facility file.
295 tuned_station = tower;
297 tuned_station = NULL;
300 // Set a position and orientation in an approximate place for hold short.
301 //cout << "rwy.width = " << rwy.width << '\n';
302 orthopos = Point3D((rwy.width / 2.0 + 10.0) * -1.0, 0.0, 0.0);
303 // TODO - set the x pos to be +ve if a RH parallel rwy.
304 _pos = ortho.ConvertFromLocal(orthopos);
305 _pos.setelev(aptElev);
306 _hdg = rwy.hdg + 90.0;
307 // TODO - reset the heading if RH rwy.
312 elevInitGood = false;
318 responseCounter = 0.0;
319 contactTower = false;
322 clearedToLineUp = false;
323 changeFreqType = TOWER;
327 // For now we'll always start the in_pattern case on the threshold ready to take-off
328 // since we've got the implementation for this case already.
329 // TODO - implement proper generic in_pattern startup.
331 // 18/10/03 - adding the ability to start on downwind (mainly to speed testing of the go-around code!!)
333 //cout << "Starting in pattern...\n";
336 tuned_station = tower;
338 tuned_station = NULL;
341 circuitsToFly = 0; // ie just fly this circuit and then stop
344 if(initialLeg == DOWNWIND) {
345 _pos = ortho.ConvertFromLocal(Point3D(1000*patternDirection, 800, 0.0));
346 _pos.setelev(rwy.threshold_pos.elev() + 1000 * SG_FEET_TO_METER);
347 _hdg = rwy.hdg + 180.0;
349 elevInitGood = false;
351 SetTrack(rwy.hdg - (180 * patternDirection));
357 _aip.setVisible(true);
359 tower->RegisterAIPlane(plane, this, CIRCUIT, DOWNWIND);
363 // Default to initial position on threshold for now
364 _pos.setlat(rwy.threshold_pos.lat());
365 _pos.setlon(rwy.threshold_pos.lon());
366 _pos.setelev(rwy.threshold_pos.elev());
369 // Now we've set the position we can do the ground elev
370 // This might not always be necessary if we implement in-air start
371 elevInitGood = false;
384 operatingState = IN_PATTERN;
388 // This implies we're being init'd by AIGAVFRTraffic - simple return now
391 SG_LOG(SG_ATC, SG_ALERT, "Attempt to set unknown operating state in FGAILocalTraffic.Init(...)\n");
400 // Set up downwind state - this is designed to be called from derived classes who are already tuned to tower
401 void FGAILocalTraffic::DownwindEntry() {
402 circuitsToFly = 0; // ie just fly this circuit and then stop
404 operatingState = IN_PATTERN;
406 elevInitGood = false;
408 SetTrack(rwy.hdg - (180 * patternDirection));
416 void FGAILocalTraffic::StraightInEntry(bool des) {
417 //cout << "************ STRAIGHT-IN ********************\n";
418 circuitsToFly = 0; // ie just fly this circuit and then stop
420 operatingState = IN_PATTERN;
422 elevInitGood = false;
425 transmitted = true; // TODO - fix this hack.
426 // TODO - set up the next 5 properly for a descent!
435 // Return what type of landing we're doing on this circuit
436 LandingType FGAILocalTraffic::GetLandingOption() {
437 //cout << "circuitsToFly = " << circuitsToFly << '\n';
439 return(touchAndGo ? TOUCH_AND_GO : STOP_AND_GO);
446 // Commands to do something from higher level logic
447 void FGAILocalTraffic::FlyCircuits(int numCircuits, bool tag) {
448 //cout << "FlyCircuits called" << endl;
450 switch(operatingState) {
452 circuitsToFly += numCircuits;
456 // HACK - assume that we're taxiing out for now
457 circuitsToFly += numCircuits;
461 circuitsToFly = numCircuits; // Note that one too many circuits gets flown because we only test and decrement circuitsToFly after landing
462 // thus flying one too many circuits. TODO - Need to sort this out better!
470 // Run the internal calculations
471 void FGAILocalTraffic::Update(double dt) {
472 //cout << "U" << flush;
474 // we shouldn't really need this since there's a LOD of 10K on the whole plane anyway I think.
475 // At the moment though I need to to avoid DList overflows - the whole plane LOD obviously isn't getting picked up.
477 if(dclGetHorizontalSeparation(_pos, Point3D(fgGetDouble("/position/longitude-deg"), fgGetDouble("/position/latitude-deg"), 0.0)) > 8000) _aip.setVisible(false);
478 else _aip.setVisible(true);
480 _aip.setVisible(false);
483 //double responseTime = 10.0; // seconds - this should get more sophisticated at some point
484 responseCounter += dt;
485 if((contactTower) && (responseCounter >= 8.0)) {
486 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
487 string trns = "Tower ";
488 double f = globals->get_ATC_mgr()->GetFrequency(airportID, TOWER) / 100.0;
490 sprintf(buf, "%.2f", f);
493 trns += plane.callsign;
494 pending_transmission = trns;
495 ConditionalTransmit(30.0);
496 responseCounter = 0.0;
497 contactTower = false;
499 changeFreqType = TOWER;
502 if((contactGround) && (responseCounter >= 8.0)) {
503 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
504 string trns = "Ground ";
505 double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;
507 sprintf(buf, "%.2f", f);
511 pending_transmission = trns;
512 ConditionalTransmit(5.0);
513 responseCounter = 0.0;
514 contactGround = false;
516 changeFreqType = GROUND;
519 if((_taxiToGA) && (responseCounter >= 8.0)) {
520 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
521 string trns = "GA Parking, Thank you and Good Day";
522 //double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;
523 pending_transmission = trns;
524 ConditionalTransmit(5.0);
526 tower->DeregisterAIPlane(plane.callsign);
529 // HACK - check if we are at a simple airport or not first
530 globals->get_AI_mgr()->ScheduleRemoval(plane.callsign);
533 if((changeFreq) && (responseCounter > 8.0)) {
534 switch(changeFreqType) {
537 SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to tower in FGAILocalTraffic, but tower is NULL!!!");
540 tuned_station = tower;
541 freq = (double)tower->get_freq() / 100.0;
543 // Contact the tower, even if only virtually
544 pending_transmission = plane.callsign;
545 pending_transmission += " at hold short for runway ";
546 pending_transmission += ConvertRwyNumToSpokenString(rwy.rwyID);
547 pending_transmission += " traffic pattern ";
549 pending_transmission += ConvertNumToSpokenDigits(circuitsToFly + 1);
550 pending_transmission += " circuits touch and go";
552 pending_transmission += " one circuit to full stop";
558 SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to ground in FGAILocalTraffic, but tower is NULL!!!");
562 SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to ground in FGAILocalTraffic, but ground is NULL!!!");
565 tower->DeregisterAIPlane(plane.callsign);
566 tuned_station = ground;
567 freq = (double)ground->get_freq() / 100.0;
568 // HACK - check if we are at a simple airport or not first
569 // TODO FIXME TODO FIXME !!!!!!!
570 if(airportID != "KEMT") globals->get_AI_mgr()->ScheduleRemoval(plane.callsign);
572 // And to avoid compiler warnings...
573 case APPROACH: break;
576 case DEPARTURE: break;
582 //cout << "," << flush;
584 switch(operatingState) {
586 //cout << "In IN_PATTERN\n";
590 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
591 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
592 //cout << "TAKEOFF_ROLL, POS = " << pos.lon() << ", " << pos.lat() << ", " << pos.elev() << '\n';
594 _aip.setVisible(true);
595 //cout << "Making plane visible!\n";
600 FlyTrafficPattern(dt);
604 //cout << "In TAXIING\n";
605 //cout << "*" << flush;
608 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
609 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
611 _aip.setVisible(true);
613 //cout << "Making plane visible!\n";
618 //cout << "~" << flush;
619 if(!((holdingShort) && (!clearedToLineUp))) {
620 //cout << "|" << flush;
623 //cout << ";" << flush;
624 if((clearedToTakeOff) && (responseCounter >= 8.0)) {
625 // possible assumption that we're at the hold short here - may not always hold
626 // TODO - sort out the case where we're cleared to line-up first and then cleared to take-off on the rwy.
627 taxiState = TD_LINING_UP;
628 //cout << "A" << endl;
629 path = ground->GetPath(holdShortNode, rwy.rwyID);
630 //cout << "B" << endl;
631 if(!path.size()) { // Assume no facility file so we'll just taxi to a point on the runway near the threshold
632 //cout << "C" << endl;
634 np->struct_type = NODE;
635 np->pos = ortho.ConvertFromLocal(Point3D(0.0, 10.0, 0.0));
638 //cout << "D" << endl;
641 cout << "path returned was:" << endl;
642 for(unsigned int i=0; i<path.size(); ++i) {
643 switch(path[i]->struct_type) {
645 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
653 clearedToTakeOff = false; // We *are* still cleared - this simply stops the response recurring!!
654 holdingShort = false;
655 string trns = "Cleared for take-off ";
656 trns += plane.callsign;
657 pending_transmission = trns;
661 //cout << "^" << flush;
665 //cout << "In PARKED\n";
668 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
669 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
671 _aip.setVisible(true);
673 //cout << "Making plane visible!\n";
679 if((taxiRequestPending) && (taxiRequestCleared)) {
680 //cout << "&" << flush;
681 // Get the active runway details (in case they've changed since init)
682 GetRwyDetails(airportID);
684 // Get the takeoff node for the active runway, get a path to it and start taxiing
685 path = ground->GetPathToHoldShort(ourGate, rwy.rwyID);
686 if(path.size() < 2) {
687 // something has gone wrong
688 SG_LOG(SG_ATC, SG_ALERT, "Invalid path from gate to theshold in FGAILocalTraffic::FlyCircuits\n");
692 cout << "path returned was:\n";
693 for(unsigned int i=0; i<path.size(); ++i) {
694 switch(path[i]->struct_type) {
696 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
704 path.erase(path.begin()); // pop the gate - we're here already!
705 taxiState = TD_OUTBOUND;
706 taxiRequestPending = false;
707 holdShortNode = (node*)(*(path.begin() + path.size()));
709 } else if(!taxiRequestPending) {
710 //cout << "(" << flush;
711 // Do some communication
712 // airport name + tower + airplane callsign + location + request taxi for + operation type + ?
715 trns += tower->get_name();
719 // TODO - get the airport name somehow if uncontrolled
721 trns += plane.callsign;
722 trns += " on apron parking request taxi for traffic pattern";
723 //cout << "trns = " << trns << endl;
724 pending_transmission = trns;
726 taxiRequestCleared = false;
727 taxiRequestPending = true;
731 //cout << "!" << flush;
733 // Maybe the below should be set when we get to the threshold and prepare for TO?
734 // FIXME TODO - pattern direction is still hardwired
735 patternDirection = -1; // Left
736 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
737 if(rwy.rwyID.size() == 3) {
738 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
742 //cout << ")" << flush;
747 //cout << "I " << flush;
749 //cout << "Update _pos = " << _pos << ", vis = " << _aip.getVisible() << '\n';
751 // Convienience output for AI debugging using the property logger
752 //fgSetDouble("/AI/Local1/ortho-x", (ortho.ConvertToLocal(_pos)).x());
753 //fgSetDouble("/AI/Local1/ortho-y", (ortho.ConvertToLocal(_pos)).y());
754 //fgSetDouble("/AI/Local1/elev", _pos.elev() * SG_METER_TO_FEET);
756 // And finally, call parent.
757 FGAIPlane::Update(dt);
760 void FGAILocalTraffic::RegisterTransmission(int code) {
762 case 1: // taxi request cleared
763 taxiRequestCleared = true;
764 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to taxi...");
766 case 2: // contact tower
769 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact tower...");
771 case 3: // Cleared to line up
773 clearedToLineUp = true;
774 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to line-up...");
776 case 4: // cleared to take-off
778 clearedToTakeOff = true;
779 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to take-off...");
781 case 5: // contact ground
783 contactGround = true;
784 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact ground...");
786 // case 6 is a temporary mega-hack for controlled airports without separate ground control
787 case 6: // taxi to the GA parking
790 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to taxi to the GA parking...");
792 case 7: // Cleared to land (also implies cleared for the option
793 _clearedToLand = true;
794 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to land...");
796 case 13: // Go around!
799 _clearedToLand = false;
800 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to go-around!!");
807 // Fly a traffic pattern
808 // FIXME - far too much of the mechanics of turning, rolling, accellerating, descending etc is in here.
809 // Move it out to FGAIPlane and have FlyTrafficPattern just specify what to do, not the implementation.
810 void FGAILocalTraffic::FlyTrafficPattern(double dt) {
811 // Need to differentiate between in-air (IAS governed) and on-ground (vel governed)
812 // Take-off is an interesting case - we are on the ground but takeoff speed is IAS governed.
815 // Wind has two effects - a mechanical one in that IAS translates to a different vel, and the hdg != track,
816 // but also a piloting effect, in that the AI must be able to descend at a different rate in order to hit the threshold.
818 //cout << "dt = " << dt << '\n';
820 // ack - I can't remember how long a rate 1 turn is meant to take.
821 double turn_time = 60.0; // seconds - TODO - check this guess
822 double turn_circumference;
824 Point3D orthopos = ortho.ConvertToLocal(_pos); // ortho position of the plane
825 //cout << "runway elev = " << rwy.threshold_pos.elev() << ' ' << rwy.threshold_pos.elev() * SG_METER_TO_FEET << '\n';
826 //cout << "elev = " << _pos.elev() << ' ' << _pos.elev() * SG_METER_TO_FEET << '\n';
828 // HACK FOR TESTING - REMOVE
829 //cout << "Calling ExitRunway..." << endl;
830 //ExitRunway(orthopos);
835 double wind_from = wind_from_hdg->getDoubleValue();
836 double wind_speed = wind_speed_knots->getDoubleValue();
848 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
849 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
851 IAS = vel + (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
854 SetTrack(rwy.hdg); // Hands over control of turning to AIPlane
856 IAS = best_rate_of_climb_speed;
858 slope = 6.0; // Reduced it slightly since it's climbing a lot steeper than I can in the JSBSim C172.
863 // Turn to crosswind if above 700ft AND if other traffic allows
864 // (decided in FGTower and accessed through GetCrosswindConstraint(...)).
865 // According to AIM, traffic should climb to within 300ft of pattern altitude before commencing crosswind turn.
866 // TODO - At hot 'n high airports this may be 500ft AGL though - need to make this a variable.
867 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 700) {
869 if(tower->GetCrosswindConstraint(cc)) {
870 if(orthopos.y() > cc) {
871 //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
874 } else if(orthopos.y() > 1500.0) { // Added this constraint as a hack to prevent turning too early when going around.
875 // TODO - We should be doing it as a distance from takeoff end, not theshold end though.
876 //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
880 // Need to check for levelling off in case we can't turn crosswind as soon
881 // as we would like due to other traffic.
882 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
885 IAS = 80.0; // FIXME - use smooth transistion to new speed and attitude.
887 if(goAround && !goAroundCalled) {
888 if(responseCounter > 5.5) {
889 pending_transmission = plane.callsign;
890 pending_transmission += " going around";
892 goAroundCalled = true;
897 SetTrack(rwy.hdg + (90.0 * patternDirection));
898 if((track < (rwy.hdg - 89.0)) || (track > (rwy.hdg + 89.0))) {
904 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
907 IAS = 80.0; // FIXME - use smooth transistion to new speed
909 // turn 1000m out for now, taking other traffic into accout
910 if(fabs(orthopos.x()) > 900) {
912 if(tower->GetDownwindConstraint(dd)) {
913 if(fabs(orthopos.x()) > fabs(dd)) {
914 //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n';
918 //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n';
924 SetTrack(rwy.hdg - (180 * patternDirection));
925 // just in case we didn't make height on crosswind
926 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
929 IAS = 80.0; // FIXME - use smooth transistion to new speed
931 if((track < (rwy.hdg - 179.0)) || (track > (rwy.hdg + 179.0))) {
937 // just in case we didn't make height on crosswind
938 if(((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 995) && ((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET < 1015)) {
941 IAS = 90.0; // FIXME - use smooth transistion to new speed
943 if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET >= 1015) {
946 IAS = 90.0; // FIXME - use smooth transistion to new speed
948 if((orthopos.y() < 0) && (!transmitted)) {
949 TransmitPatternPositionReport();
952 if((orthopos.y() < -100) && (!descending)) {
953 //cout << "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDdddd\n";
954 // Maybe we should think about when to start descending.
955 // For now we're assuming that we aim to follow the same glidepath regardless of wind.
958 CalculateSoD((tower->GetBaseConstraint(d1) ? d1 : -1000.0), (tower->GetDownwindConstraint(d2) ? d2 : 1000.0 * patternDirection), (patternDirection ? true : false));
959 if(SoD.leg == DOWNWIND) {
960 descending = (orthopos.y() < SoD.y ? true : false);
965 slope = -5.5; // FIXME - calculate to descent at 500fpm and hit the desired point on the runway (taking wind into account as well!!)
970 // Try and arrange to turn nicely onto base
971 turn_circumference = IAS * 0.514444 * turn_time;
972 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
973 //We'll leave it as a hack with IAS for now but it needs revisiting.
974 turn_radius = turn_circumference / (2.0 * DCL_PI);
975 if(orthopos.y() < -1000.0 + turn_radius) {
976 //if(orthopos.y() < -980) {
978 if(tower->GetBaseConstraint(bb)) {
979 if(fabs(orthopos.y()) > fabs(bb)) {
980 //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n';
986 //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n';
994 SetTrack(rwy.hdg - (90 * patternDirection));
995 if(fabs(rwy.hdg - track) < 91.0) {
1001 // Base report should only be transmitted at uncontrolled airport - not towered.
1002 if(!_controlled) TransmitPatternPositionReport();
1008 // Make downwind leg position artifically large to avoid any chance of SoD being returned as
1009 // on downwind when we are already on base.
1010 CalculateSoD((tower->GetBaseConstraint(d1) ? d1 : -1000.0), (10000.0 * patternDirection), (patternDirection ? true : false));
1011 if(SoD.leg == BASE) {
1012 descending = (fabs(orthopos.y()) < fabs(SoD.y) ? true : false);
1017 slope = -5.5; // FIXME - calculate to descent at 500fpm and hit the threshold (taking wind into account as well!!)
1022 // Try and arrange to turn nicely onto final
1023 turn_circumference = IAS * 0.514444 * turn_time;
1024 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
1025 //We'll leave it as a hack with IAS for now but it needs revisiting.
1026 turn_radius = turn_circumference / (2.0 * DCL_PI);
1027 if(fabs(orthopos.x()) < (turn_radius + 50)) {
1029 transmitted = false;
1035 if(fabs(track - rwy.hdg) < 0.6) {
1037 vel = nominal_final_speed;
1041 if(goAround && responseCounter > 2.0) {
1044 IAS = best_rate_of_climb_speed;
1045 slope = 5.0; // A bit less steep than the initial climbout.
1047 goAroundCalled = false;
1053 if((!_controlled) || (!_clearedToLand)) TransmitPatternPositionReport();
1057 // Make base leg position artifically large to avoid any chance of SoD being returned as
1058 // on base or downwind when we are already on final.
1059 CalculateSoD(-10000.0, (1000.0 * patternDirection), (patternDirection ? true : false));
1060 if(SoD.leg == FINAL) {
1061 descending = (fabs(orthopos.y()) < fabs(SoD.y) ? true : false);
1066 if(orthopos.y() < -50.0) {
1067 double thesh_offset = 30.0;
1068 slope = atan((_pos.elev() - dclGetAirportElev(airportID)) / (orthopos.y() - thesh_offset)) * DCL_RADIANS_TO_DEGREES;
1069 //cout << "slope = " << slope << ", elev = " << _pos.elev() << ", apt_elev = " << dclGetAirportElev(airportID) << ", op.y = " << orthopos.y() << '\n';
1070 if(slope < -10.0) slope = -10.0;
1071 _savedSlope = slope;
1075 if(_pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) {
1076 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
1077 if(_pos.elev() < (_aip.getSGLocation()->get_cur_elev_m() + wheelOffset + 1.0)) {
1081 } else if(_pos.elev() < (_aip.getSGLocation()->get_cur_elev_m() + wheelOffset + 5.0)) {
1086 slope = _savedSlope;
1091 // Elev not determined
1092 slope = _savedSlope;
1097 slope = _savedSlope;
1103 // Try and track the extended centreline
1104 SetTrack(rwy.hdg - (0.2 * orthopos.x()));
1105 //cout << "orthopos.x() = " << orthopos.x() << " hdg = " << hdg << '\n';
1106 if(_pos.elev() < (rwy.threshold_pos.elev()+20.0+wheelOffset)) {
1107 DoGroundElev(); // Need to call it here expicitly on final since it's only called
1108 // for us in update(...) when the inAir flag is false.
1110 if(_pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) {
1113 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
1114 if((_aip.getSGLocation()->get_cur_elev_m() + wheelOffset) > _pos.elev()) {
1120 ClearTrack(); // Take over explicit track handling since AIPlane currently always banks when changing course
1122 } // else need a fallback position based on arpt elev in case ground elev determination fails?
1130 if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
1131 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
1136 // FIXME - differentiate between touch and go and full stops
1138 //cout << "Vel <= 15.0, circuitsToFly = " << circuitsToFly << endl;
1139 if(circuitsToFly <= 0) {
1140 //cout << "Calling ExitRunway..." << endl;
1141 ExitRunway(orthopos);
1144 //cout << "Taking off again..." << endl;
1155 // FIXME - at the moment this is a bit screwy
1156 // The velocity correction is applied based on the relative headings.
1157 // Then the heading is changed based on the velocity.
1158 // Which comes first, the chicken or the egg?
1159 // Does it really matter?
1161 // Apply wind to ground-relative velocity if in the air
1162 vel = IAS - (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
1163 //crab = f(track, wind, vel);
1164 // The vector we need to fly is our desired vector minus the wind vector
1165 // TODO - we probably ought to use plib's built in vector types and operations for this
1166 // ie. There's almost *certainly* a better way to do this!
1167 double gxx = vel * sin(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity x component wrt ground
1168 double gyy = vel * cos(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity y component wrt ground
1169 double wxx = wind_speed * sin((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS); // Wind velocity x component
1170 double wyy = wind_speed * cos((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS); // Wind velocity y component
1171 double axx = gxx - wxx; // Plane in-air velocity x component
1172 double ayy = gyy - wyy; // Plane in-air velocity y component
1173 // Now we want the angle between gxx and axx (which is the crab)
1174 double maga = sqrt(axx*axx + ayy*ayy);
1175 double magg = sqrt(gxx*gxx + gyy*gyy);
1176 crab = acos((axx*gxx + ayy*gyy) / (maga * magg));
1177 // At this point this works except we're getting the modulus of the angle
1178 //cout << "crab = " << crab << '\n';
1180 // Make sure both headings are in the 0->360 circle in order to get sane differences
1181 dclBoundHeading(wind_from);
1182 dclBoundHeading(track);
1183 if(track > wind_from) {
1184 if((track - wind_from) <= 180) {
1188 if((wind_from - track) >= 180) {
1192 } else { // on the ground - crab dosen't apply
1196 //cout << "X " << orthopos.x() << " Y " << orthopos.y() << " SLOPE " << slope << " elev " << _pos.elev() * SG_METER_TO_FEET << '\n';
1198 _hdg = track + crab;
1199 dist = vel * 0.514444 * dt;
1200 _pos = dclUpdatePosition(_pos, track, slope, dist);
1203 // Pattern direction is true for right, false for left
1204 void FGAILocalTraffic::CalculateSoD(double base_leg_pos, double downwind_leg_pos, bool pattern_direction) {
1205 // For now we'll ignore wind and hardwire the glide angle.
1206 double ga = 5.5; //degrees
1207 double pa = 1000.0 * SG_FEET_TO_METER; // pattern altitude in meters
1208 // FIXME - get glideslope angle and pattern altitude agl from airport details if available
1210 // For convienience, we'll have +ve versions of the input distances
1211 double blp = fabs(base_leg_pos);
1212 double dlp = fabs(downwind_leg_pos);
1214 //double turn_allowance = 150.0; // Approximate distance in meters that a 90deg corner is shortened by turned in a light plane.
1216 double stod = pa / tan(ga * DCL_DEGREES_TO_RADIANS); // distance in meters from touchdown point to start descent
1217 //cout << "Descent to start = " << stod << " meters out\n";
1218 if(stod < blp) { // Start descending on final
1220 SoD.y = stod * -1.0;
1222 } else if(stod < (blp + dlp)) { // Start descending on base leg
1225 SoD.x = (pattern_direction ? (stod - dlp) : (stod - dlp) * -1.0);
1226 } else { // Start descending on downwind leg
1228 SoD.x = (pattern_direction ? dlp : dlp * -1.0);
1229 SoD.y = (blp - (stod - (blp + dlp))) * -1.0;
1233 void FGAILocalTraffic::TransmitPatternPositionReport(void) {
1234 // airport name + "traffic" + airplane callsign + pattern direction + pattern leg + rwy + ?
1238 trns += tower->get_name();
1239 trns += " Traffic ";
1240 trns += plane.callsign;
1241 if(patternDirection == 1) {
1247 // We could probably get rid of this whole switch statement and just pass a string containing the leg from the FlyPattern function.
1248 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?
1250 // Fall through to CROSSWIND
1251 case CROSSWIND: // I don't think this case will be used here but it can't hurt to leave it in
1252 trns += "crosswind ";
1255 // Fall through to DOWNWIND
1257 trns += "downwind ";
1261 // Fall through to BASE
1266 // Fall through to FINAL
1267 case FINAL: // maybe this should include long/short final if appropriate?
1271 default: // Hopefully this won't be used
1275 trns += ConvertRwyNumToSpokenString(rwy.rwyID);
1279 // And add the airport name again
1280 trns += tower->get_name();
1282 pending_transmission = trns;
1283 ConditionalTransmit(60.0, code); // Assume a report of this leg will be invalid if we can't transmit within a minute.
1287 // TODO - Really should enumerate these coded values.
1288 void FGAILocalTraffic::ProcessCallback(int code) {
1289 // 1 - Request Departure from ground
1290 // 2 - Report at hold short
1291 // 3 - Report runway vacated
1292 // 10 - report crosswind
1293 // 11 - report downwind
1295 // 13 - report final
1297 ground->RequestDeparture(plane, this);
1298 } else if(code == 2) {
1299 tower->ContactAtHoldShort(plane, this, CIRCUIT);
1300 } else if(code == 3) {
1301 tower->ReportRunwayVacated(plane.callsign);
1302 } else if(code == 11) {
1303 tower->ReportDownwind(plane.callsign);
1304 } else if(code == 13) {
1305 tower->ReportFinal(plane.callsign);
1309 void FGAILocalTraffic::ExitRunway(Point3D orthopos) {
1310 //cout << "In ExitRunway" << endl;
1311 //cout << "Runway ID is " << rwy.ID << endl;
1313 _clearedToLand = false;
1315 node_array_type exitNodes = ground->GetExits(rwy.rwyID); //I suppose we ought to have some fallback for rwy with no defined exits?
1317 cout << "Node ID's of exits are ";
1318 for(unsigned int i=0; i<exitNodes.size(); ++i) {
1319 cout << exitNodes[i]->nodeID << ' ';
1323 if(exitNodes.size()) {
1324 //Find the next exit from orthopos.y
1326 double dist = 100000; //ie. longer than any runway in existance
1327 double backdist = 100000;
1328 node_array_iterator nItr = exitNodes.begin();
1329 node* rwyExit = *(exitNodes.begin());
1330 //int gateID; //This might want to be more persistant at some point
1331 while(nItr != exitNodes.end()) {
1332 d = ortho.ConvertToLocal((*nItr)->pos).y() - ortho.ConvertToLocal(_pos).y(); //FIXME - consider making orthopos a class variable
1339 if(fabs(d) < backdist) {
1341 //TODO - need some logic here that if we don't get a forward exit we turn round and store the backwards one
1346 ourGate = ground->GetGateNode();
1347 if(ourGate == NULL) {
1348 // Implies no available gates - what shall we do?
1349 // For now just vanish the plane - possibly we can make this more elegant in the future
1350 SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst landing at " << airportID << '\n');
1351 //_aip.setVisible(false);
1352 //cout << "Setting visible false\n";
1353 operatingState = PARKED;
1356 path = ground->GetPath(rwyExit, ourGate);
1358 cout << "path returned was:" << endl;
1359 for(unsigned int i=0; i<path.size(); ++i) {
1360 switch(path[i]->struct_type) {
1362 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
1370 taxiState = TD_INBOUND;
1373 // Something must have gone wrong with the ground network file - or there is only a rwy here and no exits defined
1374 SG_LOG(SG_ATC, SG_INFO, "No exits found by FGAILocalTraffic from runway " << rwy.rwyID << " at " << airportID << '\n');
1375 //if(airportID == "KRHV") cout << "No exits found by " << plane.callsign << " from runway " << rwy.rwyID << " at " << airportID << '\n';
1376 // What shall we do - just remove the plane from sight?
1377 _aip.setVisible(false);
1379 //cout << "Setting visible false\n";
1380 //tower->ReportRunwayVacated(plane.callsign);
1381 string trns = "Clear of the runway ";
1382 trns += plane.callsign;
1383 pending_transmission = trns;
1385 operatingState = PARKED;
1389 // Set the class variable nextTaxiNode to the next node in the path
1390 // and update taxiPathPos, the class variable path iterator position
1391 // TODO - maybe should return error codes to the calling function if we fail here
1392 void FGAILocalTraffic::GetNextTaxiNode() {
1393 //cout << "GetNextTaxiNode called " << endl;
1394 //cout << "taxiPathPos = " << taxiPathPos << endl;
1395 ground_network_path_iterator pathItr = path.begin() + taxiPathPos;
1396 if(pathItr == path.end()) {
1397 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - no more nodes in path\n");
1399 if((*pathItr)->struct_type == NODE) {
1400 //cout << "ITS A NODE" << endl;
1401 //*pathItr = new node;
1402 nextTaxiNode = (node*)*pathItr;
1406 //cout << "ITS NOT A NODE" << endl;
1407 //The first item in found must have been an arc
1408 //Assume for now that it was straight
1411 if(pathItr == path.end()) {
1412 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - path ended with an arc\n");
1413 } else if((*pathItr)->struct_type == NODE) {
1414 nextTaxiNode = (node*)*pathItr;
1417 //OOPS - two non-nodes in a row - that shouldn't happen ATM
1418 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - two non-nodes in sequence\n");
1424 // StartTaxi - set up the taxiing state - call only at the start of taxiing
1425 void FGAILocalTraffic::StartTaxi() {
1426 //cout << "StartTaxi called" << endl;
1427 operatingState = TAXIING;
1431 //Set the desired heading
1432 //Assume we are aiming for first node on path
1433 //Eventually we may need to consider the fact that we might start on a curved arc and
1434 //not be able to head directly for the first node.
1435 GetNextTaxiNode(); // sets the class variable nextTaxiNode to the next taxi node!
1436 desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos);
1437 //cout << "First taxi heading is " << desiredTaxiHeading << endl;
1440 // speed in knots, headings in degrees, radius in meters.
1441 static double TaxiTurnTowardsHeading(double current_hdg, double desired_hdg, double speed, double radius, double dt) {
1442 // wrap heading - this prevents a logic bug where the plane would just go round in circles!!
1443 while(current_hdg < 0.0) {
1444 current_hdg += 360.0;
1446 while(current_hdg > 360.0) {
1447 current_hdg -= 360.0;
1449 if(fabs(current_hdg - desired_hdg) > 0.1) {
1450 // Which is the quickest direction to turn onto heading?
1451 if(desired_hdg > current_hdg) {
1452 if((desired_hdg - current_hdg) <= 180) {
1454 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1455 // TODO - check that increments are less than the delta that we check for the right direction
1456 // Probably need to reduce convergence speed as convergence is reached
1458 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1461 if((current_hdg - desired_hdg) <= 180) {
1463 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1464 // TODO - check that increments are less than the delta that we check for the right direction
1465 // Probably need to reduce convergence speed as convergence is reached
1467 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1471 return(current_hdg);
1474 void FGAILocalTraffic::Taxi(double dt) {
1475 //cout << "Taxi called" << endl;
1476 // Logic - if we are further away from next point than turn radius then head for it
1477 // If we have reached turning point then get next point and turn onto that heading
1478 // Look out for the finish!!
1480 //Point3D orthopos = ortho.ConvertToLocal(pos); // ortho position of the plane
1481 desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos);
1483 bool lastNode = (taxiPathPos == path.size() ? true : false);
1485 //cout << "LAST NODE\n";
1488 // HACK ALERT! - for now we will taxi at constant speed for straights and turns
1490 // Remember that hdg is always equal to track when taxiing so we don't have to consider them both
1491 double dist_to_go = dclGetHorizontalSeparation(_pos, nextTaxiNode->pos); // we may be able to do this more cheaply using orthopos
1492 //cout << "dist_to_go = " << dist_to_go << endl;
1493 if((nextTaxiNode->type == GATE) && (dist_to_go <= 0.1)) {
1494 // This might be more robust to outward paths starting with a gate if we check for either
1495 // last node or TD_INBOUND ?
1497 operatingState = PARKED;
1498 } else if(((dist_to_go > taxiTurnRadius) || (nextTaxiNode->type == GATE)) && (!liningUp)){
1499 // if the turn radius is r, and speed is s, then in a time dt we turn through
1500 // ((s.dt)/(PI.r)) x 180 degrees
1501 // or alternatively (s.dt)/r radians
1502 //cout << "hdg = " << hdg << " desired taxi heading = " << desiredTaxiHeading << '\n';
1503 _hdg = TaxiTurnTowardsHeading(_hdg, desiredTaxiHeading, nominalTaxiSpeed, taxiTurnRadius, dt);
1504 double vel = nominalTaxiSpeed;
1505 //cout << "vel = " << vel << endl;
1506 double dist = vel * 0.514444 * dt;
1507 //cout << "dist = " << dist << endl;
1508 double track = _hdg;
1509 //cout << "track = " << track << endl;
1511 _pos = dclUpdatePosition(_pos, track, slope, dist);
1512 //cout << "Updated position...\n";
1513 if(_aip.getSGLocation()->get_cur_elev_m() > -9990) {
1514 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
1515 } // else don't change the elev until we get a valid ground elev again!
1516 } else if(lastNode) {
1517 if(taxiState == TD_LINING_UP) {
1518 if((!liningUp) && (dist_to_go <= taxiTurnRadius)) {
1522 _hdg = TaxiTurnTowardsHeading(_hdg, rwy.hdg, nominalTaxiSpeed, taxiTurnRadius, dt);
1523 double vel = nominalTaxiSpeed;
1524 //cout << "vel = " << vel << endl;
1525 double dist = vel * 0.514444 * dt;
1526 //cout << "dist = " << dist << endl;
1527 double track = _hdg;
1528 //cout << "track = " << track << endl;
1530 _pos = dclUpdatePosition(_pos, track, slope, dist);
1531 //cout << "Updated position...\n";
1532 if(_aip.getSGLocation()->get_cur_elev_m() > -9990) {
1533 _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
1534 } // else don't change the elev until we get a valid ground elev again!
1535 if(fabs(_hdg - rwy.hdg) <= 1.0) {
1536 operatingState = IN_PATTERN;
1542 } else if(taxiState == TD_OUTBOUND) {
1543 // Pause awaiting further instructions
1544 // and for now assume we've reached the hold-short node
1545 holdingShort = true;
1546 } // else at the moment assume TD_INBOUND always ends in a gate in which case we can ignore it
1548 // Time to turn (we've already checked it's not the end we're heading for).
1549 // set the target node to be the next node which will prompt automatically turning onto
1550 // the right heading in the stuff above, with the usual provisos applied.
1552 // For now why not just recursively call this function?
1558 // Warning - ground elev determination is CPU intensive
1559 // Either this function or the logic of how often it is called
1560 // will almost certainly change.
1561 void FGAILocalTraffic::DoGroundElev() {
1562 // It would be nice if we could set the correct tile center here in order to get a correct
1563 // answer with one call to the function, but what I tried in the two commented-out lines
1564 // below only intermittently worked, and I haven't quite groked why yet.
1565 //SGBucket buck(pos.lon(), pos.lat());
1566 //aip.getSGLocation()->set_tile_center(Point3D(buck.get_center_lon(), buck.get_center_lat(), 0.0));
1568 // Only do the proper hitlist stuff if we are within visible range of the viewer.
1569 double visibility_meters = fgGetDouble("/environment/visibility-m");
1570 FGViewer* vw = globals->get_current_view();
1571 if(dclGetHorizontalSeparation(_pos, Point3D(vw->getLongitude_deg(), vw->getLatitude_deg(), 0.0)) > visibility_meters) {
1572 _aip.getSGLocation()->set_cur_elev_m(aptElev);
1577 //globals->get_tile_mgr()->prep_ssg_nodes( acmodel_location,
1578 globals->get_tile_mgr()->prep_ssg_nodes( _aip.getSGLocation(), visibility_meters );
1579 Point3D scenery_center = globals->get_scenery()->get_center();
1580 globals->get_tile_mgr()->update( _aip.getSGLocation(), visibility_meters, (_aip.getSGLocation())->get_absolute_view_pos( scenery_center ) );
1581 // save results of update in SGLocation for fdm...
1583 //if ( globals->get_scenery()->get_cur_elev() > -9990 ) {
1584 // acmodel_location->
1585 // set_cur_elev_m( globals->get_scenery()->get_cur_elev() );
1588 // The need for this here means that at least 2 consecutive passes are needed :-(
1589 _aip.getSGLocation()->set_tile_center( globals->get_scenery()->get_next_center() );
1591 //cout << "Transform Elev is " << globals->get_scenery()->get_cur_elev() << '\n';
1592 _aip.getSGLocation()->set_cur_elev_m(globals->get_scenery()->get_cur_elev());
1593 //return(globals->get_scenery()->get_cur_elev());