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 <Airports/runways.hxx>
27 #include <Main/globals.hxx>
28 #include <Main/location.hxx>
29 #include <Scenery/scenery.hxx>
30 #include <Scenery/tilemgr.hxx>
31 #include <simgear/math/point3d.hxx>
32 #include <simgear/math/sg_geodesy.hxx>
33 #include <simgear/misc/sg_path.hxx>
40 #include "AILocalTraffic.hxx"
41 #include "ATCutils.hxx"
43 FGAILocalTraffic::FGAILocalTraffic() {
44 ATC = globals->get_ATC_mgr();
50 //Hardwire initialisation for now - a lot of this should be read in from config eventually
52 best_rate_of_climb_speed = 70.0;
54 //nominal_climb_speed;
56 //nominal_circuit_speed;
59 nominal_descent_rate = 500.0;
60 nominal_final_speed = 65.0;
61 //nominal_approach_speed;
62 //stall_speed_landing_config;
63 nominalTaxiSpeed = 8.0;
65 wheelOffset = 1.45; // Warning - hardwired to the C172 - we need to read this in from file.
67 // Init the property nodes
68 wind_from_hdg = fgGetNode("/environment/wind-from-heading-deg", true);
69 wind_speed_knots = fgGetNode("/environment/wind-speed-kts", true);
74 FGAILocalTraffic::~FGAILocalTraffic() {
78 // Get details of the active runway
79 // This is a private internal function and it is assumed that by the
80 // time it is called the tower control and airport code will have been set up.
81 void FGAILocalTraffic::GetRwyDetails() {
82 //cout << "GetRwyDetails called" << endl;
84 // Based on the airport-id and wind get the active runway
85 SGPath path( globals->get_fg_root() );
86 path.append( "Airports" );
87 path.append( "runways.mk4" );
88 FGRunways runways( path.c_str() );
91 double hdg = wind_from_hdg->getDoubleValue();
92 double speed = wind_speed_knots->getDoubleValue();
93 hdg = (speed == 0.0 ? 270.0 : hdg);
94 //cout << "Heading = " << hdg << '\n';
97 bool rwyGood = runways.search(airportID, int(hdg), &runway);
99 // Get the threshold position
100 hdg = runway.heading;
101 //cout << "hdg reset to " << hdg << '\n';
102 double other_way = hdg - 180.0;
103 while(other_way <= 0.0) {
107 // move to the +l end/center of the runway
108 //cout << "Runway center is at " << runway.lon << ", " << runway.lat << '\n';
109 Point3D origin = Point3D(runway.lon, runway.lat, aptElev);
110 Point3D ref = origin;
111 double tshlon, tshlat, tshr;
112 double tolon, tolat, tor;
113 rwy.length = runway.length * SG_FEET_TO_METER;
114 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), other_way,
115 rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
116 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), hdg,
117 rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor );
118 // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user.
119 // now copy what we need out of runway into rwy
120 rwy.threshold_pos = Point3D(tshlon, tshlat, aptElev);
121 Point3D takeoff_end = Point3D(tolon, tolat, aptElev);
122 //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n';
123 //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
125 rwy.rwyID = runway.rwy_no;
126 // Set the projection for the local area
127 ortho.Init(rwy.threshold_pos, rwy.hdg);
128 rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos); // should come out as zero
129 rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
130 rwy.mag_var = 14.0; // TODO - remove this last hardwired bit!!
131 //(Although I don't think we even use the magvar any more?)
132 rwy.mag_hdg = rwy.hdg - rwy.mag_var;
133 rwy.ID = (int)rwy.mag_hdg / 10;
134 //cout << "rwy.ID = " << rwy.ID << '\n';
136 SG_LOG(SG_GENERAL, SG_ALERT, "Help - can't get good runway in FGAILocalTraffic!!\n");
141 There are two possible scenarios during initialisation:
142 The first is that the user is flying towards the airport, and hence the traffic
143 could be initialised anywhere, as long as the AI planes are consistent with
145 The second is that the user has started the sim at or close to the airport, and
146 hence the traffic must be initialised with respect to the user as well as each other.
147 To a certain extent it's FGAIMgr that has to worry about this, but we need to provide
148 sufficient initialisation functionality within the plane classes to allow the manager
149 to initialy position them where and how required.
151 bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg initialLeg) {
152 //cout << "FGAILocalTraffic.Init(...) called" << endl;
153 // Hack alert - Hardwired path!!
154 string planepath = "Aircraft/c172/Models/c172-dpm.ac";
155 SGPath path = globals->get_fg_root();
156 path.append(planepath);
157 aip.init(planepath.c_str());
158 aip.setVisible(false); // This will be set to true once a valid ground elevation has been determined
159 globals->get_scenery()->get_scene_graph()->addKid(aip.getSceneGraph());
161 // Find the tower frequency - this is dependent on the ATC system being initialised before the AI system
164 if(ATC->GetAirportATCDetails(airportID, &a)) {
165 if(a.tower_freq) { // Has a tower
166 tower = (FGTower*)ATC->GetATCPointer((string)airportID, TOWER); // Maybe need some error checking here
168 // Something has gone wrong - abort or carry on with un-towered operation?
171 freq = (double)tower->get_freq() / 100.0;
173 // Check CTAF, unicom etc
175 if(a.ground_freq) { // Ground control
176 ground = (FGGround*)ATC->GetATCPointer((string)airportID, GROUND); // Maybe need some error checking here
178 // Something has gone wrong :-(
179 cout << "ERROR - ground has frequency but can't get ground pointer :-(\n";
182 freq = (double)tower->get_freq() / 100.0;
184 // Initialise ground anyway to do the shortest path stuff!
185 // This is a bit of a hack - might need to be altered sometime, but
186 // in theory AILocalTraffic doesn't get called unless we have a logical
187 // network for ground to use so it should work for now!
188 ground = new FGGround(airportID); // TODO - ought to set a flag saying that we're responsible
189 // for deleting ground in this instance, since normally we're not.
193 //cout << "Unable to find airport details in FGAILocalTraffic::Init()\n";
196 // Get the airport elevation
197 aptElev = dclGetAirportElev(airportID.c_str()) * SG_FEET_TO_METER;
198 //cout << "Airport elev in AILocalTraffic = " << aptElev << '\n';
199 // WARNING - we use this elev for the whole airport - some assumptions in the code
200 // might fall down with very slopey airports.
202 //cout << "In Init(), initialState = " << initialState << '\n';
203 operatingState = initialState;
204 switch(operatingState) {
206 ourGate = ground->GetGateNode();
207 if(ourGate == NULL) {
208 // Implies no available gates - what shall we do?
209 // For now just vanish the plane - possibly we can make this more elegant in the future
210 SG_LOG(SG_GENERAL, SG_ALERT, "No gate found by FGAILocalTraffic whilst attempting Init at " << airportID << '\n');
218 pos.setelev(aptElev);
219 hdg = ourGate->heading;
221 // Now we've set the position we can do the ground elev
222 elevInitGood = false;
229 // FIXME - implement this case properly
230 return(false); // remove this line when fixed!
233 // For now we'll always start the in_pattern case on the threshold ready to take-off
234 // since we've got the implementation for this case already.
235 // TODO - implement proper generic in_pattern startup.
237 // Get the active runway details (and copy them into rwy)
240 // Initial position on threshold for now
241 pos.setlat(rwy.threshold_pos.lat());
242 pos.setlon(rwy.threshold_pos.lon());
243 pos.setelev(rwy.threshold_pos.elev());
246 // Now we've set the position we can do the ground elev
247 // This might not always be necessary if we implement in-air start
248 elevInitGood = false;
258 circuitsToFly = 0; // ie just fly this circuit and then stop
260 // FIXME TODO - pattern direction is still hardwired
261 patternDirection = -1; // Left
262 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
263 if(rwy.rwyID.size() == 3) {
264 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
267 operatingState = IN_PATTERN;
272 SG_LOG(SG_GENERAL, SG_ALERT, "Attempt to set unknown operating state in FGAILocalTraffic.Init(...)\n");
280 // Commands to do something from higher level logic
281 void FGAILocalTraffic::FlyCircuits(int numCircuits, bool tag) {
282 //cout << "FlyCircuits called" << endl;
284 switch(operatingState) {
286 circuitsToFly += numCircuits;
290 // For now we'll punt this and do nothing
293 circuitsToFly = numCircuits - 1; // Hack (-1) because we only test and decrement circuitsToFly after landing
294 // thus flying one too many circuits. TODO - Need to sort this out better!
297 // Get the active runway details (and copy them into rwy)
300 // Get the takeoff node for the active runway, get a path to it and start taxiing
301 path = ground->GetPath(ourGate, rwy.rwyID);
302 if(path.size() < 2) {
303 // something has gone wrong
304 SG_LOG(SG_GENERAL, SG_ALERT, "Invalid path from gate to theshold in FGAILocalTraffic::FlyCircuits\n");
308 cout << "path returned was:" << endl;
309 for(unsigned int i=0; i<path.size(); ++i) {
310 switch(path[i]->struct_type) {
312 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
320 // pop the gate - we're here already!
321 path.erase(path.begin());
322 //path.erase(path.begin());
324 cout << "path after popping front is:" << endl;
325 for(unsigned int i=0; i<path.size(); ++i) {
326 switch(path[i]->struct_type) {
328 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
337 taxiState = TD_OUTBOUND;
340 // Maybe the below should be set when we get to the threshold and prepare for TO?
341 // FIXME TODO - pattern direction is still hardwired
342 patternDirection = -1; // Left
343 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
344 if(rwy.rwyID.size() == 3) {
345 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
353 // Run the internal calculations
354 void FGAILocalTraffic::Update(double dt) {
355 switch(operatingState) {
357 //cout << "In IN_PATTERN\n";
358 if(!inAir) DoGroundElev();
360 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
361 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
362 //cout << "TAKEOFF_ROLL, POS = " << pos.lon() << ", " << pos.lat() << ", " << pos.elev() << '\n';
364 aip.setVisible(true);
365 //cout << "Making plane visible!\n";
369 FlyTrafficPattern(dt);
373 //cout << "In TAXIING\n";
376 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
377 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
379 aip.setVisible(true);
381 //cout << "Making plane visible!\n";
390 //cout << "In PARKED\n";
393 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
394 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
396 aip.setVisible(true);
398 //cout << "Making plane visible!\n";
410 // Fly a traffic pattern
411 // FIXME - far too much of the mechanics of turning, rolling, accellerating, descending etc is in here.
412 // Move it out to FGAIPlane and have FlyTrafficPattern just specify what to do, not the implementation.
413 void FGAILocalTraffic::FlyTrafficPattern(double dt) {
414 // Need to differentiate between in-air (IAS governed) and on-ground (vel governed)
415 // Take-off is an interesting case - we are on the ground but takeoff speed is IAS governed.
417 static bool transmitted = false; // FIXME - this is a hack
420 // Wind has two effects - a mechanical one in that IAS translates to a different vel, and the hdg != track,
421 // but also a piloting effect, in that the AI must be able to descend at a different rate in order to hit the threshold.
423 //cout << "dt = " << dt << '\n';
425 // ack - I can't remember how long a rate 1 turn is meant to take.
426 double turn_time = 60.0; // seconds - TODO - check this guess
427 double turn_circumference;
429 Point3D orthopos = ortho.ConvertToLocal(pos); // ortho position of the plane
430 //cout << "runway elev = " << rwy.threshold_pos.elev() << ' ' << rwy.threshold_pos.elev() * SG_METER_TO_FEET << '\n';
431 //cout << "elev = " << pos.elev() << ' ' << pos.elev() * SG_METER_TO_FEET << '\n';
433 // HACK FOR TESTING - REMOVE
434 //cout << "Calling ExitRunway..." << endl;
435 //ExitRunway(orthopos);
440 double wind_from = wind_from_hdg->getDoubleValue();
441 double wind_speed = wind_speed_knots->getDoubleValue();
451 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
452 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
454 IAS = vel + (cos((hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
458 IAS = best_rate_of_climb_speed;
465 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 600) {
470 track += (360.0 / turn_time) * dt * patternDirection;
471 Bank(25.0 * patternDirection);
472 if((track < (rwy.hdg - 89.0)) || (track > (rwy.hdg + 89.0))) {
478 track = rwy.hdg + (90.0 * patternDirection);
479 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
482 IAS = 80.0; // FIXME - use smooth transistion to new speed
484 // turn 1000m out for now
485 if(fabs(orthopos.x()) > 980) {
490 track += (360.0 / turn_time) * dt * patternDirection;
491 Bank(25.0 * patternDirection);
492 // just in case we didn't make height on crosswind
493 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
496 IAS = 80.0; // FIXME - use smooth transistion to new speed
498 if((track < (rwy.hdg - 179.0)) || (track > (rwy.hdg + 179.0))) {
506 track = rwy.hdg - (180 * patternDirection); //should tend to bring track back into the 0->360 range
507 // just in case we didn't make height on crosswind
508 if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
511 IAS = 90.0; // FIXME - use smooth transistion to new speed
513 if((orthopos.y() < 0) && (!transmitted)) {
514 TransmitPatternPositionReport();
517 if(orthopos.y() < -480) {
518 slope = -4.0; // FIXME - calculate to descent at 500fpm and hit the threshold (taking wind into account as well!!)
522 if(orthopos.y() < -980) {
530 track += (360.0 / turn_time) * dt * patternDirection;
531 Bank(25.0 * patternDirection);
532 if(fabs(rwy.hdg - track) < 91.0) {
539 TransmitPatternPositionReport();
542 track = rwy.hdg - (90 * patternDirection);
543 slope = -6.0; // FIXME - calculate to descent at 500fpm and hit the threshold
545 IAS = 70.0; // FIXME - slowdown gradually
546 // Try and arrange to turn nicely onto base
547 turn_circumference = IAS * 0.514444 * turn_time;
548 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
549 //We'll leave it as a hack with IAS for now but it needs revisiting.
551 turn_radius = turn_circumference / (2.0 * DCL_PI);
552 if(fabs(orthopos.x()) < (turn_radius + 50)) {
559 track += (360.0 / turn_time) * dt * patternDirection;
560 Bank(25.0 * patternDirection);
561 if(fabs(track - rwy.hdg) < 0.6) {
563 vel = nominal_final_speed;
569 TransmitPatternPositionReport();
572 // Try and track the extended centreline
573 track = rwy.hdg - (0.2 * orthopos.x());
574 //cout << "orthopos.x() = " << orthopos.x() << " hdg = " << hdg << '\n';
575 if(pos.elev() < (rwy.threshold_pos.elev()+20.0+wheelOffset)) {
576 DoGroundElev(); // Need to call it here expicitly on final since it's only called
577 // for us in update(...) when the inAir flag is false.
579 if(pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) {
580 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
581 if((aip.getFGLocation()->get_cur_elev_m() + wheelOffset) > pos.elev()) {
587 } // else need a fallback position based on arpt elev in case ground elev determination fails?
592 if(aip.getFGLocation()->get_cur_elev_m() > -9990.0) {
593 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
596 double dveldt = -5.0;
598 // FIXME - differentiate between touch and go and full stops
600 //cout << "Vel <= 15.0, circuitsToFly = " << circuitsToFly << endl;
601 if(circuitsToFly <= 0) {
602 //cout << "Calling ExitRunway..." << endl;
603 ExitRunway(orthopos);
606 //cout << "Taking off again..." << endl;
615 // FIXME - at the moment this is a bit screwy
616 // The velocity correction is applied based on the relative headings.
617 // Then the heading is changed based on the velocity.
618 // Which comes first, the chicken or the egg?
619 // Does it really matter?
621 // Apply wind to ground-relative velocity if in the air
622 vel = IAS - (cos((hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
623 //crab = f(track, wind, vel);
624 // The vector we need to fly is our desired vector minus the wind vector
625 // TODO - we probably ought to use plib's built in vector types and operations for this
626 // ie. There's almost *certainly* a better way to do this!
627 double gxx = vel * sin(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity x component wrt ground
628 double gyy = vel * cos(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity y component wrt ground
629 double wxx = wind_speed * sin((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS); // Wind velocity x component
630 double wyy = wind_speed * cos((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS); // Wind velocity y component
631 double axx = gxx - wxx; // Plane in-air velocity x component
632 double ayy = gyy - wyy; // Plane in-air velocity y component
633 // Now we want the angle between gxx and axx (which is the crab)
634 double maga = sqrt(axx*axx + ayy*ayy);
635 double magg = sqrt(gxx*gxx + gyy*gyy);
636 crab = acos((axx*gxx + ayy*gyy) / (maga * magg));
637 // At this point this works except we're getting the modulus of the angle
638 //cout << "crab = " << crab << '\n';
640 // Make sure both headings are in the 0->360 circle in order to get sane differences
641 dclBoundHeading(wind_from);
642 dclBoundHeading(track);
643 if(track > wind_from) {
644 if((track - wind_from) <= 180) {
648 if((wind_from - track) >= 180) {
652 } else { // on the ground - crab dosen't apply
657 dist = vel * 0.514444 * dt;
658 pos = dclUpdatePosition(pos, track, slope, dist);
661 void FGAILocalTraffic::TransmitPatternPositionReport(void) {
662 // airport name + "traffic" + airplane callsign + pattern direction + pattern leg + rwy + ?
665 trns += tower->get_name();
667 // FIXME - add the callsign to the class variables
668 trns += "Trainer-two-five-charlie ";
669 if(patternDirection == 1) {
675 // We could probably get rid of this whole switch statement and just pass a string containing the leg from the FlyPattern function.
676 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?
678 // Fall through to CROSSWIND
679 case CROSSWIND: // I don't think this case will be used here but it can't hurt to leave it in
680 trns += "crosswind ";
683 // Fall through to DOWNWIND
688 // Fall through to BASE
693 // Fall through to FINAL
694 case FINAL: // maybe this should include long/short final if appropriate?
697 default: // Hopefully this won't be used
701 // FIXME - I've hardwired the runway call as well!! (We could work this out from rwy heading and mag deviation)
702 trns += ConvertRwyNumToSpokenString(1);
704 // And add the airport name again
705 trns += tower->get_name();
710 void FGAILocalTraffic::ExitRunway(Point3D orthopos) {
711 //cout << "In ExitRunway" << endl;
712 //cout << "Runway ID is " << rwy.ID << endl;
713 node_array_type exitNodes = ground->GetExits(rwy.ID); //I suppose we ought to have some fallback for rwy with no defined exits?
715 cout << "Node ID's of exits are ";
716 for(unsigned int i=0; i<exitNodes.size(); ++i) {
717 cout << exitNodes[i]->nodeID << ' ';
721 if(exitNodes.size()) {
722 //Find the next exit from orthopos.y
724 double dist = 100000; //ie. longer than any runway in existance
725 double backdist = 100000;
726 node_array_iterator nItr = exitNodes.begin();
727 node* rwyExit = *(exitNodes.begin());
728 //int gateID; //This might want to be more persistant at some point
729 while(nItr != exitNodes.end()) {
730 d = ortho.ConvertToLocal((*nItr)->pos).y() - ortho.ConvertToLocal(pos).y(); //FIXME - consider making orthopos a class variable
737 if(fabs(d) < backdist) {
739 //TODO - need some logic here that if we don't get a forward exit we turn round and store the backwards one
744 ourGate = ground->GetGateNode();
745 if(ourGate == NULL) {
746 // Implies no available gates - what shall we do?
747 // For now just vanish the plane - possibly we can make this more elegant in the future
748 SG_LOG(SG_GENERAL, SG_ALERT, "No gate found by FGAILocalTraffic whilst landing at " << airportID << '\n');
749 aip.setVisible(false);
750 operatingState = PARKED;
753 path = ground->GetPath(rwyExit, ourGate);
755 cout << "path returned was:" << endl;
756 for(unsigned int i=0; i<path.size(); ++i) {
757 switch(path[i]->struct_type) {
759 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
767 taxiState = TD_INBOUND;
770 // Something must have gone wrong with the ground network file - or there is only a rwy here and no exits defined
771 SG_LOG(SG_GENERAL, SG_ALERT, "No exits found by FGAILocalTraffic from runway " << rwy.ID << " at " << airportID << '\n');
772 // What shall we do - just remove the plane from sight?
773 aip.setVisible(false);
774 operatingState = PARKED;
778 // Set the class variable nextTaxiNode to the next node in the path
779 // and update taxiPathPos, the class variable path iterator position
780 // TODO - maybe should return error codes to the calling function if we fail here
781 void FGAILocalTraffic::GetNextTaxiNode() {
782 //cout << "GetNextTaxiNode called " << endl;
783 //cout << "taxiPathPos = " << taxiPathPos << endl;
784 ground_network_path_iterator pathItr = path.begin() + taxiPathPos;
785 if(pathItr == path.end()) {
786 SG_LOG(SG_GENERAL, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - no more nodes in path\n");
788 if((*pathItr)->struct_type == NODE) {
789 //cout << "ITS A NODE" << endl;
790 //*pathItr = new node;
791 nextTaxiNode = (node*)*pathItr;
795 //cout << "ITS NOT A NODE" << endl;
796 //The first item in found must have been an arc
797 //Assume for now that it was straight
800 if(pathItr == path.end()) {
801 SG_LOG(SG_GENERAL, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - path ended with an arc\n");
802 } else if((*pathItr)->struct_type == NODE) {
803 nextTaxiNode = (node*)*pathItr;
806 //OOPS - two non-nodes in a row - that shouldn't happen ATM
807 SG_LOG(SG_GENERAL, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - two non-nodes in sequence\n");
813 // StartTaxi - set up the taxiing state - call only at the start of taxiing
814 void FGAILocalTraffic::StartTaxi() {
815 //cout << "StartTaxi called" << endl;
816 operatingState = TAXIING;
819 //Set the desired heading
820 //Assume we are aiming for first node on path
821 //Eventually we may need to consider the fact that we might start on a curved arc and
822 //not be able to head directly for the first node.
823 GetNextTaxiNode(); // sets the class variable nextTaxiNode to the next taxi node!
824 desiredTaxiHeading = GetHeadingFromTo(pos, nextTaxiNode->pos);
825 //cout << "First taxi heading is " << desiredTaxiHeading << endl;
828 // speed in knots, headings in degrees, radius in meters.
829 static double TaxiTurnTowardsHeading(double current_hdg, double desired_hdg, double speed, double radius, double dt) {
830 // wrap heading - this prevents a logic bug where the plane would just go round in circles!!
831 while(current_hdg < 0.0) {
832 current_hdg += 360.0;
834 while(current_hdg > 360.0) {
835 current_hdg -= 360.0;
837 if(fabs(current_hdg - desired_hdg) > 0.1) {
838 // Which is the quickest direction to turn onto heading?
839 if(desired_hdg > current_hdg) {
840 if((desired_hdg - current_hdg) <= 180) {
842 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
843 // TODO - check that increments are less than the delta that we check for the right direction
844 // Probably need to reduce convergence speed as convergence is reached
846 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
849 if((current_hdg - desired_hdg) <= 180) {
851 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
852 // TODO - check that increments are less than the delta that we check for the right direction
853 // Probably need to reduce convergence speed as convergence is reached
855 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
862 void FGAILocalTraffic::Taxi(double dt) {
863 //cout << "Taxi called" << endl;
864 // Logic - if we are further away from next point than turn radius then head for it
865 // If we have reached turning point then get next point and turn onto that heading
866 // Look out for the finish!!
868 //Point3D orthopos = ortho.ConvertToLocal(pos); // ortho position of the plane
869 desiredTaxiHeading = GetHeadingFromTo(pos, nextTaxiNode->pos);
871 bool lastNode = (taxiPathPos == path.size() ? true : false);
873 //cout << "LAST NODE\n";
876 // HACK ALERT! - for now we will taxi at constant speed for straights and turns
878 // Remember that hdg is always equal to track when taxiing so we don't have to consider them both
879 double dist_to_go = dclGetHorizontalSeparation(pos, nextTaxiNode->pos); // we may be able to do this more cheaply using orthopos
880 //cout << "dist_to_go = " << dist_to_go << endl;
881 if((nextTaxiNode->type == GATE) && (dist_to_go <= 0.1)) {
882 // This might be more robust to outward paths starting with a gate if we check for either
883 // last node or TD_INBOUND ?
885 operatingState = PARKED;
886 } else if(((dist_to_go > taxiTurnRadius) || (nextTaxiNode->type == GATE)) && (!liningUp)){
887 // if the turn radius is r, and speed is s, then in a time dt we turn through
888 // ((s.dt)/(PI.r)) x 180 degrees
889 // or alternatively (s.dt)/r radians
890 //cout << "hdg = " << hdg << " desired taxi heading = " << desiredTaxiHeading << '\n';
891 hdg = TaxiTurnTowardsHeading(hdg, desiredTaxiHeading, nominalTaxiSpeed, taxiTurnRadius, dt);
892 double vel = nominalTaxiSpeed;
893 //cout << "vel = " << vel << endl;
894 double dist = vel * 0.514444 * dt;
895 //cout << "dist = " << dist << endl;
897 //cout << "track = " << track << endl;
899 pos = dclUpdatePosition(pos, track, slope, dist);
900 //cout << "Updated position...\n";
901 if(aip.getFGLocation()->get_cur_elev_m() > -9990) {
902 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
903 } // else don't change the elev until we get a valid ground elev again!
904 } else if(lastNode) {
905 if(taxiState == TD_OUTBOUND) {
906 if((!liningUp) && (dist_to_go <= taxiTurnRadius)) {
910 hdg = TaxiTurnTowardsHeading(hdg, rwy.hdg, nominalTaxiSpeed, taxiTurnRadius, dt);
911 double vel = nominalTaxiSpeed;
912 //cout << "vel = " << vel << endl;
913 double dist = vel * 0.514444 * dt;
914 //cout << "dist = " << dist << endl;
916 //cout << "track = " << track << endl;
918 pos = dclUpdatePosition(pos, track, slope, dist);
919 //cout << "Updated position...\n";
920 if(aip.getFGLocation()->get_cur_elev_m() > -9990) {
921 pos.setelev(aip.getFGLocation()->get_cur_elev_m() + wheelOffset);
922 } // else don't change the elev until we get a valid ground elev again!
923 if(fabs(hdg - rwy.hdg) <= 1.0) {
924 operatingState = IN_PATTERN;
930 } // else at the moment assume TD_INBOUND always ends in a gate in which case we can ignore it
932 // Time to turn (we've already checked it's not the end we're heading for).
933 // set the target node to be the next node which will prompt automatically turning onto
934 // the right heading in the stuff above, with the usual provisos applied.
936 // For now why not just recursively call this function?
942 // Warning - ground elev determination is CPU intensive
943 // Either this function or the logic of how often it is called
944 // will almost certainly change.
945 void FGAILocalTraffic::DoGroundElev() {
947 // It would be nice if we could set the correct tile center here in order to get a correct
948 // answer with one call to the function, but what I tried in the two commented-out lines
949 // below only intermittently worked, and I haven't quite groked why yet.
950 //SGBucket buck(pos.lon(), pos.lat());
951 //aip.getFGLocation()->set_tile_center(Point3D(buck.get_center_lon(), buck.get_center_lat(), 0.0));
953 double visibility_meters = fgGetDouble("/environment/visibility-m");
954 //globals->get_tile_mgr()->prep_ssg_nodes( acmodel_location,
955 globals->get_tile_mgr()->prep_ssg_nodes( aip.getFGLocation(), visibility_meters );
956 globals->get_tile_mgr()->update( aip.getFGLocation(), visibility_meters, (aip.getFGLocation())->get_absolute_view_pos() );
957 // save results of update in FGLocation for fdm...
959 //if ( globals->get_scenery()->get_cur_elev() > -9990 ) {
960 // acmodel_location->
961 // set_cur_elev_m( globals->get_scenery()->get_cur_elev() );
964 // The need for this here means that at least 2 consecutive passes are needed :-(
965 aip.getFGLocation()->set_tile_center( globals->get_scenery()->get_next_center() );
967 //cout << "Transform Elev is " << globals->get_scenery()->get_cur_elev() << '\n';
968 aip.getFGLocation()->set_cur_elev_m(globals->get_scenery()->get_cur_elev());
969 //return(globals->get_scenery()->get_cur_elev());