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.
26 #include <simgear/scene/model/location.hxx>
28 #include <Airports/runways.hxx>
29 #include <Main/globals.hxx>
30 #include <Scenery/scenery.hxx>
31 #include <Scenery/tilemgr.hxx>
32 #include <simgear/math/point3d.hxx>
33 #include <simgear/math/sg_geodesy.hxx>
34 #include <simgear/misc/sg_path.hxx>
41 #include "AILocalTraffic.hxx"
42 #include "ATCutils.hxx"
44 FGAILocalTraffic::FGAILocalTraffic() {
45 ATC = globals->get_ATC_mgr();
47 // TODO - unhardwire this - possibly let the AI manager set the callsign
48 plane.callsign = "Trainer-two-five-charlie";
49 plane.type = GA_SINGLE;
55 //Hardwire initialisation for now - a lot of this should be read in from config eventually
57 best_rate_of_climb_speed = 70.0;
59 //nominal_climb_speed;
61 //nominal_circuit_speed;
64 nominal_descent_rate = 500.0;
65 nominal_final_speed = 65.0;
66 //nominal_approach_speed;
67 //stall_speed_landing_config;
68 nominalTaxiSpeed = 8.0;
70 wheelOffset = 1.45; // Warning - hardwired to the C172 - we need to read this in from file.
72 // Init the property nodes
73 wind_from_hdg = fgGetNode("/environment/wind-from-heading-deg", true);
74 wind_speed_knots = fgGetNode("/environment/wind-speed-kts", true);
77 taxiRequestPending = false;
78 taxiRequestCleared = false;
80 clearedToLineUp = false;
81 clearedToTakeOff = false;
82 reportReadyForDeparture = false;
84 contactGround = false;
87 FGAILocalTraffic::~FGAILocalTraffic() {
91 // Get details of the active runway
92 // It is assumed that by the time this is called the tower control and airport code will have been set up.
93 void FGAILocalTraffic::GetRwyDetails() {
94 //cout << "GetRwyDetails called" << endl;
96 rwy.rwyID = tower->GetActiveRunway();
98 // Now we need to get the threshold position and rwy heading
100 SGPath path( globals->get_fg_root() );
101 path.append( "Airports" );
102 path.append( "runways.mk4" );
103 FGRunways runways( path.c_str() );
106 bool rwyGood = runways.search(airportID, rwy.rwyID, &runway);
108 // Get the threshold position
109 hdg = runway.heading; // TODO - check - is this our heading we are setting here, and if so should we be?
110 //cout << "hdg reset to " << hdg << '\n';
111 double other_way = hdg - 180.0;
112 while(other_way <= 0.0) {
116 // move to the +l end/center of the runway
117 //cout << "Runway center is at " << runway.lon << ", " << runway.lat << '\n';
118 Point3D origin = Point3D(runway.lon, runway.lat, aptElev);
119 Point3D ref = origin;
120 double tshlon, tshlat, tshr;
121 double tolon, tolat, tor;
122 rwy.length = runway.length * SG_FEET_TO_METER;
123 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), other_way,
124 rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
125 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), hdg,
126 rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor );
127 // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user.
128 // now copy what we need out of runway into rwy
129 rwy.threshold_pos = Point3D(tshlon, tshlat, aptElev);
130 Point3D takeoff_end = Point3D(tolon, tolat, aptElev);
131 //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n';
132 //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
134 // Set the projection for the local area
135 ortho.Init(rwy.threshold_pos, rwy.hdg);
136 rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos); // should come out as zero
137 rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
139 SG_LOG(SG_ATC, SG_ALERT, "Help - can't get good runway in FGAILocalTraffic!!\n");
144 There are two possible scenarios during initialisation:
145 The first is that the user is flying towards the airport, and hence the traffic
146 could be initialised anywhere, as long as the AI planes are consistent with
148 The second is that the user has started the sim at or close to the airport, and
149 hence the traffic must be initialised with respect to the user as well as each other.
150 To a certain extent it's FGAIMgr that has to worry about this, but we need to provide
151 sufficient initialisation functionality within the plane classes to allow the manager
152 to initialy position them where and how required.
154 bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg initialLeg) {
155 //cout << "FGAILocalTraffic.Init(...) called" << endl;
156 // Hack alert - Hardwired path!!
157 string planepath = "Aircraft/c172/Models/c172-dpm.ac";
158 SGPath path = globals->get_fg_root();
159 path.append(planepath);
160 aip.init( path.str(), planepath.c_str(), globals->get_props(),
161 globals->get_sim_time_sec() );
162 aip.setVisible(false); // This will be set to true once a valid ground elevation has been determined
163 globals->get_scenery()->get_scene_graph()->addKid(aip.getSceneGraph());
165 // Find the tower frequency - this is dependent on the ATC system being initialised before the AI system
168 if(ATC->GetAirportATCDetails(airportID, &a)) {
169 if(a.tower_freq) { // Has a tower
170 tower = (FGTower*)ATC->GetATCPointer((string)airportID, TOWER); // Maybe need some error checking here
172 // Something has gone wrong - abort or carry on with un-towered operation?
175 freq = (double)tower->get_freq() / 100.0;
176 ground = tower->GetGroundPtr();
178 // Something has gone wrong :-(
179 SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a ground pointer from tower control in FGAILocalTraffic::Init() :-(");
181 } else if((initialState == PARKED) || (initialState == TAXIING)) {
182 freq = (double)ground->get_freq() / 100.0;
184 //cout << "AILocalTraffic freq is " << freq << '\n';
186 // Check CTAF, unicom etc
189 //cout << "Unable to find airport details in FGAILocalTraffic::Init()\n";
192 // Get the airport elevation
193 aptElev = dclGetAirportElev(airportID.c_str()) * SG_FEET_TO_METER;
194 //cout << "Airport elev in AILocalTraffic = " << aptElev << '\n';
195 // WARNING - we use this elev for the whole airport - some assumptions in the code
196 // might fall down with very slopey airports.
198 //cout << "In Init(), initialState = " << initialState << endl;
199 operatingState = initialState;
200 switch(operatingState) {
202 ourGate = ground->GetGateNode();
203 if(ourGate == NULL) {
204 // Implies no available gates - what shall we do?
205 // For now just vanish the plane - possibly we can make this more elegant in the future
206 SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst attempting Init at " << airportID << '\n');
214 pos.setelev(aptElev);
215 hdg = ourGate->heading;
217 // Now we've set the position we can do the ground elev
218 elevInitGood = false;
225 // FIXME - implement this case properly
226 return(false); // remove this line when fixed!
229 // For now we'll always start the in_pattern case on the threshold ready to take-off
230 // since we've got the implementation for this case already.
231 // TODO - implement proper generic in_pattern startup.
233 // Get the active runway details (and copy them into rwy)
236 // Initial position on threshold for now
237 pos.setlat(rwy.threshold_pos.lat());
238 pos.setlon(rwy.threshold_pos.lon());
239 pos.setelev(rwy.threshold_pos.elev());
242 // Now we've set the position we can do the ground elev
243 // This might not always be necessary if we implement in-air start
244 elevInitGood = false;
254 circuitsToFly = 0; // ie just fly this circuit and then stop
256 // FIXME TODO - pattern direction is still hardwired
257 patternDirection = -1; // Left
258 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
259 if(rwy.rwyID.size() == 3) {
260 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
263 operatingState = IN_PATTERN;
268 SG_LOG(SG_ATC, SG_ALERT, "Attempt to set unknown operating state in FGAILocalTraffic.Init(...)\n");
276 // Commands to do something from higher level logic
277 void FGAILocalTraffic::FlyCircuits(int numCircuits, bool tag) {
278 //cout << "FlyCircuits called" << endl;
280 switch(operatingState) {
282 circuitsToFly += numCircuits;
286 // For now we'll punt this and do nothing
289 circuitsToFly = numCircuits; // Note that one too many circuits gets flown because we only test and decrement circuitsToFly after landing
290 // thus flying one too many circuits. TODO - Need to sort this out better!
293 // Get the active runway details (and copy them into rwy)
296 // Get the takeoff node for the active runway, get a path to it and start taxiing
297 path = ground->GetPath(ourGate, rwy.rwyID);
298 if(path.size() < 2) {
299 // something has gone wrong
300 SG_LOG(SG_ATC, SG_ALERT, "Invalid path from gate to theshold in FGAILocalTraffic::FlyCircuits\n");
304 cout << "path returned was:" << endl;
305 for(unsigned int i=0; i<path.size(); ++i) {
306 switch(path[i]->struct_type) {
308 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
316 // pop the gate - we're here already!
317 path.erase(path.begin());
318 //path.erase(path.begin());
320 cout << "path after popping front is:" << endl;
321 for(unsigned int i=0; i<path.size(); ++i) {
322 switch(path[i]->struct_type) {
324 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
333 taxiState = TD_OUTBOUND;
336 // Maybe the below should be set when we get to the threshold and prepare for TO?
337 // FIXME TODO - pattern direction is still hardwired
338 patternDirection = -1; // Left
339 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
340 if(rwy.rwyID.size() == 3) {
341 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
350 // Run the internal calculations
351 void FGAILocalTraffic::Update(double dt) {
352 //cout << "A" << flush;
353 double responseTime = 10.0; // seconds - this should get more sophisticated at some point
354 responseCounter += dt;
355 if((contactTower) && (responseCounter >= 8.0)) {
356 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
357 string trns = "Tower ";
358 double f = globals->get_ATC_mgr()->GetFrequency(airportID, TOWER) / 100.0;
360 sprintf(buf, "%f", f);
363 trns += plane.callsign;
365 responseCounter = 0.0;
366 contactTower = false;
368 changeFreqType = TOWER;
371 if((changeFreq) && (responseCounter > 8.0)) {
372 switch(changeFreqType) {
374 freq = (double)tower->get_freq() / 100.0;
376 // Contact the tower, even if only virtually
378 tower->ContactAtHoldShort(plane, this, CIRCUIT);
381 freq = (double)ground->get_freq() / 100.0;
383 // And to avoid compiler warnings...
397 //cout << "." << flush;
399 switch(operatingState) {
401 //cout << "In IN_PATTERN\n";
402 if(!inAir) DoGroundElev();
404 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
405 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
406 //cout << "TAKEOFF_ROLL, POS = " << pos.lon() << ", " << pos.lat() << ", " << pos.elev() << '\n';
408 aip.setVisible(true);
409 //cout << "Making plane visible!\n";
413 FlyTrafficPattern(dt);
417 //cout << "In TAXIING\n";
418 //cout << "*" << flush;
421 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
422 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
424 aip.setVisible(true);
426 //cout << "Making plane visible!\n";
431 //cout << "," << flush;
432 if(!((holdingShort) && (!clearedToLineUp))) {
433 //cout << "|" << flush;
436 //cout << ";" << flush;
437 if((clearedToTakeOff) && (responseCounter >= 8.0)) {
438 // possible assumption that we're at the hold short here - may not always hold
439 // TODO - sort out the case where we're cleared to line-up first and then cleared to take-off on the rwy.
440 taxiState = TD_LINING_UP;
441 path = ground->GetPath(holdShortNode, rwy.rwyID);
443 cout << "path returned was:" << endl;
444 for(unsigned int i=0; i<path.size(); ++i) {
445 switch(path[i]->struct_type) {
447 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
455 clearedToTakeOff = false; // We *are* still cleared - this simply stops the response recurring!!
456 holdingShort = false;
457 string trns = "Cleared for take-off ";
458 trns += plane.callsign;
462 //cout << "^" << flush;
466 //cout << "In PARKED\n";
469 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
470 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
472 aip.setVisible(true);
474 //cout << "Making plane visible!\n";
480 if((taxiRequestPending) && (taxiRequestCleared)) {
481 //cout << "&" << flush;
482 // Get the active runway details (and copy them into rwy)
485 // Get the takeoff node for the active runway, get a path to it and start taxiing
486 path = ground->GetPathToHoldShort(ourGate, rwy.rwyID);
487 if(path.size() < 2) {
488 // something has gone wrong
489 SG_LOG(SG_ATC, SG_ALERT, "Invalid path from gate to theshold in FGAILocalTraffic::FlyCircuits\n");
493 cout << "path returned was:\n";
494 for(unsigned int i=0; i<path.size(); ++i) {
495 switch(path[i]->struct_type) {
497 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
505 path.erase(path.begin()); // pop the gate - we're here already!
506 taxiState = TD_OUTBOUND;
507 taxiRequestPending = false;
508 holdShortNode = (node*)(*(path.begin() + path.size()));
510 } else if(!taxiRequestPending) {
511 //cout << "(" << flush;
512 ground->RequestDeparture(plane, this);
513 // Do some communication
514 // airport name + tower + airplane callsign + location + request taxi for + operation type + ?
516 trns += tower->get_name();
518 trns += plane.callsign;
519 trns += " on apron parking request taxi for traffic pattern";
520 //cout << "trns = " << trns << endl;
522 taxiRequestCleared = false;
523 taxiRequestPending = true;
527 //cout << "!" << flush;
529 // Maybe the below should be set when we get to the threshold and prepare for TO?
530 // FIXME TODO - pattern direction is still hardwired
531 patternDirection = -1; // Left
532 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
533 if(rwy.rwyID.size() == 3) {
534 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
538 //cout << ")" << flush;
543 //cout << "I " << flush;
546 void FGAILocalTraffic::RegisterTransmission(int code) {
548 case 1: // taxi request cleared
549 taxiRequestCleared = true;
550 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to taxi...");
552 case 2: // contact tower
555 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact tower...");
557 case 3: // Cleared to line up
559 clearedToLineUp = true;
560 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to line-up...");
562 case 4: // cleared to take-off
564 clearedToTakeOff = true;
565 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to take-off...");
572 // Fly a traffic pattern
573 // FIXME - far too much of the mechanics of turning, rolling, accellerating, descending etc is in here.
574 // Move it out to FGAIPlane and have FlyTrafficPattern just specify what to do, not the implementation.
575 void FGAILocalTraffic::FlyTrafficPattern(double dt) {
576 // Need to differentiate between in-air (IAS governed) and on-ground (vel governed)
577 // Take-off is an interesting case - we are on the ground but takeoff speed is IAS governed.
579 static bool transmitted = false; // FIXME - this is a hack
582 // Wind has two effects - a mechanical one in that IAS translates to a different vel, and the hdg != track,
583 // but also a piloting effect, in that the AI must be able to descend at a different rate in order to hit the threshold.
585 //cout << "dt = " << dt << '\n';
587 // ack - I can't remember how long a rate 1 turn is meant to take.
588 double turn_time = 60.0; // seconds - TODO - check this guess
589 double turn_circumference;
591 Point3D orthopos = ortho.ConvertToLocal(pos); // ortho position of the plane
592 //cout << "runway elev = " << rwy.threshold_pos.elev() << ' ' << rwy.threshold_pos.elev() * SG_METER_TO_FEET << '\n';
593 //cout << "elev = " << pos.elev() << ' ' << pos.elev() * SG_METER_TO_FEET << '\n';
595 // HACK FOR TESTING - REMOVE
596 //cout << "Calling ExitRunway..." << endl;
597 //ExitRunway(orthopos);
602 double wind_from = wind_from_hdg->getDoubleValue();
603 double wind_speed = wind_speed_knots->getDoubleValue();
613 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
614 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
616 IAS = vel + (cos((hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
620 IAS = best_rate_of_climb_speed;
627 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 600) {
628 cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
633 track += (360.0 / turn_time) * dt * patternDirection;
634 Bank(25.0 * patternDirection);
635 if((track < (rwy.hdg - 89.0)) || (track > (rwy.hdg + 89.0))) {
641 track = rwy.hdg + (90.0 * patternDirection);
642 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
645 IAS = 80.0; // FIXME - use smooth transistion to new speed
647 // turn 1000m out for now
648 if(fabs(orthopos.x()) > 980) {
653 track += (360.0 / turn_time) * dt * patternDirection;
654 Bank(25.0 * patternDirection);
655 // just in case we didn't make height on crosswind
656 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
659 IAS = 80.0; // FIXME - use smooth transistion to new speed
661 if((track < (rwy.hdg - 179.0)) || (track > (rwy.hdg + 179.0))) {
669 track = rwy.hdg - (180 * patternDirection); //should tend to bring track back into the 0->360 range
670 // just in case we didn't make height on crosswind
671 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
674 IAS = 90.0; // FIXME - use smooth transistion to new speed
676 if((orthopos.y() < 0) && (!transmitted)) {
677 TransmitPatternPositionReport();
680 if(orthopos.y() < -480) {
681 slope = -4.0; // FIXME - calculate to descent at 500fpm and hit the threshold (taking wind into account as well!!)
685 if(orthopos.y() < -980) {
693 track += (360.0 / turn_time) * dt * patternDirection;
694 Bank(25.0 * patternDirection);
695 if(fabs(rwy.hdg - track) < 91.0) {
702 TransmitPatternPositionReport();
705 track = rwy.hdg - (90 * patternDirection);
706 slope = -6.0; // FIXME - calculate to descent at 500fpm and hit the threshold
708 IAS = 70.0; // FIXME - slowdown gradually
709 // Try and arrange to turn nicely onto base
710 turn_circumference = IAS * 0.514444 * turn_time;
711 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
712 //We'll leave it as a hack with IAS for now but it needs revisiting.
714 turn_radius = turn_circumference / (2.0 * DCL_PI);
715 if(fabs(orthopos.x()) < (turn_radius + 50)) {
722 track += (360.0 / turn_time) * dt * patternDirection;
723 Bank(25.0 * patternDirection);
724 if(fabs(track - rwy.hdg) < 0.6) {
726 vel = nominal_final_speed;
732 TransmitPatternPositionReport();
735 // Try and track the extended centreline
736 track = rwy.hdg - (0.2 * orthopos.x());
737 //cout << "orthopos.x() = " << orthopos.x() << " hdg = " << hdg << '\n';
738 if(pos.elev() < (rwy.threshold_pos.elev()+20.0+wheelOffset)) {
739 DoGroundElev(); // Need to call it here expicitly on final since it's only called
740 // for us in update(...) when the inAir flag is false.
742 if(pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) {
743 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
744 if((aip.getFGLocation()->get_cur_elev_m() + wheelOffset) > pos.elev()) {
750 } // else need a fallback position based on arpt elev in case ground elev determination fails?
755 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
756 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
759 double dveldt = -5.0;
761 // FIXME - differentiate between touch and go and full stops
763 //cout << "Vel <= 15.0, circuitsToFly = " << circuitsToFly << endl;
764 if(circuitsToFly <= 0) {
765 //cout << "Calling ExitRunway..." << endl;
766 ExitRunway(orthopos);
769 //cout << "Taking off again..." << endl;
778 // FIXME - at the moment this is a bit screwy
779 // The velocity correction is applied based on the relative headings.
780 // Then the heading is changed based on the velocity.
781 // Which comes first, the chicken or the egg?
782 // Does it really matter?
784 // Apply wind to ground-relative velocity if in the air
785 vel = IAS - (cos((hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
786 //crab = f(track, wind, vel);
787 // The vector we need to fly is our desired vector minus the wind vector
788 // TODO - we probably ought to use plib's built in vector types and operations for this
789 // ie. There's almost *certainly* a better way to do this!
790 double gxx = vel * sin(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity x component wrt ground
791 double gyy = vel * cos(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity y component wrt ground
792 double wxx = wind_speed * sin((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS); // Wind velocity x component
793 double wyy = wind_speed * cos((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS); // Wind velocity y component
794 double axx = gxx - wxx; // Plane in-air velocity x component
795 double ayy = gyy - wyy; // Plane in-air velocity y component
796 // Now we want the angle between gxx and axx (which is the crab)
797 double maga = sqrt(axx*axx + ayy*ayy);
798 double magg = sqrt(gxx*gxx + gyy*gyy);
799 crab = acos((axx*gxx + ayy*gyy) / (maga * magg));
800 // At this point this works except we're getting the modulus of the angle
801 //cout << "crab = " << crab << '\n';
803 // Make sure both headings are in the 0->360 circle in order to get sane differences
804 dclBoundHeading(wind_from);
805 dclBoundHeading(track);
806 if(track > wind_from) {
807 if((track - wind_from) <= 180) {
811 if((wind_from - track) >= 180) {
815 } else { // on the ground - crab dosen't apply
820 dist = vel * 0.514444 * dt;
821 pos = dclUpdatePosition(pos, track, slope, dist);
824 void FGAILocalTraffic::TransmitPatternPositionReport(void) {
825 // airport name + "traffic" + airplane callsign + pattern direction + pattern leg + rwy + ?
828 trns += tower->get_name();
830 trns += plane.callsign;
831 if(patternDirection == 1) {
837 // We could probably get rid of this whole switch statement and just pass a string containing the leg from the FlyPattern function.
838 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?
840 // Fall through to CROSSWIND
841 case CROSSWIND: // I don't think this case will be used here but it can't hurt to leave it in
842 trns += "crosswind ";
845 // Fall through to DOWNWIND
850 // Fall through to BASE
855 // Fall through to FINAL
856 case FINAL: // maybe this should include long/short final if appropriate?
859 default: // Hopefully this won't be used
863 // FIXME - I've hardwired the runway call as well!! (We could work this out from rwy heading and mag deviation)
864 trns += ConvertRwyNumToSpokenString(1);
866 // And add the airport name again
867 trns += tower->get_name();
872 void FGAILocalTraffic::ExitRunway(Point3D orthopos) {
873 //cout << "In ExitRunway" << endl;
874 //cout << "Runway ID is " << rwy.ID << endl;
875 node_array_type exitNodes = ground->GetExits(rwy.rwyID); //I suppose we ought to have some fallback for rwy with no defined exits?
877 cout << "Node ID's of exits are ";
878 for(unsigned int i=0; i<exitNodes.size(); ++i) {
879 cout << exitNodes[i]->nodeID << ' ';
883 if(exitNodes.size()) {
884 //Find the next exit from orthopos.y
886 double dist = 100000; //ie. longer than any runway in existance
887 double backdist = 100000;
888 node_array_iterator nItr = exitNodes.begin();
889 node* rwyExit = *(exitNodes.begin());
890 //int gateID; //This might want to be more persistant at some point
891 while(nItr != exitNodes.end()) {
892 d = ortho.ConvertToLocal((*nItr)->pos).y() - ortho.ConvertToLocal(pos).y(); //FIXME - consider making orthopos a class variable
899 if(fabs(d) < backdist) {
901 //TODO - need some logic here that if we don't get a forward exit we turn round and store the backwards one
906 ourGate = ground->GetGateNode();
907 if(ourGate == NULL) {
908 // Implies no available gates - what shall we do?
909 // For now just vanish the plane - possibly we can make this more elegant in the future
910 SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst landing at " << airportID << '\n');
911 aip.setVisible(false);
912 operatingState = PARKED;
915 path = ground->GetPath(rwyExit, ourGate);
917 cout << "path returned was:" << endl;
918 for(unsigned int i=0; i<path.size(); ++i) {
919 switch(path[i]->struct_type) {
921 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
929 taxiState = TD_INBOUND;
932 // Something must have gone wrong with the ground network file - or there is only a rwy here and no exits defined
933 SG_LOG(SG_ATC, SG_ALERT, "No exits found by FGAILocalTraffic from runway " << rwy.rwyID << " at " << airportID << '\n');
934 // What shall we do - just remove the plane from sight?
935 aip.setVisible(false);
936 operatingState = PARKED;
940 // Set the class variable nextTaxiNode to the next node in the path
941 // and update taxiPathPos, the class variable path iterator position
942 // TODO - maybe should return error codes to the calling function if we fail here
943 void FGAILocalTraffic::GetNextTaxiNode() {
944 //cout << "GetNextTaxiNode called " << endl;
945 //cout << "taxiPathPos = " << taxiPathPos << endl;
946 ground_network_path_iterator pathItr = path.begin() + taxiPathPos;
947 if(pathItr == path.end()) {
948 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - no more nodes in path\n");
950 if((*pathItr)->struct_type == NODE) {
951 //cout << "ITS A NODE" << endl;
952 //*pathItr = new node;
953 nextTaxiNode = (node*)*pathItr;
957 //cout << "ITS NOT A NODE" << endl;
958 //The first item in found must have been an arc
959 //Assume for now that it was straight
962 if(pathItr == path.end()) {
963 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - path ended with an arc\n");
964 } else if((*pathItr)->struct_type == NODE) {
965 nextTaxiNode = (node*)*pathItr;
968 //OOPS - two non-nodes in a row - that shouldn't happen ATM
969 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - two non-nodes in sequence\n");
975 // StartTaxi - set up the taxiing state - call only at the start of taxiing
976 void FGAILocalTraffic::StartTaxi() {
977 //cout << "StartTaxi called" << endl;
978 operatingState = TAXIING;
981 //Set the desired heading
982 //Assume we are aiming for first node on path
983 //Eventually we may need to consider the fact that we might start on a curved arc and
984 //not be able to head directly for the first node.
985 GetNextTaxiNode(); // sets the class variable nextTaxiNode to the next taxi node!
986 desiredTaxiHeading = GetHeadingFromTo(pos, nextTaxiNode->pos);
987 //cout << "First taxi heading is " << desiredTaxiHeading << endl;
990 // speed in knots, headings in degrees, radius in meters.
991 static double TaxiTurnTowardsHeading(double current_hdg, double desired_hdg, double speed, double radius, double dt) {
992 // wrap heading - this prevents a logic bug where the plane would just go round in circles!!
993 while(current_hdg < 0.0) {
994 current_hdg += 360.0;
996 while(current_hdg > 360.0) {
997 current_hdg -= 360.0;
999 if(fabs(current_hdg - desired_hdg) > 0.1) {
1000 // Which is the quickest direction to turn onto heading?
1001 if(desired_hdg > current_hdg) {
1002 if((desired_hdg - current_hdg) <= 180) {
1004 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1005 // TODO - check that increments are less than the delta that we check for the right direction
1006 // Probably need to reduce convergence speed as convergence is reached
1008 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1011 if((current_hdg - desired_hdg) <= 180) {
1013 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1014 // TODO - check that increments are less than the delta that we check for the right direction
1015 // Probably need to reduce convergence speed as convergence is reached
1017 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1021 return(current_hdg);
1024 void FGAILocalTraffic::Taxi(double dt) {
1025 //cout << "Taxi called" << endl;
1026 // Logic - if we are further away from next point than turn radius then head for it
1027 // If we have reached turning point then get next point and turn onto that heading
1028 // Look out for the finish!!
1030 //Point3D orthopos = ortho.ConvertToLocal(pos); // ortho position of the plane
1031 desiredTaxiHeading = GetHeadingFromTo(pos, nextTaxiNode->pos);
1033 bool lastNode = (taxiPathPos == path.size() ? true : false);
1035 //cout << "LAST NODE\n";
1038 // HACK ALERT! - for now we will taxi at constant speed for straights and turns
1040 // Remember that hdg is always equal to track when taxiing so we don't have to consider them both
1041 double dist_to_go = dclGetHorizontalSeparation(pos, nextTaxiNode->pos); // we may be able to do this more cheaply using orthopos
1042 //cout << "dist_to_go = " << dist_to_go << endl;
1043 if((nextTaxiNode->type == GATE) && (dist_to_go <= 0.1)) {
1044 // This might be more robust to outward paths starting with a gate if we check for either
1045 // last node or TD_INBOUND ?
1047 operatingState = PARKED;
1048 } else if(((dist_to_go > taxiTurnRadius) || (nextTaxiNode->type == GATE)) && (!liningUp)){
1049 // if the turn radius is r, and speed is s, then in a time dt we turn through
1050 // ((s.dt)/(PI.r)) x 180 degrees
1051 // or alternatively (s.dt)/r radians
1052 //cout << "hdg = " << hdg << " desired taxi heading = " << desiredTaxiHeading << '\n';
1053 hdg = TaxiTurnTowardsHeading(hdg, desiredTaxiHeading, nominalTaxiSpeed, taxiTurnRadius, dt);
1054 double vel = nominalTaxiSpeed;
1055 //cout << "vel = " << vel << endl;
1056 double dist = vel * 0.514444 * dt;
1057 //cout << "dist = " << dist << endl;
1059 //cout << "track = " << track << endl;
1061 pos = dclUpdatePosition(pos, track, slope, dist);
1062 //cout << "Updated position...\n";
1063 if(aip.getFGLocation()->get_cur_elev_m() > -9990) {
1064 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
1065 } // else don't change the elev until we get a valid ground elev again!
1066 } else if(lastNode) {
1067 if(taxiState == TD_LINING_UP) {
1068 if((!liningUp) && (dist_to_go <= taxiTurnRadius)) {
1072 hdg = TaxiTurnTowardsHeading(hdg, rwy.hdg, nominalTaxiSpeed, taxiTurnRadius, dt);
1073 double vel = nominalTaxiSpeed;
1074 //cout << "vel = " << vel << endl;
1075 double dist = vel * 0.514444 * dt;
1076 //cout << "dist = " << dist << endl;
1078 //cout << "track = " << track << endl;
1080 pos = dclUpdatePosition(pos, track, slope, dist);
1081 //cout << "Updated position...\n";
1082 if(aip.getFGLocation()->get_cur_elev_m() > -9990) {
1083 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
1084 } // else don't change the elev until we get a valid ground elev again!
1085 if(fabs(hdg - rwy.hdg) <= 1.0) {
1086 operatingState = IN_PATTERN;
1092 } else if(taxiState == TD_OUTBOUND) {
1093 // Pause awaiting further instructions
1094 // and for now assume we've reached the hold-short node
1095 holdingShort = true;
1096 } // else at the moment assume TD_INBOUND always ends in a gate in which case we can ignore it
1098 // Time to turn (we've already checked it's not the end we're heading for).
1099 // set the target node to be the next node which will prompt automatically turning onto
1100 // the right heading in the stuff above, with the usual provisos applied.
1102 // For now why not just recursively call this function?
1108 // Warning - ground elev determination is CPU intensive
1109 // Either this function or the logic of how often it is called
1110 // will almost certainly change.
1111 void FGAILocalTraffic::DoGroundElev() {
1113 // It would be nice if we could set the correct tile center here in order to get a correct
1114 // answer with one call to the function, but what I tried in the two commented-out lines
1115 // below only intermittently worked, and I haven't quite groked why yet.
1116 //SGBucket buck(pos.lon(), pos.lat());
1117 //aip.getFGLocation()->set_tile_center(Point3D(buck.get_center_lon(), buck.get_center_lat(), 0.0));
1119 double visibility_meters = fgGetDouble("/environment/visibility-m");
1120 //globals->get_tile_mgr()->prep_ssg_nodes( acmodel_location,
1121 globals->get_tile_mgr()->prep_ssg_nodes( aip.getFGLocation(), visibility_meters );
1122 Point3D scenery_center = globals->get_scenery()->get_center();
1123 globals->get_tile_mgr()->update( aip.getFGLocation(), visibility_meters, (aip.getFGLocation())->get_absolute_view_pos( scenery_center ) );
1124 // save results of update in FGLocation for fdm...
1126 //if ( globals->get_scenery()->get_cur_elev() > -9990 ) {
1127 // acmodel_location->
1128 // set_cur_elev_m( globals->get_scenery()->get_cur_elev() );
1131 // The need for this here means that at least 2 consecutive passes are needed :-(
1132 aip.getFGLocation()->set_tile_center( globals->get_scenery()->get_next_center() );
1134 //cout << "Transform Elev is " << globals->get_scenery()->get_cur_elev() << '\n';
1135 aip.getFGLocation()->set_cur_elev_m(globals->get_scenery()->get_cur_elev());
1136 //return(globals->get_scenery()->get_cur_elev());