FlightGear next
AIFlightPlan.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: AIFlightPlan.cxx
3 * SPDX-FileComment: class for loading and storing AI flight plans
4 * SPDX-FileCopyrightText: Written by David Culp, started May 2004 - davidculp2@comcast.net
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#ifdef HAVE_CONFIG_H
9#include <config.h>
10#endif
11
12#include <algorithm>
13#include <iterator>
14
15#include <simgear/constants.h>
16#include <simgear/debug/logstream.hxx>
17#include <simgear/io/iostreams/sgstream.hxx>
18#include <simgear/math/sg_geodesy.hxx>
19#include <simgear/misc/sg_path.hxx>
20#include <simgear/props/props.hxx>
21#include <simgear/props/props_io.hxx>
22#include <simgear/structure/exception.hxx>
23#include <simgear/timing/sg_time.hxx>
24
25#include <Airports/airport.hxx>
26#include <Airports/dynamics.hxx>
28#include <Airports/runways.hxx>
29#include <Main/fg_init.hxx>
30#include <Main/fg_props.hxx>
31#include <Main/globals.hxx>
32
35
36#include <Traffic/Schedule.hxx>
37
38#include "AIAircraft.hxx"
39#include "AIFlightPlan.hxx"
40
41using std::string;
42
44{
45 speed = 0;
46 crossat = 0;
47 finished = 0;
48 gear_down = 0;
49 flaps = 0;
50 on_ground = 0;
51 routeIndex = 0;
52 time_sec = 0;
53 trackLength = 0;
54}
55
56bool FGAIWaypoint::contains(const string& target)
57{
58 size_t found = name.find(target);
59 if (found == string::npos)
60 return false;
61 else
62 return true;
63}
64
66{
67 return pos.getLatitudeDeg();
68}
69
71{
72 return pos.getLongitudeDeg();
73}
74
76{
77 return pos.getElevationFt();
78}
79
81{
82 pos.setLatitudeDeg(lat);
83}
84
86{
87 pos.setLongitudeDeg(lon);
88}
89
91{
92 pos.setElevationFt(alt);
93}
94
96 repeat(false),
97 distance_to_go(0),
98 lead_distance_ft(0),
99 leadInAngle(0),
100 start_time(0),
101 arrivalTime(0),
102 leg(0),
103 lastNodeVisited(0),
104 isValid(true)
105{
106 wpt_iterator = waypoints.begin();
107}
108
109FGAIFlightPlan::FGAIFlightPlan(const string& filename) : sid(NULL),
110 repeat(false),
111 distance_to_go(0),
112 lead_distance_ft(0),
113 leadInAngle(0),
114 start_time(0),
115 arrivalTime(0),
116 leg(10),
117 lastNodeVisited(0),
118 isValid(parseProperties(filename))
119{
120}
121
122
132 const std::string& p,
133 double course,
134 time_t start,
135 time_t remainingTime,
136 FGAirport* dep,
137 FGAirport* arr,
138 bool firstLeg,
139 double radius,
140 double alt,
141 double lat,
142 double lon,
143 double speed,
144 const string& fltType,
145 const string& acType,
146 const string& airline) : sid(NULL),
147 repeat(false),
148 distance_to_go(0),
149 lead_distance_ft(0),
150 leadInAngle(0),
151 start_time(start),
152 arrivalTime(0),
153 leg(10),
154 lastNodeVisited(0),
155 isValid(false),
156 departure(dep),
157 arrival(arr)
158{
159 if (parseProperties(p)) {
160 isValid = true;
161 } else {
162 createWaypoints(ac, course, start, remainingTime, dep, arr, firstLeg, radius,
163 alt, lat, lon, speed, fltType, acType, airline);
164 }
165}
166
168{
169 deleteWaypoints();
170 //delete taxiRoute;
171}
172
173void FGAIFlightPlan::createWaypoints(FGAIAircraft* ac,
174 double course,
175 time_t start,
176 time_t remainingTime,
177 FGAirport* dep,
178 FGAirport* arr,
179 bool firstLeg,
180 double radius,
181 double alt,
182 double lat,
183 double lon,
184 double speed,
185 const string& fltType,
186 const string& acType,
187 const string& airline)
188{
189 time_t now = globals->get_time_params()->get_cur_time();
190 time_t timeDiff = now - start;
192
193 if ((timeDiff > 60) && (timeDiff < 500))
194 leg = AILeg::RUNWAY_TAXI;
195 else if ((timeDiff >= 500) && (timeDiff < 600))
196 leg = AILeg::TAKEOFF;
197 else if ((timeDiff >= 600) && (timeDiff < 1000))
198 leg = AILeg::CLIMB;
199 else if (timeDiff >= 1000) {
200 if (remainingTime > 2000) {
201 leg = AILeg::CRUISE;
202 } else {
203 leg = AILeg::APPROACH;
204 }
205 }
206
207 SG_LOG(SG_AI, SG_DEBUG, ac->getTrafficRef()->getCallSign() << "|Route from " << dep->getId() << " to " << arr->getId() << ". Set leg to : " << leg << " " << remainingTime);
208
209 wpt_iterator = waypoints.begin();
210 bool dist = 0;
211 isValid = create(ac, dep, arr, leg, alt, speed, lat, lon,
212 firstLeg, radius, fltType, acType, airline, dist);
213 wpt_iterator = waypoints.begin();
214}
215
216bool FGAIFlightPlan::parseProperties(const std::string& filename)
217{
218 SGPath fp = globals->findDataPath("AI/FlightPlans/" + filename);
219 if (!fp.exists()) {
220 return false;
221 }
222
223 return readFlightplan(fp);
224}
225
226bool FGAIFlightPlan::readFlightplan(const SGPath& file)
227{
228 sg_ifstream f(file);
229 return readFlightplan(f, file);
230}
231
232bool FGAIFlightPlan::readFlightplan(std::istream& stream, const sg_location& loc)
233{
234 SGPropertyNode root;
235 try {
236 readProperties(stream, &root);
237 } catch (const sg_exception& e) {
238 SG_LOG(SG_AI, SG_ALERT, "Error reading AI flight plan: " << loc.asString() << " message:" << e.getFormattedMessage());
239 return false;
240 }
241
242 SGPropertyNode* node = root.getNode("flightplan");
243 if (!node) {
244 SG_LOG(SG_AI, SG_ALERT, "Error reading AI flight plan: " << loc.asString() << ": no <flightplan> root element");
245 return false;
246 }
247
248 for (int i = 0; i < node->nChildren(); i++) {
249 FGAIWaypoint* wpt = new FGAIWaypoint;
250 SGPropertyNode* wpt_node = node->getChild(i);
251
252 bool gear, flaps;
253
254 // Calculate some default values if they are not set explicitly in the flightplan
255 if (wpt_node->getDoubleValue("ktas", 0) < 1.0f) {
256 // Not moving so assume shut down.
257 wpt->setPowerDownLights();
258 flaps = false;
259 gear = true;
260 } else if (wpt_node->getDoubleValue("alt", 0) > 10000.0f) {
261 // Cruise flight;
262 wpt->setCruiseLights();
263 flaps = false;
264 gear = false;
265 } else if (wpt_node->getBoolValue("on-ground", false)) {
266 // On ground
267 wpt->setGroundLights();
268 flaps = true;
269 gear = true;
270 } else if (wpt_node->getDoubleValue("alt", 0) < 3000.0f) {
271 // In the air below 3000 ft, so flaps and gear down.
272 wpt->setApproachLights();
273 flaps = true;
274 gear = true;
275 } else {
276 // In the air 3000-10000 ft
277 wpt->setApproachLights();
278 flaps = false;
279 gear = false;
280 }
281
282 wpt->setName(wpt_node->getStringValue("name", "END"));
283 wpt->setLatitude(wpt_node->getDoubleValue("lat", 0));
284 wpt->setLongitude(wpt_node->getDoubleValue("lon", 0));
285 wpt->setAltitude(wpt_node->getDoubleValue("alt", 0));
286 wpt->setSpeed(wpt_node->getDoubleValue("ktas", 0));
287 wpt->setCrossat(wpt_node->getDoubleValue("crossat", -10000));
288 wpt->setGear_down(wpt_node->getBoolValue("gear-down", gear));
289 wpt->setFlaps(wpt_node->getBoolValue("flaps-down", flaps) ? 1.0 : 0.0);
290 wpt->setSpoilers(wpt_node->getBoolValue("spoilers", false) ? 1.0 : 0.0);
291 wpt->setSpeedBrakes(wpt_node->getBoolValue("speedbrakes", false) ? 1.0 : 0.0);
292 wpt->setOn_ground(wpt_node->getBoolValue("on-ground", false));
293 wpt->setTime_sec(wpt_node->getDoubleValue("time-sec", 0));
294 wpt->setTime(wpt_node->getStringValue("time", ""));
295 wpt->setFinished((wpt->getName() == "END"));
296 pushBackWaypoint(wpt);
297 }
298
299 auto lastWp = getLastWaypoint();
300 if (!lastWp || lastWp->getName().compare("END") != 0) {
301 SG_LOG(SG_AI, SG_ALERT, "FGAIFlightPlan::Flightplan (" + loc.asString() + ") missing END node");
302 return false;
303 }
304
305
306 wpt_iterator = waypoints.begin();
307 return true;
308}
309
311{
312 if (waypoints.empty())
313 return nullptr;
314
315 return waypoints.back();
316};
317
319{
320 if (empty())
321 return nullptr;
322
323 if (wpt_iterator == waypoints.begin()) {
324 return nullptr;
325 } else {
326 wpt_vector_iterator prev = wpt_iterator;
327 return *(--prev);
328 }
329}
330
332{
333 if (wpt_iterator == waypoints.end())
334 return nullptr;
335 return *wpt_iterator;
336}
337
339{
340 if (wpt_iterator == waypoints.end())
341 return nullptr;
342
343 wpt_vector_iterator last = waypoints.end() - 1;
344 if (wpt_iterator == last) {
345 return nullptr;
346 } else {
347 return *(wpt_iterator + 1);
348 }
349}
350
352{
353 return nextTurnAngle;
354}
355
356
357void FGAIFlightPlan::IncrementWaypoint(bool eraseWaypoints)
358{
359 if (empty())
360 return;
361
362 if (eraseWaypoints) {
363 if (wpt_iterator == waypoints.begin())
364 ++wpt_iterator;
365 else if (!waypoints.empty()) {
366 delete *(waypoints.begin());
367 waypoints.erase(waypoints.begin());
368 wpt_iterator = waypoints.begin();
369 ++wpt_iterator;
370 }
371 } else {
372 ++wpt_iterator;
373 }
374 // Calculate the angle of the next turn.
375 if (wpt_iterator == waypoints.end())
376 return;
377 if (wpt_iterator == waypoints.begin())
378 return;
379 if (wpt_iterator + 1 == waypoints.end())
380 return;
381 if (waypoints.size() < 3)
382 return;
383 FGAIWaypoint* previousWP = *(wpt_iterator - 1);
384 FGAIWaypoint* currentWP = *(wpt_iterator);
385 FGAIWaypoint* nextWP = *(wpt_iterator + 1);
386 int currentBearing = this->getBearing(previousWP, currentWP);
387 int nextBearing = this->getBearing(currentWP, nextWP);
388
389 nextTurnAngle = SGMiscd::normalizePeriodic(-180, 180, nextBearing - currentBearing);
390 if ((previousWP->getSpeed() > 0 && nextWP->getSpeed() < 0) ||
391 (previousWP->getSpeed() < 0 && nextWP->getSpeed() > 0)) {
392 nextTurnAngle += 180;
393 SG_LOG(SG_AI, SG_BULK, "Add 180 to turn angle pushback end");
394 }
395 SG_LOG(SG_AI, SG_BULK, "Calculated next turn angle " << nextTurnAngle << " " << previousWP->getName() << " " << currentWP->getName() << " Previous Speed " << previousWP->getSpeed() << " Next Speed " << nextWP->getSpeed());
396}
397
399{
400 if (empty())
401 return;
402
403 --wpt_iterator;
404}
405
406void FGAIFlightPlan::eraseLastWaypoint()
407{
408 if (empty())
409 return;
410
411 delete (waypoints.back());
412 waypoints.pop_back();
413 wpt_iterator = waypoints.begin();
414 ++wpt_iterator;
415}
416
417// gives distance in meters from a position to a waypoint
418double FGAIFlightPlan::getDistanceToGo(double lat, double lon, FGAIWaypoint* wp) const
419{
420 return SGGeodesy::distanceM(SGGeod::fromDeg(lon, lat), wp->getPos());
421}
422
428 double bearing,
429 FGAIWaypoint* current,
430 FGAIWaypoint* next)
431{
432 double turn_radius_m;
433 // Handle Ground steering
434 // At a turn rate of 30 degrees per second, it takes 12 seconds to do a full 360 degree turn
435 // So, to get an estimate of the turn radius, calculate the circumference of the circle
436 // we travel on. Get the turn radius by dividing by PI (*2).
437 // FIXME Why when going backwards? No fabs
438 if (speed < 0.5) {
439 setLeadDistance(0.5);
440 return;
441 }
442 if (speed > 0 && speed < 0.5) {
443 setLeadDistance(5 * SG_METER_TO_FEET);
444 SG_LOG(SG_AI, SG_BULK, "Setting Leaddistance fixed " << (lead_distance_ft * SG_FEET_TO_METER));
445 return;
446 }
447
448 double speed_mps = speed * SG_KT_TO_MPS;
449 if (speed < 25) {
450 turn_radius_m = ((360 / 30) * fabs(speed_mps)) / (2 * M_PI);
451 } else {
452 turn_radius_m = 0.1911 * speed * speed; // an estimate for 25 degrees bank
453 }
454
455 double inbound = bearing;
456 double outbound = getBearing(current, next);
457 leadInAngle = fabs(inbound - outbound);
458 if (leadInAngle > 180.0) leadInAngle = 360.0 - leadInAngle;
459 //if (leadInAngle < 30.0) // To prevent lead_dist from getting so small it is skipped
460 // leadInAngle = 30.0;
461
462 //lead_distance_ft = turn_radius * sin(leadInAngle * SG_DEGREES_TO_RADIANS);
463
464 if ((int)leadInAngle == 0) {
465 double lead_distance_m = fabs(2 * speed) * SG_FEET_TO_METER;
466 setLeadDistance(lead_distance_m * SG_METER_TO_FEET);
467 if (lead_distance_ft > 1000) {
468 SG_LOG(SG_AI, SG_BULK, "Excessive leaddistance leadin 0 " << lead_distance_ft << " leadInAngle " << leadInAngle << " inbound " << inbound << " outbound " << outbound);
469 }
470 } else {
471 double lead_distance_m = turn_radius_m * tan((leadInAngle * SG_DEGREES_TO_RADIANS) / 2);
472 setLeadDistance(lead_distance_m * SG_METER_TO_FEET);
473 SG_LOG(SG_AI, SG_BULK, "Setting Leaddistance " << (lead_distance_ft * SG_FEET_TO_METER) << " Turnradius " << turn_radius_m << " Speed " << speed_mps << " Half turn Angle " << (leadInAngle) / 2);
474 if (lead_distance_ft > 1000) {
475 SG_LOG(SG_AI, SG_BULK, "Excessive leaddistance possible direction change " << lead_distance_ft << " leadInAngle " << leadInAngle << " inbound " << inbound << " outbound " << outbound << " at " << current->getName());
476 }
477 }
478
479 /*
480 if ((lead_distance_ft > (3*turn_radius)) && (current->on_ground == false)) {
481 SG_LOG(SG_AI, SG_ALERT, "Warning: Lead-in distance is large. Inbound = " << inbound
482 << ". Outbound = " << outbound << ". Lead in angle = " << leadInAngle << ". Turn radius = " << turn_radius);
483 lead_distance_ft = 3 * turn_radius;
484 return;
485 }
486 if ((leadInAngle > 90) && (current->on_ground == true)) {
487 lead_distance_ft = turn_radius * tan((90 * SG_DEGREES_TO_RADIANS)/2);
488 return;
489 }*/
490}
491
492void FGAIFlightPlan::setLeadDistance(double distance_ft)
493{
494 lead_distance_ft = distance_ft;
495 if (lead_distance_ft > 10000) {
496 SG_LOG(SG_AI, SG_BULK, "Excessive Leaddistance " << distance_ft);
497 }
498}
499
500
502{
503 return SGGeodesy::courseDeg(first->getPos(), second->getPos());
504}
505
506double FGAIFlightPlan::getBearing(const SGGeod& aPos, FGAIWaypoint* wp) const
507{
508 return SGGeodesy::courseDeg(aPos, wp->getPos());
509}
510
511void FGAIFlightPlan::deleteWaypoints()
512{
513 for (wpt_vector_iterator i = waypoints.begin(); i != waypoints.end(); ++i)
514 delete (*i);
515 waypoints.clear();
516 wpt_iterator = waypoints.begin();
517}
518
519// Delete all waypoints except the last,
520// which we will recycle as the first waypoint in the next leg;
521void FGAIFlightPlan::resetWaypoints()
522{
523 if (waypoints.begin() == waypoints.end())
524 return;
525
526
527 FGAIWaypoint* wpt = new FGAIWaypoint;
528 wpt_vector_iterator i = waypoints.end();
529 --i;
530 wpt->setName((*i)->getName());
531 wpt->setPos((*i)->getPos());
532 wpt->setCrossat((*i)->getCrossat());
533 wpt->setGear_down((*i)->getGear_down());
534 wpt->setFlaps((*i)->getFlaps());
535 wpt->setSpoilers((*i)->getSpoilers());
536 wpt->setSpeedBrakes((*i)->getSpeedBrakes());
537 wpt->setBeaconLight((*i)->getBeaconLight());
538 wpt->setLandingLight((*i)->getLandingLight());
539 wpt->setNavLight((*i)->getNavLight());
540 wpt->setStrobeLight((*i)->getStrobeLight());
541 wpt->setTaxiLight((*i)->getTaxiLight());
542 wpt->setFinished(false);
543 wpt->setOn_ground((*i)->getOn_ground());
544 SG_LOG(SG_AI, SG_DEBUG, "Recycling waypoint " << wpt->getName());
545 deleteWaypoints();
546 pushBackWaypoint(wpt);
547}
548
550{
551 pushBackWaypoint(wpt);
552}
553
554void FGAIFlightPlan::pushBackWaypoint(FGAIWaypoint* wpt)
555{
556 if (!wpt) {
557 SG_LOG(SG_AI, SG_WARN, "Null WPT added");
558 }
559 size_t pos = wpt_iterator - waypoints.begin();
560 if (waypoints.size() > 0) {
561 double dist = SGGeodesy::distanceM(waypoints.back()->getPos(), wpt->getPos());
562 if (dist == 0) {
563 SG_LOG(SG_AI, SG_DEBUG, "Double WP : \t" << wpt->getName() << " not added ");
564 } else {
565 waypoints.push_back(wpt);
566 SG_LOG(SG_AI, SG_BULK, "Added WP : \t" << std::setprecision(12) << wpt->getName() << "\t" << wpt->getPos() << "\t" << wpt->getSpeed());
567 }
568 } else {
569 waypoints.push_back(wpt);
570 SG_LOG(SG_AI, SG_BULK, "Added WP : \t" << std::setprecision(12) << wpt->getName() << "\t" << wpt->getPos() << "\t" << wpt->getSpeed());
571 }
572 // std::vector::push_back invalidates waypoints
573 // so we should restore wpt_iterator after push_back
574 // (or it could be an index in the vector)
575 wpt_iterator = waypoints.begin() + pos;
576}
577
578// Start flightplan over from the beginning
580{
581 wpt_iterator = waypoints.begin();
582}
583
585{
586 if ((i > 0) && (i < (int)waypoints.size())) {
587 return waypoints[i]->getRouteIndex();
588 } else
589 return 0;
590}
591
592double FGAIFlightPlan::checkTrackLength(const string& wptName) const
593{
594 // skip the first two waypoints: first one is behind, second one is partially done;
595 double trackDistance = 0;
596 wpt_vector_iterator wptvec = waypoints.begin();
597 ++wptvec;
598 ++wptvec;
599 while ((wptvec != waypoints.end())) {
600 if (*wptvec != nullptr && (!((*wptvec)->contains(wptName)))) {
601 break;
602 }
603 trackDistance += (*wptvec)->getTrackLength();
604 ++wptvec;
605 }
606 if (wptvec == waypoints.end()) {
607 trackDistance = 0; // name not found
608 }
609 return trackDistance;
610}
611
612void FGAIFlightPlan::shortenToFirst(unsigned int number, const std::string& name)
613{
614 while (waypoints.size() > number + 3) {
615 eraseLastWaypoint();
616 }
617 (waypoints.back())->setName((waypoints.back())->getName() + name);
618}
619
621{
622 gate = pka;
623}
624
626{
627 return gate.parking();
628}
629
631{
632 return departure;
633}
634
636{
637 return arrival;
638}
639
641{
643 fp->isValid = false;
644 return fp;
645}
646
648{
649 return waypoints.empty();
650}
651
656
657time_t FGAIFlightPlan::calcArrivalTimes() const
658{
659 time_t runtime = 0;
660
661 if (waypoints.cbegin() == waypoints.cend()) {
662 return 0;
663 }
664 auto previousWP = (*waypoints.cbegin());
665
666 for(const auto wp : waypoints) {
667 SGGeod lastPos = previousWP->getPos();
668 SGGeod currentPos = wp->getPos();
669 double dist_m = SGGeodesy::distanceM(lastPos, currentPos);
670 double speed_mps = wp->getSpeed() * SG_KT_TO_MPS;
671 double time_s = dist_m / speed_mps;
672 runtime += time_s;
673 previousWP = wp;
674 if (getLeg() == AILeg::HOLD_PATTERN) {
675 SG_LOG(SG_AI, SG_DEBUG, "Runtime : " << runtime << " " << dist_m );
676 }
677 }
678 SG_LOG(SG_AI, SG_DEBUG, "Runtime : " << runtime << " " << getLeg() );
679
680 return runtime;
681}
682
#define p(x)
#define M_PI
Definition FGJSBBase.h:50
#define i(x)
SGSharedPtr< FGAirport > FGAirportRef
FGAISchedule * getTrafficRef()
void setGate(const ParkingAssignment &pka)
void IncrementWaypoint(bool erase)
void setLeadDistance(double speed, double bearing, FGAIWaypoint *current, FGAIWaypoint *next)
Set lead_distance_ft.
int getRouteIndex(int i) const
FGAIWaypoint * getNextWaypoint(void) const
FGParking * getParkingGate() const
FGAirportRef departureAirport() const
double getDistanceToGo(double lat, double lon, FGAIWaypoint *wp) const
static FGAIFlightPlan * createDummyUserPlan()
create a nearly empty FlightPlan for the user aircraft, based on the current position and route-manag...
FGAirportRef arrivalAirport() const
double checkTrackLength(const std::string &wptName) const
void shortenToFirst(unsigned int number, const std::string &name)
bool readFlightplan(const SGPath &file)
read a flight-plan from a file.
virtual ~FGAIFlightPlan()
bool create(FGAIAircraft *, FGAirport *dep, FGAirport *arr, int leg, double alt, double speed, double lat, double lon, bool firstLeg, double radius, const std::string &fltType, const std::string &aircraftType, const std::string &airline, double distance)
bool empty() const
int getNextTurnAngle(void) const
int getLeg() const
void restart(void)
FGAIWaypoint * getPreviousWaypoint(void) const
FGAIWaypoint * getCurrentWaypoint(void) const
FGAIWaypoint * getLastWaypoint() const
void addWaypoint(FGAIWaypoint *wpt)
double getBearing(FGAIWaypoint *previous, FGAIWaypoint *next) const
std::string getCallSign()
Definition Schedule.cxx:552
void setApproachLights()
void setGear_down(bool grd)
void setAltitude(double alt)
double getSpeed()
void setCrossat(double val)
bool contains(const std::string &name)
void setName(const std::string &nam)
double getLatitude() const
void setStrobeLight(bool strobe)
void setOn_ground(bool grn)
const std::string & getName()
void setSpoilers(double val)
void setTime_sec(double ts)
void setFinished(bool fin)
void setLongitude(double lon)
void setTime(const std::string &tme)
void setLatitude(double lat)
void setSpeed(double spd)
void setCruiseLights()
void setBeaconLight(bool beacon)
void setLandingLight(bool ldg)
void setPos(const SGGeod &aPos)
void setPowerDownLights()
double getLongitude() const
void setFlaps(double val)
void setTaxiLight(bool taxi)
void setGroundLights()
double getAltitude() const
void setSpeedBrakes(double val)
const SGGeod & getPos()
void setNavLight(bool nav)
const std::string & getId() const
Definition airport.hxx:53
SGPath findDataPath(const std::string &pathSuffix) const
Given a path suffix (eg 'Textures' or 'AI/Traffic'), find the first data directory which defines it.
Definition globals.cxx:348
FGGlobals * globals
Definition globals.cxx:142
@ HOLD_PATTERN
@ RUNWAY_TAXI
@ STARTUP_PUSHBACK