FlightGear next
AIAircraft.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: AIAircraft.cxx
3 * SPDX-FileComment: AIBase derived class creates an AI aircraft
4 * SPDX-FileCopyrightText: Copyright (C) 2003 David P. Culp - davidculp2@comcast.net
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
10#include <FDM/fdm_shell.hxx>
11#include <Main/fg_props.hxx>
12#include <Main/globals.hxx>
13#include <Main/util.hxx>
14#include <Scenery/scenery.hxx>
15#include <Traffic/Schedule.hxx>
16
17#include <simgear/io/iostreams/sgstream.hxx>
18#include <simgear/structure/exception.hxx>
19#include <simgear/timing/sg_time.hxx>
20
21#include <cmath>
22#include <ctime>
23#include <iostream>
24#include <signal.h>
25#include <string>
26
27// defined in AIShip.cxx
28extern double fgIsFinite(double x);
29
30#include "AIAircraft.hxx"
31#include "AIFlightPlan.hxx"
32#include "AIManager.hxx"
33#include "performancedata.hxx"
34#include "performancedb.hxx"
35
36#include <ATC/atc_mgr.hxx>
37#include <ATC/ATCController.hxx>
39
40
41FGAIAircraft::FGAIAircraft(FGAISchedule* ref) : /* HOT must be disabled for AI Aircraft,
42 * otherwise traffic detection isn't working as expected.*/
44 _performance(nullptr),
45 csvFile{std::make_unique<sg_ofstream>()}
46{
47 trafficRef = ref;
48 if (trafficRef) {
49 groundOffset = trafficRef->getGroundOffset();
50 setCallSign(trafficRef->getCallSign());
51 tracked = getCallSign() == fgGetString("/ai/track-callsign") || fgGetString("/ai/track-callsign") == "ALL";
52 } else {
53 groundOffset = 0;
54 }
55
56 fp = nullptr;
57 controller = nullptr;
58 prevController = nullptr;
59 towerController = nullptr;
60 dt_count = 0;
61 dt_elev_count = 0;
62 use_perf_vs = true;
63
64 no_roll = false;
65 tgt_speed = 0;
66 speed = 0;
67 groundTargetSpeed = 0;
68 spinCounter = 0;
69
70 // set heading and altitude locks
71 hdg_lock = false;
72 alt_lock = false;
73 roll = 0;
74 headingChangeRate = 0.0;
75 headingError = 0;
76 minBearing = 360;
77 speedFraction = 1.0;
78 prevSpeed = 0.0;
79 prev_dist_to_go = 0.0;
80
81 auto perfDB = globals->get_subsystem<PerformanceDB>();
82
83 if (perfDB) {
84 _performance = perfDB->getDefaultPerformance();
85 } else {
86 SG_LOG(SG_AI, SG_DEV_ALERT, "no performance DB loaded");
87 _performance = PerformanceData::getDefaultData();
88 }
89
90 timeElapsed = 0;
91
92 trackCache.remainingLength = 0;
93 trackCache.startWptName = "-";
94
95 tcasThreatNode = props->getNode("tcas/threat-level", true);
96 tcasRANode = props->getNode("tcas/ra-sense", true);
98}
99
100void FGAIAircraft::lazyInitControlsNodes()
101{
102 _controlsLateralModeNode = props->getNode("controls/flight/lateral-mode", true);
103 _controlsVerticalModeNode = props->getNode("controls/flight/vertical-mode", true);
104 _controlsTargetHeadingNode = props->getNode("controls/flight/target-hdg", true);
105 _controlsTargetRollNode = props->getNode("controls/flight/target-roll", true);
106 _controlsTargetAltitude = props->getNode("controls/flight/target-alt", true);
107 _controlsTargetPitch = props->getNode("controls/flight/target-pitch", true);
108 _controlsTargetSpeed = props->getNode("controls/flight/target-spd", true);
109}
110
112{
113 assert(!controller);
114 if (controller) {
115 // we no longer signOff from controller here, controller should
116 // have been cleared using clearATCControllers
117 // see FLIGHTGEAR-15 on Sentry
118 SG_LOG(SG_AI, SG_ALERT, "Destruction of AIAircraft which was not unbound");
119 }
120}
121
122
123void FGAIAircraft::readFromScenario(SGPropertyNode* scFileNode)
124{
125 if (!scFileNode)
126 return;
127
128 FGAIBase::readFromScenario(scFileNode);
129
130 setPerformance("", scFileNode->getStringValue("class", "jet_transport"));
131 setFlightPlan(scFileNode->getStringValue("flightplan"),
132 scFileNode->getBoolValue("repeat", false));
133 setCallSign(scFileNode->getStringValue("callsign"));
134 tracked = getCallSign() == fgGetString("/ai/track-callsign") || fgGetString("/ai/track-callsign") == "ALL";
135}
136
137
139{
141
142 tie("transponder-id",
143 SGRawValueMethods<FGAIAircraft, const char*>(*this,
144 &FGAIAircraft::_getTransponderCode));
145}
146
147void FGAIAircraft::update(double dt)
148{
150 Run(dt);
151 Transform();
152 if (tracked && !csvFile->is_open()) {
153 char fname[160];
154 time_t t = time(0); // get time now
155 snprintf(fname, sizeof(fname), "%s_%ld.csv", getCallSign().c_str(), t);
156 SGPath p = globals->get_download_dir() / fname;
157 csvFile->open(p);
158 dumpCSVHeader(csvFile);
159 }
160 if (tracked && csvFile->is_open()) {
161 dumpCSV(csvFile, csvIndex++);
162 }
163}
164
170
171void FGAIAircraft::setPerformance(const std::string& acType, const std::string& acClass)
172{
173 auto perfDB = globals->get_subsystem<PerformanceDB>();
174 if (perfDB) {
175 _performance = perfDB->getDataFor(acType, acClass);
176 }
177
178 if (!_performance) {
179 SG_LOG(SG_AI, SG_DEV_ALERT, "no AI performance data found for: " << acType << "/" << acClass);
180 _performance = PerformanceData::getDefaultData();
181 }
182}
183
184void FGAIAircraft::Run(double dt)
185{
186 // We currently have one situation in which an AIAircraft object is used that is not attached to the
187 // AI manager. In this particular case, the AIAircraft is used to shadow the user's aircraft's behavior in the AI world.
188 // Since we perhaps don't want a radar entry of our own aircraft, the following conditional should probably be adequate
189 // enough
190 const bool isUserAircraft = (manager == nullptr);
191
192 bool flightplanActive = true;
193
194 // user aircraft speed, heading and position are synchronized in
195 // FGAIManager::fetchUserState()
196 if (!isUserAircraft) {
197 bool outOfSight = false;
198 updatePrimaryTargetValues(dt, flightplanActive, outOfSight); // target hdg, alt, speed
199 if (outOfSight) {
200 return;
201 }
202 } else {
203 updateUserFlightPlan(dt);
204 }
205
206 if (!flightplanActive) {
207 groundTargetSpeed = 0;
208 }
209
210 handleATCRequests(dt); // ATC also has a word to say
211 updateSecondaryTargetValues(dt); // target roll, vertical speed, pitch
212 updateActualState(dt);
213
214 updateModelProperties(dt);
215
216
217 if (!isUserAircraft) {
219 invisible = !manager->isVisible(pos);
220 }
221}
222
223
225{
227 if (!isStationary()) {
228 _needsGroundElevation = true;
229 }
230}
231
232
233void FGAIAircraft::PitchTo(double angle)
234{
235 tgt_pitch = angle;
236 alt_lock = false;
237}
238
239
240void FGAIAircraft::RollTo(double angle)
241{
242 tgt_roll = angle;
243 hdg_lock = false;
244}
245
246
247#if 0
248void FGAIAircraft::YawTo(double angle) {
249 tgt_yaw = angle;
250}
251#endif
252
253
254void FGAIAircraft::ClimbTo(double alt_ft)
255{
256 tgt_altitude_ft = alt_ft;
257 alt_lock = true;
258}
259
260
261void FGAIAircraft::TurnTo(double heading)
262{
263 const double headingDiff = SGMiscd::normalizePeriodic(-180, 180, heading - tgt_heading);
264
265 if (fabs(heading) < 0.1 && fabs(headingDiff) > 1) {
266 SG_LOG(SG_AI, SG_WARN, getCallSign() << "(" << getID() << ")Heading reset to zero " << tgt_heading << " " << headingDiff << " at " << getGeodPos());
267 }
268 tgt_heading = heading;
269 // SG_LOG(SG_AI, SG_BULK, "Turn tgt_heading to " << tgt_heading);
270 hdg_lock = true;
271}
272
273
274double FGAIAircraft::sign(double x)
275{
276 if (x == 0.0)
277 return x;
278 else
279 return x / fabs(x);
280}
281
282
283void FGAIAircraft::setFlightPlan(const std::string& flightplan, bool repeat)
284{
285 if (flightplan.empty()) {
286 // this is the case for Nasal-scripted aircraft
287 return;
288 }
289
290 std::unique_ptr<FGAIFlightPlan> plan(new FGAIFlightPlan(flightplan));
291 if (plan->isValidPlan()) {
292 plan->setRepeat(repeat);
293 FGAIBase::setFlightPlan(std::move(plan));
294 } else {
295 SG_LOG(SG_AI, SG_WARN, "setFlightPlan: invalid flightplan specified:" << flightplan);
296 }
297}
298
299void FGAIAircraft::ProcessFlightPlan(double dt, time_t now)
300{
301 // the one behind you
302 FGAIWaypoint* prev = 0;
303 // the one ahead
304 FGAIWaypoint* curr = 0;
305 // the next plus 1
306 FGAIWaypoint* next = 0;
308 int nextTurnAngle = 0;
309
310 prev = fp->getPreviousWaypoint();
311 curr = fp->getCurrentWaypoint();
312 next = fp->getNextWaypoint();
313 nextTurnAngle = fp->getNextTurnAngle();
314
315 dt_count += dt;
316
318 // Initialize the flightplan
320 if (!prev || repositioned) {
321 if (!fp->empty()) {
322 handleFirstWaypoint();
323 } else {
324 SG_LOG(SG_AI, SG_WARN, getCallSign() << " didn't have a valid flightplan and was killed");
325 setDie(true);
326 }
327 return;
328 } // end of initialization
329 if (!fpExecutable(now)) {
330 return;
331 }
332 dt_count = 0;
333
334 double distanceToDescent;
335 // Not the best solution. Double adding of legs is possible
336 if (fp->getLastWaypoint() == fp->getNextWaypoint() &&
337 reachedEndOfCruise(distanceToDescent)) {
338 if (!loadNextLeg(distanceToDescent)) {
339 setDie(true);
340 return;
341 }
342 fp->IncrementWaypoint(false);
343 fp->IncrementWaypoint(false);
344 prev = fp->getPreviousWaypoint();
345 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") EndofCruise Previous WP \t" << prev->getName());
346 curr = fp->getCurrentWaypoint();
347 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") EndofCruise Current WP \t" << curr->getName());
348 next = fp->getNextWaypoint();
349 if (next) {
350 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") EndofCruise Next WP \t" << next->getName());
351 }
352 }
353 if (!curr) {
354 if (!next) {
355 SG_LOG(SG_AI, SG_WARN, getCallSign() << "(" << getID() << ") No more WPs");
356 } else {
357 SG_LOG(SG_AI, SG_WARN, getCallSign() << "(" << getID() << ") No current WP" << next->getName());
358 }
359 return;
360 }
361
362 if (!leadPointReached(curr, next, nextTurnAngle)) {
363 controlHeading(curr, nullptr);
364 controlSpeed(curr, next);
365 } else {
366 if (curr->isFinished()) { //end of the flight plan
367 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") Flightplan ended");
368 if (fp->getRepeat()) {
369 fp->restart();
370 } else {
371 setDie(true);
372 }
373 return;
374 }
375
376 if (next) {
377 // TODO: more intelligent method in AIFlightPlan, no need to send data it already has :-)
378 tgt_heading = fp->getBearing(curr, next);
379 spinCounter = 0;
380 SG_LOG(SG_AI, SG_BULK, "Set tgt_heading to " << tgt_heading);
381 }
382
383 // TODO: let the fp handle this (loading of next leg)
384 fp->IncrementWaypoint(trafficRef != 0);
385 if (((!(fp->getNextWaypoint()))) && (trafficRef != 0)) {
386 if (!loadNextLeg()) {
387 setDie(true);
388 return;
389 }
390 }
391
392 prev = fp->getPreviousWaypoint();
393 if (prev) {
394 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") Previous WP \t" << prev->getName() << "\t" << prev->getPos());
395 }
396 curr = fp->getCurrentWaypoint();
397 if (curr) {
398 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") Current WP \t" << curr->getName() << "\t" << curr->getPos());
399 }
400 next = fp->getNextWaypoint();
401 if (next) {
402 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") Next WP \t" << next->getName() << "\t" << next->getPos());
403 }
404
405 // Now that we have incremented the waypoints, execute some traffic manager specific code
406 if (trafficRef) {
407 // TODO: isn't this best executed right at the beginning?
408 if (!aiTrafficVisible()) {
409 setDie(true);
410 return;
411 }
412
413 if (!handleAirportEndPoints(prev, now)) {
414 setDie(true);
415 return;
416 }
417
419 if (fp && props) {
420 props->getChild("arrival-time-sec", 0, true)->setIntValue(fp->getArrivalTime());
421 }
422 }
423
424 if (next && !curr->contains("END") && !curr->contains("PushBackPointlegend")) {
425 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") Setting Leaddistance");
426 fp->setLeadDistance(tgt_speed, tgt_heading, curr, next);
427 }
428
429 // Calculate a target altitude for any leg in which at least one waypoint is in the air.
430
431 if (prev->getInAir() && curr->getInAir()) {
432 // Completely in-air leg, so calculate the target altitude and VS.
433 if (curr->getCrossat() > -1000.0) {
434 use_perf_vs = false;
435 double dist_m = fp->getDistanceToGo(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr);
436 double vert_dist_ft = curr->getCrossat() - altitude_ft;
437 double err_dist = prev->getCrossat() - altitude_ft;
438 tgt_vs = calcVerticalSpeed(vert_dist_ft, dist_m, speed, err_dist);
439 tgt_altitude_ft = curr->getCrossat();
440 checkTcas();
441 } else {
442 use_perf_vs = true;
443 tgt_altitude_ft = prev->getCrossat();
444 }
445 } else if (curr->getInAir()) {
446 // Take-off leg (prev is on ground)
447 if (curr->getCrossat() > -1000.0) {
448 // Altitude restriction
449 use_perf_vs = false;
450 double dist_m = fp->getDistanceToGo(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr);
451 double vert_dist_ft = curr->getCrossat() - altitude_ft;
452 double err_dist = -altitude_ft;
453 tgt_vs = calcVerticalSpeed(vert_dist_ft, dist_m, speed, err_dist);
454 tgt_altitude_ft = curr->getCrossat();
455 } else {
456 // no cross-at, so assume same as previous
457 use_perf_vs = true;
458 tgt_altitude_ft = prev->getCrossat();
459 }
460 } else if (prev->getInAir()) {
461 // Landing Leg (curr is on ground).
462
463 // Assume we want to touch down on the point, and not early!
464 use_perf_vs = false;
465 double dist_m = fp->getDistanceToGo(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr);
466 double vert_dist_ft = curr->getAltitude() - altitude_ft;
467 double err_dist = -altitude_ft;
468 tgt_vs = calcVerticalSpeed(vert_dist_ft, dist_m, speed, err_dist);
470 }
471
472 if(!holdPos) {
473 AccelTo(prev->getSpeed());
474 }
475 hdg_lock = alt_lock = true;
476 no_roll = (prev->getOn_ground() && curr->getOn_ground());
477 }
478}
479
480double FGAIAircraft::calcVerticalSpeed(double vert_ft, double dist_m, double speed, double err)
481{
482 // err is negative when we passed too high
483 double vert_m = vert_ft * SG_FEET_TO_METER;
484 double speedMs = (speed * SG_NM_TO_METER) / 3600;
485 double vs = 0;
486 if (dist_m) {
487 vs = ((vert_m) / dist_m) * speedMs;
488 }
489 // Convert to feet per minute
490 vs *= (SG_METER_TO_FEET * 60);
491 return vs;
492}
493
495{
496 if (controller && getDie()) {
497 //If we are dead we are automatically erased
498 controller->signOff(getID());
499 }
500
501 controller = nullptr;
502 prevController = nullptr;
503 towerController = nullptr;
504}
505
507{
508 if (!other) {
509 return false;
510 }
511 int azimuth = SGGeodesy::courseDeg(getGeodPos(), other->getGeodPos());
512 const double headingDiff = SGMiscd::normalizePeriodic(-180, 180, hdg - azimuth);
513
514 SG_LOG(SG_AI, SG_BULK, getCallSign() << " blocked by " << other->getCallSign() << azimuth << " Heading " << hdg << " Diff" << headingDiff);
515 return false;
516}
517
518#if 0
519void FGAIAircraft::assertSpeed(double speed)
520{
521 if ((speed < -50) || (speed > 1000)) {
522 SG_LOG(SG_AI, SG_DEBUG, getCallSign() << " "
523 << "Previous waypoint " << fp->getPreviousWaypoint()->getName() << " "
524 << "Departure airport " << trafficRef->getDepartureAirport() << " "
525 << "Leg " << fp->getLeg() << " "
526 << "target_speed << " << tgt_speed << " "
527 << "speedFraction << " << speedFraction << " "
528 << "Current speed << " << speed << " ");
529 }
530}
531#endif
532
533
535{
536 if (tcasThreatNode && tcasThreatNode->getIntValue() == 3) {
537 const int RASense = tcasRANode->getIntValue();
538 if ((RASense > 0) && (tgt_vs < 4000)) {
539 // upward RA: climb!
540 tgt_vs = 4000;
541 } else if (RASense < 0) {
542 // downward RA: descend!
543 if (altitude_ft < 1000) {
544 // too low: level off
545 if (tgt_vs > 0)
546 tgt_vs = 0;
547 } else {
548 if (tgt_vs > -4000)
549 tgt_vs = -4000;
550 }
551 }
552 }
553}
554
555#if 0
556void FGAIAircraft::initializeFlightPlan() {
557}
558#endif
559
560const char* FGAIAircraft::_getTransponderCode() const
561{
562 return transponderCode.c_str();
563}
564
565// NOTE: Check whether the new (delayed leg increment code has any effect on this code.
566// Probably not, because it should only be executed after we have already passed the leg incrementing waypoint.
567
568bool FGAIAircraft::loadNextLeg(double distance)
569{
570 const int leg = fp->getLeg();
571 if (leg == AILeg::PARKING) {
572 FGAirport* oldArr = trafficRef->getArrivalAirport();
573 if (!trafficRef->next()) {
574 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") No following flight killing");
575
576 //FIXME I'm on leg 9 and don't even reach parking.
577 return false;
578 }
579 FGAirport* dep = trafficRef->getDepartureAirport();
580 if (oldArr != dep) {
581 // as though we are first leg
582 repositioned = true;
583 } else {
584 repositioned = false;
585 }
586 setCallSign(trafficRef->getCallSign());
587 tracked = getCallSign() == fgGetString("/ai/track-callsign") || fgGetString("/ai/track-callsign") == "ALL";
588 fp->setLeg(AILeg::UNKNOWN);
589 }
590
591 FGAirport* dep = trafficRef->getDepartureAirport();
592 FGAirport* arr = trafficRef->getArrivalAirport();
593 if (!(dep && arr)) {
594 SG_LOG(SG_AI, SG_WARN, getCallSign() << "(" << getID() << ") No dep/arr for fp ");
595 setDie(true);
596 } else {
597 double cruiseAlt = trafficRef->getCruiseAlt() * 100;
598
599 int nextLeg = determineNextLeg(leg);
600
601 SG_LOG(SG_AI, SG_DEBUG, getCallSign() << "(" << getID() << ") |Loading Leg:" << leg << " Next: " << nextLeg);
602 bool ok = fp->create(this,
603 dep,
604 arr,
605 nextLeg,
606 cruiseAlt,
607 trafficRef->getSpeed(),
608 _getLatitude(),
610 false,
611 trafficRef->getRadius(),
612 trafficRef->getFlightType(),
613 acType,
614 company,
615 distance);
616
617 if (!ok) {
618 SG_LOG(SG_AI, SG_WARN, getCallSign() << "(" << getID() << ") Failed to create waypoints for leg:" << leg + 1);
619 }
620 }
621 return true;
622}
623
624
625// Note: This code is copied from David Luff's AILocalTraffic
626// Warning - ground elev determination is CPU intensive
627// Either this function or the logic of how often it is called
628// will almost certainly change.
629
631{
632 dt_elev_count += dt;
633
634 if (!needGroundElevation())
635 return;
636 // Update minimally every three secs, but add some randomness
637 // to prevent all AI objects doing this in synchrony
638 if (dt_elev_count < (3.0) + (rand() % 10))
639 return;
640
641 dt_elev_count = 0;
642
643 // Only do the proper hitlist stuff if we are within visible range of the viewer.
644 if (!invisible) {
645 double visibility_meters = fgGetDouble("/environment/visibility-m");
646 if (SGGeodesy::distanceM(globals->get_view_position(), pos) > visibility_meters) {
647 return;
648 }
649
650 double range = 500.0;
651 if (globals->get_scenery()->schedule_scenery(pos, range, 5.0)) {
652 double alt;
653 if (getGroundElevationM(SGGeod::fromGeodM(pos, 20000), alt, 0)) {
654 tgt_altitude_ft = alt * SG_METER_TO_FEET;
655 if (isStationary()) {
656 // aircraft is stationary and we obtained altitude for this spot - we're done.
657 _needsGroundElevation = false;
658 }
659 }
660 }
661 }
662}
663
665{
666 if ((fp->getLeg() == AILeg::LANDING) && ((altitude_ft - tgt_altitude_ft) > 5)) {
667 tgt_vs = -500;
668 } else {
669 if ((fabs(altitude_ft - (tgt_altitude_ft + groundOffset)) > 1000.0) ||
670 (isStationary()))
671 altitude_ft = (tgt_altitude_ft + groundOffset);
672 else
673 altitude_ft += 0.1 * ((tgt_altitude_ft + groundOffset) - altitude_ft);
674 tgt_vs = 0;
675 }
676}
677
678
680{
681 if (!trafficRef) {
682 return;
683 }
684 const bool isUserAircraft = (manager == nullptr);
685
686 if ( isUserAircraft && globals->get_subsystem<FDMShell>()->is_suspended()) {
687 return;
688 }
689
690 int leg = fp->getLeg();
691 if (!fp->getCurrentWaypoint()) {
692 // http://code.google.com/p/flightgear-bugs/issues/detail?id=1153
693 // throw an exception so this aircraft gets killed by the AIManager.
694 throw sg_exception("bad AI flight plan. No current WP");
695 }
696 auto mgr = globals->get_subsystem<FGATCManager>();
697
698 // Note that leg has been incremented after creating the current leg, so we should use
699 // leg numbers here that are one higher than the number that is used to create the leg
700 // NOTE: As of July, 30, 2011, the post-creation leg updating is no longer happening.
701 // Leg numbers are updated only once the aircraft passes the last waypoint created for
702 // that leg, so I should probably just use the original leg numbers here!
703 switch (leg) {
704 case AILeg::STARTUP_PUSHBACK: // Startup and Push back
705 if (trafficRef->getDepartureAirport()->getDynamics())
706 controller = trafficRef->getDepartureAirport()->getDynamics()->getStartupController();
707 break;
708 case AILeg::RUNWAY_TAXI: // Taxiing to runway
709 if (trafficRef->getDepartureAirport()->getDynamics()->getGroundController()->exists())
710 controller = trafficRef->getDepartureAirport()->getDynamics()->getGroundController();
711 break;
712 case AILeg::TAKEOFF: //Take off tower controller
713 if (trafficRef->getDepartureAirport()->getDynamics()) {
714 controller = trafficRef->getDepartureAirport()->getDynamics()->getTowerController();
715 if (towerController) {
716 SG_LOG(SG_AI, SG_BULK, " : " << controller->getName() << "#" << towerController->getName() << (controller != towerController));
717 }
718 towerController = nullptr;
719 } else {
720 SG_LOG(SG_AI, SG_BULK, "Error: Could not find Dynamics at airport : " << trafficRef->getDepartureAirport()->getId());
721 }
722 break;
723 case AILeg::CLIMB:
724 case AILeg::CRUISE:
725 if (mgr && mgr->getEnRouteController()) {
726 controller = mgr->getEnRouteController();
727 }
728 break;
729 case AILeg::APPROACH:
731 if (trafficRef->getArrivalAirport()->getDynamics()) {
732 controller = trafficRef->getArrivalAirport()->getDynamics()->getApproachController();
733 }
734 break;
735 case AILeg::LANDING:
736 if (trafficRef->getArrivalAirport()->getDynamics()) {
737 controller = trafficRef->getArrivalAirport()->getDynamics()->getTowerController();
738 }
739 break;
740 case AILeg::PARKING_TAXI: // Taxiing for parking
741 if (trafficRef->getArrivalAirport()->getDynamics()->getGroundController()->exists()) {
742 controller = trafficRef->getArrivalAirport()->getDynamics()->getGroundController();
743 } else {
744 SG_LOG(SG_ATC, SG_ALERT, trafficRef->getArrivalAirport()->getId() << " doesn't have a groundcontroller" );
745 }
746 break;
747 case AILeg::PARKING: // Parked
748 if (controller) {
749 SG_LOG(SG_AI, SG_BULK, "Will be signing off from " << controller->getName());
750 controller->signOff(getID());
751 } else {
752 SG_LOG(SG_AI, SG_BULK, "Controller was null");
753 }
754 controller = nullptr;
755 break;
756 default:
757 SG_LOG(SG_AI, SG_ALERT, "AILeg " << leg << " not covered by a controller type");
758 if (prevController) {
759 SG_LOG(SG_AI, SG_BULK, "Will be signing off from " << prevController->getName());
760 }
761 controller = nullptr;
762 break;
763 }
764
765 if ((controller != prevController) && prevController && !getDie()) {
766 // We update one last time to update the Radar state.
767 prevController->announcePosition(getID(), fp.get(), fp->getCurrentWaypoint()->getRouteIndex(),
769 trafficRef->getRadius(), leg, this);
770 if (controller!=nullptr) {
771 SG_LOG(SG_AI, SG_DEBUG, "Handing over " << this->getCallSign() << "(" << this->getID() << ") to " << controller->getName());
772 controller->handover(prevController->getRecord(getID()), leg);
773 }
774 //If we are dead we are automatically erased
775 prevController->signOff(getID());
776 }
777 prevController = controller;
778 if (controller) {
779 controller->announcePosition(getID(), fp.get(), fp->getCurrentWaypoint()->getRouteIndex(),
781 trafficRef->getRadius(), leg, this);
782 } else {
783 if (fp->getLeg()<=AILeg::PARKING) {
784 // No controller when parked
785 SG_LOG(SG_AI, SG_ALERT, "Can't announcePosition " << this->getCallSign() << " no controller on Leg " << fp->getLeg());
786 }
787 }
788}
789
793
797
799{
800 if (instruction.getCheckForCircularWait()) {
801 // This is not exactly an elegant solution,
802 // but at least it gives me a chance to check
803 // if circular waits are resolved.
804 // For now, just take the offending aircraft
805 // out of the scene
806 setDie(true);
807 // a more proper way should be - of course - to
808 // let an offending aircraft take an evasive action
809 // for instance taxi back a little bit.
810 }
811
812 if (instruction.getHoldPattern()) {
813 //holdtime = instruction.getHoldTime();
814 }
815
816 waitsForId = instruction.getWaitsForId();
817
818 // Hold Position
819 if (instruction.getHoldPosition()) {
820 holdPos = true;
821 if (onGround()) {
822 groundTargetSpeed = 0.0;
823 }
824 AccelTo(0.0);
825 } else {
826 holdPos = false;
827
828 // Change speed Instruction. This can only be executed when there is no
829 // Hold position instruction.
830 if (instruction.getChangeSpeed()) {
831 AccelTo(instruction.getSpeed());
832 } else {
833 if (fp)
834 AccelTo(fp->getPreviousWaypoint()->getSpeed());
835 }
836 }
837
838 if (instruction.getChangeHeading()) {
839 hdg_lock = false;
840 TurnTo(instruction.getHeading());
841 } else {
842 if (fp) {
843 hdg_lock = true;
844 }
845 }
846
847 if (instruction.getChangeAltitude()) {}
848}
849
850void FGAIAircraft::handleFirstWaypoint()
851{
852 headingError = 0;
853 bool eraseWaypoints = trafficRef ? true : false;
854
855 FGAIWaypoint* prev = 0; // the one behind you
856 FGAIWaypoint* curr = 0; // the one ahead
857 FGAIWaypoint* next = 0; // the next plus 1
858
859 spinCounter = 0;
860
861 // TODO: fp should handle this
862 fp->IncrementWaypoint(eraseWaypoints);
863 if (!(fp->getNextWaypoint()) && trafficRef) {
864 if (!loadNextLeg()) {
865 setDie(true);
866 return;
867 }
868 }
869
870 prev = fp->getPreviousWaypoint(); //first waypoint
871 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") Previous WP \t" << prev->getName());
872 curr = fp->getCurrentWaypoint(); //second waypoint
873 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") Current WP \t" << curr->getName());
874 next = fp->getNextWaypoint(); //third waypoint (might not exist!)
875 if (next) {
876 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") Next WP \t" << next->getName());
877 }
878
879 setLatitude(prev->getLatitude());
880 setLongitude(prev->getLongitude());
881 if (fp->getLeg() == 1) {
882 setSpeed(0);
883 } else {
884 setSpeed(prev->getSpeed());
885 }
886 setAltitude(prev->getAltitude());
887
888 if (prev->getSpeed() > 0.0)
889 setHeading(fp->getBearing(prev, curr));
890 else {
891 // FIXME When going to parking it must be the heading of the parking
892 setHeading(fp->getBearing(curr, prev));
893 }
894
895 // If next doesn't exist, as in incrementally created flightplans for
896 // AI/Trafficmanager created plans,
897 // Make sure lead distance is initialized otherwise
898 // If we are ending in a parking
899 if (next && !curr->contains("END") && !curr->contains("PushBackPointlegend")) {
900 fp->setLeadDistance(tgt_speed, hdg, curr, next);
901 }
902
903 if (curr->getCrossat() > -1000.0) //use a calculated descent/climb rate
904 {
905 use_perf_vs = false;
906 double vert_dist_ft = curr->getCrossat() - altitude_ft;
907 double err_dist = prev->getCrossat() - altitude_ft;
908 double dist_m = fp->getDistanceToGo(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr);
909 tgt_vs = calcVerticalSpeed(vert_dist_ft, dist_m, speed, err_dist);
910 checkTcas();
911 tgt_altitude_ft = curr->getCrossat();
912 } else {
913 use_perf_vs = true;
915 }
916 alt_lock = hdg_lock = true;
917 no_roll = prev->getOn_ground();
918 if (no_roll) {
919 Transform(); // make sure aip is initialized.
920 getGroundElev(60.1); // make sure it's executed first time around, so force a large dt value
922 _needsGroundElevation = true; // check ground elevation again (maybe scenery wasn't available yet)
923 }
924 // Make sure to announce the aircraft's position
926 prevSpeed = 0;
927}
928
937bool FGAIAircraft::fpExecutable(time_t now)
938{
939 double rand_exec_time = (rand() % 100) / 100;
940 return (dt_count > (0.1 + rand_exec_time)) && (fp->isActive(now));
941}
942
943
952bool FGAIAircraft::leadPointReached(FGAIWaypoint* curr, FGAIWaypoint* next, int nextTurnAngle)
953{
954 double dist_to_go_m = fp->getDistanceToGo(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr);
955 // Leaddistance should be ft
956 double lead_distance_m = fp->getLeadDistance() * SG_FEET_TO_METER;
957 const double arrivalDist = fabs(10.0 * fp->getCurrentWaypoint()->getSpeed());
958 // arrive at pushback end
959 if ((dist_to_go_m < arrivalDist) && (speed < 0) && (tgt_speed < 0) && fp->getCurrentWaypoint()->contains("PushBackPoint")) {
960 // tgt_speed = -(dist_to_go_m / 10.0);
961 tgt_speed = -std::sqrt((pow(arrivalDist, 2) - pow(arrivalDist - dist_to_go_m, 2)));
962 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") tgt_speed " << tgt_speed);
963 if (tgt_speed > -1) {
964 // Speed is int and cannot go below 1 knot
965 tgt_speed = -1;
966 }
967
968 if (fp->getPreviousWaypoint()->getSpeed() < tgt_speed) {
969 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") Set speed of WP from " << fp->getPreviousWaypoint()->getSpeed() << " to " << tgt_speed);
970 fp->getPreviousWaypoint()->setSpeed(tgt_speed);
971 }
972 }
973 // arrive at parking
974 if ((dist_to_go_m < arrivalDist) && (speed > 0) && (tgt_speed > 0) && fp->getCurrentWaypoint()->contains("END")) {
975 tgt_speed = (dist_to_go_m / 10.0);
976 if (tgt_speed < 1) {
977 // Speed is int and cannot go below 1 knot
978 tgt_speed = 1;
979 }
980
981 if (fp->getPreviousWaypoint()->getSpeed() < tgt_speed) {
982 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") Set speed of WP from " << fp->getPreviousWaypoint()->getSpeed() << " to " << tgt_speed);
983 fp->getPreviousWaypoint()->setSpeed(tgt_speed);
984 }
985 }
986
987 if (lead_distance_m < fabs(2 * speed) * SG_FEET_TO_METER) {
988 //don't skip over the waypoint
989 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") Set lead_distance_m due to speed " << lead_distance_m << " to " << fabs(2 * speed) * SG_FEET_TO_METER);
990 lead_distance_m = fabs(2 * speed) * SG_FEET_TO_METER;
991 fp->setLeadDistance(lead_distance_m * SG_METER_TO_FEET);
992 }
993
994 double bearing = 0;
995 // don't do bearing calculations for ground traffic
996 bearing = getBearing(fp->getBearing(pos, curr));
997 double nextBearing = bearing;
998 if (next) {
999 nextBearing = getBearing(fp->getBearing(pos, next));
1000 }
1001 if (onGround() && fabs(nextTurnAngle) > 50) {
1002 //don't skip over the waypoint
1003 const int multiplicator = 4;
1004 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") Set lead_distance_m due to next turn angle " << lead_distance_m << " to " << fabs(multiplicator * speed) * SG_FEET_TO_METER << " dist_to_go_m " << dist_to_go_m << " Next turn angle : " << fabs(nextTurnAngle));
1005 lead_distance_m = fabs(multiplicator * speed) * SG_FEET_TO_METER;
1006 fp->setLeadDistance(lead_distance_m * SG_METER_TO_FEET);
1007 }
1008 if (bearing < minBearing) {
1009 minBearing = bearing;
1010 if (minBearing < 10) {
1011 minBearing = 10;
1012 }
1013 if ((minBearing < 360.0) && (minBearing > 10.0)) {
1014 speedFraction = 0.5 + (cos(minBearing * SG_DEGREES_TO_RADIANS) * 0.5);
1015 } else {
1016 speedFraction = 1.0;
1017 }
1018 }
1019
1020 // We are traveling straight and have passed the waypoint. This would result in a loop.
1021 if (next && abs(nextTurnAngle) < 5) {
1022 double bearingTowardsCurrent = fp->getBearing(this->getGeodPos(), curr);
1023 double headingDiffCurrent = SGMiscd::normalizePeriodic(-180, 180, hdg - bearingTowardsCurrent);
1024 double bearingTowardsNext = fp->getBearing(this->getGeodPos(), next);
1025 double headingDiffNext = SGMiscd::normalizePeriodic(-180, 180, hdg - bearingTowardsNext);
1026 if (abs(headingDiffCurrent) > 80 && speed > 0) {
1027 if (trafficRef != nullptr) {
1028 if (fp->getLeg() <= AILeg::CLIMB) {
1029 SG_LOG(SG_AI, SG_WARN, getCallSign() << "(" << getID() << ") possible missed WP at " << trafficRef->getDepartureAirport()->getId() << " " << curr->getName());
1030 } else {
1031 SG_LOG(SG_AI, SG_WARN, getCallSign() << "(" << getID() << ") possible missed WP at " << trafficRef->getArrivalAirport()->getId() << " " << curr->getName() << " Leg " << fp->getLeg() << " Speed " << getSpeed());
1032 }
1033 } else {
1034 SG_LOG(SG_AI, SG_WARN, getCallSign() << "(" << getID() << ") possible missed WP " << curr->getName());
1035 }
1036 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") headingDiffCurrent " << headingDiffCurrent << " headingDiffNext " << headingDiffNext);
1037 }
1038 }
1039 if ((dist_to_go_m < lead_distance_m) ||
1040 ((dist_to_go_m > prev_dist_to_go) && (bearing > (minBearing * 1.1)))) {
1041 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") Leadpoint reached Bearing : " << bearing << "\tNext Bearing : " << nextBearing << " Next Turn Angle : " << fabs(nextTurnAngle));
1042 minBearing = 360;
1043 speedFraction = 1.0;
1044 prev_dist_to_go = HUGE_VAL;
1045 return true;
1046 } else {
1047 if (prev_dist_to_go == dist_to_go_m
1048 && fabs(groundTargetSpeed) > 0
1049 && this->atGate().empty()) {
1050 //FIXME must be suppressed when parked
1051 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") Aircraft stuck. Speed " << speed);
1052 stuckCounter++;
1053 if (stuckCounter > AI_STUCK_LIMIT) {
1054 SG_LOG(SG_AI, SG_WARN, getCallSign() << "(" << getID() << ") Stuck flight killed on leg " << fp->getLeg());
1055 setDie(true);
1056 }
1057 } else {
1058 stuckCounter = 0;
1059 }
1060 prev_dist_to_go = dist_to_go_m;
1061 return false;
1062 }
1063}
1064
1065
1066bool FGAIAircraft::aiTrafficVisible()
1067{
1068 SGVec3d cartPos = SGVec3d::fromGeod(pos);
1069 const double d2 = (TRAFFIC_TO_AI_DIST_TO_DIE * SG_NM_TO_METER) *
1070 (TRAFFIC_TO_AI_DIST_TO_DIE * SG_NM_TO_METER);
1071 return (distSqr(cartPos, globals->get_aircraft_position_cart()) < d2);
1072}
1073
1074
1082
1083// TODO: the trafficRef is the right place for the method
1084bool FGAIAircraft::handleAirportEndPoints(FGAIWaypoint* prev, time_t now)
1085{
1086 using namespace std::string_literals;
1087
1088 // prepare routing from one airport to another
1089 FGAirport* dep = trafficRef->getDepartureAirport();
1090 FGAirport* arr = trafficRef->getArrivalAirport();
1091
1092 if (!(dep && arr))
1093 return false;
1094 if (!prev)
1095 return false;
1096
1097 // This waypoint marks the fact that the aircraft has passed the initial taxi
1098 // departure waypoint, so it can release the parking.
1099 if (prev->contains("PushBackPoint"s)) {
1100 // clearing the parking assignment will release the gate
1101 fp->setGate(ParkingAssignment());
1102 AccelTo(0.0);
1103 }
1104 if (prev->contains("legend"s)) {
1105 int nextLeg = determineNextLeg(fp->getLeg());
1106 while (nextLeg != fp->getLeg()) {
1107 fp->incrementLeg();
1108 }
1109 }
1110 if (prev->contains("DepartureHold"s)) {
1111 //std::cerr << "Passing point DepartureHold" << std::endl;
1113 }
1114
1115 // This is the last taxi waypoint, and marks the the end of the flight plan
1116 // so, the schedule should update and wait for the next departure time.
1117 if (prev->contains("END"s)) {
1118 SG_LOG(SG_AI, SG_DEBUG, getCallSign() << "(" << getID() << ") Reached " << prev->getName());
1119
1120 //FIXME Heading Error should be reset
1121 time_t nextDeparture = trafficRef->getDepartureTime();
1122 // make sure to wait at least 20 minutes at parking to prevent "nervous" taxi behavior
1123 if (nextDeparture < (now + 1200)) {
1124 nextDeparture = now + 1200;
1125 }
1126 fp->setTime(nextDeparture);
1128 }
1129
1130 return true;
1131}
1132
1133
1139void FGAIAircraft::controlHeading(FGAIWaypoint* curr, FGAIWaypoint* next)
1140{
1141 const double calc_bearing = speed < 0 ? SGMiscd::normalizePeriodic(0, 360, fp->getBearing(pos, curr) + 180.0) : fp->getBearing(pos, curr);
1142
1143 // when moving forward we want to aim for an average heading
1144 if (next && speed > 0) {
1145 const double calcNextBearing = fp->getBearing(pos, next);
1146 if (fgIsFinite(calc_bearing) && fgIsFinite(calcNextBearing)) {
1147 double averageHeading = calc_bearing + (calcNextBearing - calc_bearing) / 2;
1148 averageHeading = SGMiscd::normalizePeriodic(0, 360, averageHeading);
1149 double hdg_error = averageHeading - tgt_heading;
1150 if (fabs(hdg_error) > 0.01) {
1151 TurnTo(averageHeading);
1152 }
1153 } else {
1154 SG_LOG(SG_AI, SG_WARN, "calc_bearing is not a finite number : "
1155 << "Speed " << speed << "pos : " << pos.getLatitudeDeg() << ", " << pos.getLongitudeDeg() << ", waypoint: " << curr->getLatitude() << ", " << curr->getLongitude());
1156 SG_LOG(SG_AI, SG_WARN, "waypoint name: '" << curr->getName() << "'");
1157 ;
1158 }
1159 } else {
1160 if (fgIsFinite(calc_bearing)) {
1161 double hdg_error = calc_bearing - tgt_heading;
1162 if (fabs(hdg_error) > 0.01) {
1163 TurnTo(calc_bearing);
1164 }
1165 } else {
1166 SG_LOG(SG_AI, SG_WARN, "calc_bearing is not a finite number : "
1167 << "Speed " << speed << "pos : " << pos.getLatitudeDeg() << ", " << pos.getLongitudeDeg() << ", waypoint: " << curr->getLatitude() << ", " << curr->getLongitude());
1168 SG_LOG(SG_AI, SG_WARN, "waypoint name: '" << curr->getName() << "'");
1169 ;
1170 }
1171 }
1172}
1173
1174
1182void FGAIAircraft::controlSpeed(FGAIWaypoint* curr, FGAIWaypoint* next)
1183{
1184 double speed_diff = speed - prevSpeed;
1185
1186 if (fabs(speed_diff) > 10) {
1187 prevSpeed = speed;
1188 if (next) {
1189 if (!curr->contains("END") && !curr->contains("PushBackPointlegend")) {
1190 if (speed_diff > 0 && tgt_speed >= 5) {
1191 fp->setLeadDistance(speed, tgt_heading, curr, next);
1192 } else {
1193 fp->setLeadDistance(tgt_speed, tgt_heading, curr, next);
1194 }
1195 }
1196 }
1197 }
1198}
1199
1203void FGAIAircraft::updatePrimaryTargetValues(double dt, bool& flightplanActive, bool& aiOutOfSight)
1204{
1205 if (fp && fp->isValidPlan()) // AI object has a flightplan
1206 {
1207 // TODO: make this a function of AIBase
1208 time_t now = globals->get_time_params()->get_cur_time();
1209
1210 //std::cerr << "UpdateTArgetValues() " << std::endl;
1211 ProcessFlightPlan(dt, now);
1212
1213 // Do execute Ground elev for inactive aircraft, so they
1214 // Are repositioned to the correct ground altitude when the user flies within visibility range.
1215 // In addition, check whether we are out of user range, so this aircraft
1216 // can be deleted.
1217 if (onGround()) {
1218 Transform(); // make sure aip is initialized.
1219 getGroundElev(dt);
1221 // Transform();
1222 pos.setElevationFt(altitude_ft);
1223 }
1224 if (trafficRef) {
1225 aiOutOfSight = !aiTrafficVisible();
1226 if (aiOutOfSight) {
1227 setDie(true);
1228 aiOutOfSight = true;
1229 return;
1230 }
1231 }
1232 timeElapsed = now - fp->getStartTime();
1233 flightplanActive = fp->isActive(now);
1234 } else {
1235 // no flight plan, update target heading, speed, and altitude
1236 // from control properties. These default to the initial
1237 // settings in the config file, but can be changed "on the
1238 // fly".
1239
1240 if (!_controlsLateralModeNode) {
1241 lazyInitControlsNodes();
1242 }
1243
1244
1245 const std::string& lat_mode = _controlsLateralModeNode->getStringValue();
1246 if (lat_mode == "roll") {
1247 const double angle = _controlsTargetRollNode->getDoubleValue();
1248 RollTo(angle);
1249 } else {
1250 const double angle = _controlsTargetHeadingNode->getDoubleValue();
1251 TurnTo(angle);
1252 }
1253
1254 std::string vert_mode = _controlsVerticalModeNode->getStringValue();
1255 if (vert_mode == "alt") {
1256 const double alt = _controlsTargetAltitude->getDoubleValue();
1257 ClimbTo(alt);
1258 } else {
1259 const double angle = _controlsTargetPitch->getDoubleValue();
1260 PitchTo(angle);
1261 }
1262
1263 AccelTo(_controlsTargetSpeed->getDoubleValue());
1264 }
1265}
1266
1267void FGAIAircraft::updateHeading(double dt)
1268{
1269 // adjust heading based on current bank angle
1270 if (roll == 0.0)
1271 roll = 0.01;
1272
1273 if (roll != 0.0) {
1274 // If on ground, calculate heading change directly
1275 if (onGround()) {
1276 const double headingDiff = SGMiscd::normalizePeriodic(-180, 180, hdg - tgt_heading);
1277
1278 // When pushback behind us we still want to move but ...
1279 groundTargetSpeed = tgt_speed * cos(headingDiff * SG_DEGREES_TO_RADIANS);
1280
1281 if (sign(groundTargetSpeed) != sign(tgt_speed) && fabs(tgt_speed) > 0) {
1282 if (fabs(speed) < 2 && fp->isActive(globals->get_time_params()->get_cur_time())) {
1283 // This seems to happen in case there is a change from forward to pushback.
1284 // which should never happen.
1285 SG_LOG(SG_AI, SG_BULK, "Oh dear " << _callsign << " might get stuck aka next point is behind us. Speed is " << speed);
1286 stuckCounter++;
1287 if (stuckCounter > AI_STUCK_LIMIT) {
1288 SG_LOG(SG_AI, SG_WARN, "Stuck flight " << _callsign << " killed on leg " << fp->getLeg() << " because point behind");
1289 setDie(true);
1290 }
1291 }
1292 // Negative Cosinus means angle > 90°
1293 groundTargetSpeed = 0.21 * sign(tgt_speed); // to prevent speed getting stuck in 'negative' mode
1294 }
1295
1296 // Only update the target values when we're not moving because otherwise we might introduce an enormous target change rate while waiting a the gate, or holding.
1297 if (speed != 0) {
1298 if (fabs(headingDiff) > 30.0) {
1299 // invert if pushed backward
1300 if (sign(headingChangeRate) == sign(headingDiff)) {
1301 // left/right change
1302 headingChangeRate = 10.0 * dt * sign(headingDiff) * -1;
1303 } else {
1304 headingChangeRate -= 10.0 * dt * sign(headingDiff);
1305 }
1306
1307 // Clamp the maximum steering rate to 30 degrees per second,
1308 // But only do this when the heading error is decreasing.
1309 // FIXME
1310 if ((headingDiff < headingError)) {
1311 if (headingChangeRate > 30)
1312 headingChangeRate = 30;
1313 else if (headingChangeRate < -30)
1314 headingChangeRate = -30;
1315 }
1316 } else {
1317 if (sign(headingChangeRate) == sign(headingDiff)) {
1318 // left/right change
1319 headingChangeRate = 3 * dt * sign(headingDiff) * -1;
1320 } else {
1321 headingChangeRate -= 3 * dt * sign(headingDiff);
1322 }
1323 /*
1324 if (headingChangeRate > headingDiff ||
1325 headingChangeRate < headingDiff) {
1326 headingChangeRate = headingDiff*sign(roll);
1327 }
1328 else {
1329 headingChangeRate += dt * sign(roll);
1330 }
1331 */
1332 }
1333 }
1334
1335 hdg += headingChangeRate * dt * sqrt(fabs(speed) / 15);
1336
1337 headingError = headingDiff;
1338 // SG_LOG(SG_AI, SG_BULK, "Headingerror " << headingError );
1339 if (fabs(headingError) < 1.0) {
1340 hdg = tgt_heading;
1341 }
1342 } else {
1343 if (fabs(speed) > 1.0) {
1344 turn_radius_ft = 0.088362 * speed * speed / tan(fabs(roll) / SG_RADIANS_TO_DEGREES);
1345 } else {
1346 // Check if turn_radius_ft == 0; this might lead to a division by 0.
1347 turn_radius_ft = 1.0;
1348 }
1349 double turn_circum_ft = SGD_2PI * turn_radius_ft;
1350 double dist_covered_ft = speed * 1.686 * dt;
1351 double alpha = dist_covered_ft / turn_circum_ft * 360.0;
1352 hdg += alpha * sign(roll);
1353 }
1354 while (hdg > 360.0) {
1355 hdg -= 360.0;
1356 spinCounter++;
1357 }
1358 while (hdg < 0.0) {
1359 hdg += 360.0;
1360 spinCounter--;
1361 }
1362 }
1363}
1364
1365
1366void FGAIAircraft::updateBankAngleTarget()
1367{
1368 // adjust target bank angle if heading lock engaged
1369 if (hdg_lock) {
1370 double bank_sense = 0.0;
1371 double diff = fabs(hdg - tgt_heading);
1372 if (diff > 180)
1373 diff = fabs(diff - 360);
1374
1375 double sum = hdg + diff;
1376 if (sum > 360.0)
1377 sum -= 360.0;
1378 if (fabs(sum - tgt_heading) < 1.0) {
1379 bank_sense = 1.0; // right turn
1380 } else {
1381 bank_sense = -1.0; // left turn
1382 }
1383 if (diff < _performance->maximumBankAngle()) {
1384 tgt_roll = diff * bank_sense;
1385 } else {
1386 tgt_roll = _performance->maximumBankAngle() * bank_sense;
1387 }
1388 if ((fabs((double)spinCounter) > 1) && (diff > _performance->maximumBankAngle())) {
1389 tgt_speed *= 0.999; // Ugly hack: If aircraft get stuck, they will continually spin around.
1390 // The only way to resolve this is to make them slow down.
1391 }
1392 }
1393}
1394
1395void FGAIAircraft::updateVerticalSpeedTarget(double dt)
1396{
1397 // adjust target Altitude, based on ground elevation when on ground
1398 if (onGround()) {
1399 getGroundElev(dt);
1401 } else if (alt_lock) {
1402 // find target vertical speed
1403 if (use_perf_vs) {
1405 tgt_vs = std::min(tgt_altitude_ft - altitude_ft, _performance->climbRate());
1406 } else {
1407 tgt_vs = std::max(tgt_altitude_ft - altitude_ft, -_performance->descentRate());
1408 }
1409 } else if (fp->getCurrentWaypoint()) {
1410 double vert_dist_ft = fp->getCurrentWaypoint()->getCrossat() - altitude_ft;
1411 double err_dist = 0;
1412 double dist_m = fp->getDistanceToGo(pos.getLatitudeDeg(), pos.getLongitudeDeg(), fp->getCurrentWaypoint());
1413 tgt_vs = calcVerticalSpeed(vert_dist_ft, dist_m, speed, err_dist);
1414 } else {
1415 // avoid crashes when fp has no current waypoint
1416 // eg see FLIGHTGEAR-68 on sentry; we crashed in getCrossat()
1417 tgt_vs = 0.0;
1418 }
1419 }
1420
1421 checkTcas();
1422}
1423
1424void FGAIAircraft::updatePitchAngleTarget()
1425{
1426 // if on ground and above vRotate -> initial rotation
1427 if (onGround() && (speed > _performance->vRotate()))
1428 tgt_pitch = 8.0; // some rough B737 value
1429
1430 // TODO: pitch angle on approach and landing
1431
1432 // match pitch angle to vertical speed
1433 else if (tgt_vs > 0) {
1434 tgt_pitch = tgt_vs * 0.005;
1435 } else {
1436 tgt_pitch = tgt_vs * 0.002;
1437 }
1438}
1439
1440const std::string& FGAIAircraft::atGate()
1441{
1442 if ((fp->getLeg() < 3) && trafficRef) {
1443 if (fp->getParkingGate()) {
1444 return fp->getParkingGate()->ident();
1445 }
1446 }
1447
1448 static const std::string empty{};
1449 return empty;
1450}
1451
1452int FGAIAircraft::determineNextLeg(int leg)
1453{
1454 if (leg == AILeg::APPROACH || leg == AILeg::HOLD_PATTERN) {
1455 time_t now = globals->get_time_params()->get_cur_time();
1456 if (controller == nullptr || controller->getInstruction(getID()).getRunwaySlot() > now) {
1457 SG_LOG(SG_AI, SG_DEBUG, getCallSign() << "(" << getID() << ") Entering holding pattern " << leg );
1458 return AILeg::HOLD_PATTERN;
1459 } else {
1460 SG_LOG(SG_AI, SG_DEBUG, getCallSign() << "(" << getID() << ") Landing" );
1461 return AILeg::LANDING;
1462 }
1463 } else {
1464 return leg + 1;
1465 }
1466}
1467
1468void FGAIAircraft::handleATCRequests(double dt)
1469{
1470 if (!this->getTrafficRef()) {
1471 return;
1472 }
1473
1474 // TODO: implement NullController for having no ATC to save the conditionals
1475 if (controller) {
1476 controller->updateAircraftInformation(getID(),
1477 pos,
1478 hdg,
1479 speed,
1481 dt);
1482 processATC(controller->getInstruction(getID()));
1483 }
1484 if (towerController) {
1485 towerController->updateAircraftInformation(getID(),
1486 pos,
1487 hdg,
1488 speed,
1490 dt);
1491 }
1492}
1493
1494void FGAIAircraft::updateUserFlightPlan(double dt)
1495{
1496 // If the aircraft leaves the airport proximity increase the flightplan leg to sign off
1497 // from the tower controller and free the runway #2358 The user doesn't
1498 // need to fly out straight
1499 if (fp) {
1500 switch (fp->getLeg()) {
1501 case AILeg::TAKEOFF: {
1502 auto current = fp->getCurrentWaypoint();
1503 auto last = fp->getLastWaypoint();
1504 int legDistance = SGGeodesy::distanceM(current->getPos(), last->getPos());
1505 int currDist = SGGeodesy::distanceM(getGeodPos(), current->getPos());
1506 int lastDist = SGGeodesy::distanceM(getGeodPos(), last->getPos());
1507 if (currDist > legDistance) {
1508 SG_LOG(SG_ATC, SG_BULK, "Signing off from Tower "
1509 << "\t currDist\t" << currDist << "\t legDistance\t" << legDistance << "\t" << lastDist << "\t" << getGeodPos().getLatitudeDeg() << "\t" << getGeodPos().getLongitudeDeg() << "\t" << current->getPos().getLatitudeDeg() << "\t" << current->getPos().getLongitudeDeg());
1510 // We are definitely beyond the airport
1511 fp->incrementLeg();
1512 }
1513 } break;
1514 default:
1515 break;
1516 }
1517 // TODO:
1518 }
1519}
1520
1521void FGAIAircraft::updateActualState(double dt)
1522{
1523 //update current state
1524 // TODO: have a single tgt_speed and check speed limit on ground on setting tgt_speed
1525 double distance = speed * SG_KT_TO_MPS * dt;
1526 pos = SGGeodesy::direct(pos, hdg, distance);
1527
1528 if (onGround())
1529 speed = _performance->actualSpeed(this, groundTargetSpeed, dt, holdPos);
1530 else
1531 speed = _performance->actualSpeed(this, (tgt_speed * speedFraction), dt, false);
1532
1533 updateHeading(dt);
1534 roll = _performance->actualBankAngle(this, tgt_roll, dt);
1535
1536 // adjust altitude (meters) based on current vertical speed (fpm)
1537 altitude_ft += vs_fps * dt;
1538 pos.setElevationFt(altitude_ft);
1539
1540 vs_fps = _performance->actualVerticalSpeed(this, tgt_vs, dt) / 60;
1541 pitch = _performance->actualPitch(this, tgt_pitch, dt);
1542}
1543
1544void FGAIAircraft::updateSecondaryTargetValues(double dt)
1545{
1546 // derived target state values
1547 updateBankAngleTarget();
1548 updateVerticalSpeedTarget(dt);
1549 updatePitchAngleTarget();
1550 // TODO: calculate wind correction angle (tgt_yaw)
1551}
1552
1553
1554bool FGAIAircraft::reachedEndOfCruise(double& distance)
1555{
1556 FGAIWaypoint* curr = fp->getCurrentWaypoint();
1557 if (!curr) {
1558 SG_LOG(SG_AI, SG_WARN, "FGAIAircraft::reachedEndOfCruise: no current waypoint");
1559
1560 // return true (=done) here, so we don't just get stuck on this forever
1561 return true;
1562 }
1563
1564 if (curr->getName() == std::string("BOD")) {
1565 // Sentry: FLIGHTGEAR-893
1566 if (!trafficRef->getArrivalAirport()) {
1567 SG_LOG(SG_AI, SG_WARN, trafficRef->getCallSign() << "FGAIAircraft::reachedEndOfCruise: no arrival airport");
1568 setDie(true);
1569 // return 'done' here, we are broken
1570 return true;
1571 }
1572
1573 double dist = fp->getDistanceToGo(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr);
1574 double descentSpeed = (getPerformance()->vDescent() * SG_NM_TO_METER) / 3600.0; // convert from kts to meter/s
1575 double descentRate = (getPerformance()->descentRate() * SG_FEET_TO_METER) / 60.0; // convert from feet/min to meter/s
1576
1577 double verticalDistance = ((altitude_ft - 2000.0) - trafficRef->getArrivalAirport()->getElevation()) * SG_FEET_TO_METER;
1578 double descentTimeNeeded = verticalDistance / descentRate;
1579 double distanceCoveredByDescent = descentSpeed * descentTimeNeeded;
1580
1581 if (tracked) {
1582 SG_LOG(SG_AI, SG_BULK, "Checking for end of cruise stage for :" << trafficRef->getCallSign());
1583 SG_LOG(SG_AI, SG_BULK, "Descent rate : " << descentRate);
1584 SG_LOG(SG_AI, SG_BULK, "Descent speed : " << descentSpeed);
1585 SG_LOG(SG_AI, SG_BULK, "VerticalDistance : " << verticalDistance << ". Altitude : " << altitude_ft << ". Elevation " << trafficRef->getArrivalAirport()->getElevation());
1586 SG_LOG(SG_AI, SG_BULK, "DecentTimeNeeded : " << descentTimeNeeded);
1587 SG_LOG(SG_AI, SG_BULK, "DistanceCovered : " << distanceCoveredByDescent);
1588 }
1589
1590 distance = distanceCoveredByDescent;
1591 if (dist < distanceCoveredByDescent) {
1592 SG_LOG(SG_AI, SG_BULK, getCallSign() << "(" << getID() << ") End Of Cruise");
1593 return true;
1594 } else {
1595 return false;
1596 }
1597 } else {
1598 return false;
1599 }
1600}
1601
1603{
1604 if (fp->empty()) {
1605 return;
1606 }
1607 // the one behind you
1608 FGAIWaypoint* prev = nullptr;
1609 // the one ahead
1610 FGAIWaypoint* curr = nullptr;
1611 // the next plus 1
1612 FGAIWaypoint* next = nullptr;
1613
1614 prev = fp->getPreviousWaypoint();
1615 curr = fp->getCurrentWaypoint();
1616 next = fp->getNextWaypoint();
1617
1618 if (curr == nullptr || next == nullptr) {
1619 SG_LOG(SG_AI, SG_WARN, getCallSign() << "(" << getID() << ") Repositioned without curr/next");
1620 return;
1621 }
1622
1623 setLatitude(prev->getLatitude());
1624 setLongitude(prev->getLongitude());
1625 setHeading(fp->getBearing(curr, next));
1626 setAltitude(prev->getAltitude());
1627 setSpeed(prev->getSpeed());
1628}
1629
1633
1634double FGAIAircraft::getBearing(double crse)
1635{
1636 double hdgDiff = fabs(hdg - crse);
1637 if (hdgDiff > 180)
1638 hdgDiff = fabs(hdgDiff - 360);
1639 return hdgDiff;
1640}
1641
1642time_t FGAIAircraft::checkForArrivalTime(const std::string& wptName)
1643{
1644 FGAIWaypoint* curr = 0;
1645 curr = fp->getCurrentWaypoint();
1646
1647 // don't recalculate tracklength unless the start/stop waypoint has changed
1648 if (curr &&
1649 ((curr->getName() != trackCache.startWptName) ||
1650 (wptName != trackCache.finalWptName))) {
1651 trackCache.remainingLength = fp->checkTrackLength(wptName);
1652 trackCache.startWptName = curr->getName();
1653 trackCache.finalWptName = wptName;
1654 }
1655 double tracklength = trackCache.remainingLength;
1656 if (tracklength > 0.1) {
1657 tracklength += fp->getDistanceToGo(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr);
1658 } else {
1659 return 0;
1660 }
1661 time_t now = globals->get_time_params()->get_cur_time();
1662 time_t arrivalTime = fp->getArrivalTime();
1663
1664 time_t ete = tracklength / ((speed * SG_NM_TO_METER) / 3600.0);
1665 time_t secondsToGo = arrivalTime - now;
1666 if (tracked) {
1667 SG_LOG(SG_AI, SG_BULK, "Checking arrival time: ete " << ete << ". Time to go : " << secondsToGo << ". Track length = " << tracklength);
1668 }
1669 return (ete - secondsToGo); // Positive when we're too slow...
1670}
1671
1673{
1674 time_t departure = this->checkForArrivalTime("DepartureHold");
1675 if (!departure) {
1676 departure = globals->get_time_params()->get_cur_time();
1677 }
1678 return departure;
1679}
1680
1681double limitRateOfChange(double cur, double target, double maxDeltaSec, double dt)
1682{
1683 double delta = target - cur;
1684 double maxDelta = maxDeltaSec * dt;
1685
1686 // if delta is > maxDelta, use maxDelta, but with the sign of delta.
1687 return (fabs(delta) < maxDelta) ? delta : copysign(maxDelta, delta);
1688}
1689
1690// Drive various properties in a semi-realistic fashion.
1691// Note that we assume that the properties are set at
1692// a waypoint rather than in the leg before. So we need
1693// to use the previous waypoint (i.e. the one just passed)
1694// rather than the current one (i.e. the next one on the route)
1695void FGAIAircraft::updateModelProperties(double dt)
1696{
1697 if ((!fp) || (!fp->getPreviousWaypoint())) {
1698 return;
1699 }
1700
1701 double targetGearPos = fp->getPreviousWaypoint()->getGear_down() ? 1.0 : 0.0;
1702 double gearPos = GearPos();
1703
1704 if (gearPos != targetGearPos) {
1705 gearPos = gearPos + limitRateOfChange(gearPos, targetGearPos, 0.1, dt);
1706 if (gearPos < 0.001) {
1707 gearPos = 0.0;
1708 } else if (gearPos > 0.999) {
1709 gearPos = 1.0;
1710 }
1711 GearPos(gearPos);
1712 }
1713
1714 double targetFlapsPos = fp->getPreviousWaypoint()->getFlaps();
1715 double flapsPos = FlapsPos();
1716
1717 if (flapsPos != targetFlapsPos) {
1718 flapsPos = flapsPos + limitRateOfChange(flapsPos, targetFlapsPos, 0.05, dt);
1719 if (flapsPos < 0.001) {
1720 flapsPos = 0.0;
1721 } else if (flapsPos > 0.999) {
1722 flapsPos = 1.0;
1723 }
1724 FlapsPos(flapsPos);
1725 }
1726
1727 SpeedBrakePos(fp->getPreviousWaypoint()->getSpeedBrakes());
1728 SpoilerPos(fp->getPreviousWaypoint()->getSpoilers());
1729 CabinLight(fp->getPreviousWaypoint()->getCabinLight());
1730 BeaconLight(fp->getPreviousWaypoint()->getBeaconLight());
1731 LandingLight(fp->getPreviousWaypoint()->getLandingLight());
1732 NavLight(fp->getPreviousWaypoint()->getNavLight());
1733 StrobeLight(fp->getPreviousWaypoint()->getStrobeLight());
1734 TaxiLight(fp->getPreviousWaypoint()->getTaxiLight());
1735}
1736
1737void FGAIAircraft::dumpCSVHeader(const std::unique_ptr<sg_ofstream>& o)
1738{
1739 (*o) << "Index\t";
1740 (*o) << "Lat\t";
1741 (*o) << "Lon\t";
1742 (*o) << "Alt\t";
1743 (*o) << "Callsign\t";
1744 (*o) << "headingDiff\t";
1745 (*o) << "headingChangeRate\t";
1746 (*o) << "headingError\t";
1747 (*o) << "hdg\t";
1748 (*o) << "tgt_heading\t";
1749 (*o) << "tgt_speed\t";
1750 (*o) << "minBearing\t";
1751 (*o) << "speedFraction\t";
1752 (*o) << "groundOffset\t";
1753 (*o) << "speed\t";
1754 (*o) << "groundTargetSpeed\t";
1755 (*o) << "getVerticalSpeedFPM\t";
1756 (*o) << "getTrueHeadingDeg\t";
1757 (*o) << "bearingToWP\t";
1758
1759 (*o) << "Name\t";
1760 (*o) << "WP Lat\t";
1761 (*o) << "WP Lon\t";
1762 (*o) << "Dist\t";
1763 (*o) << "Next Lat\t";
1764 (*o) << "Next Lon\t";
1765 (*o) << "Departuretime\t";
1766 (*o) << "Time\t";
1767 (*o) << "Startup diff\t";
1768 (*o) << "Departure\t";
1769 (*o) << "Arrival\t";
1770 (*o) << "dist_to_go_m\t";
1771 (*o) << "leadInAngle\t";
1772 (*o) << "Leaddistance\t";
1773 (*o) << "Leg\t";
1774 (*o) << "Num WP\t";
1775 (*o) << "no_roll\t";
1776 (*o) << "roll\t";
1777 (*o) << "repositioned\t";
1778 (*o) << "stuckCounter\t";
1779 (*o) << "blockerId\t";
1780 (*o) << "holdPos\t";
1781 (*o) << std::endl;
1782}
1783
1784void FGAIAircraft::dumpCSV(const std::unique_ptr<sg_ofstream>& o, int lineIndex)
1785{
1786 const double headingDiff = SGMiscd::normalizePeriodic(-180, 180, hdg - tgt_heading);
1787
1788 (*o) << lineIndex << "\t";
1789 (*o) << std::setprecision(12);
1790 (*o) << this->getGeodPos().getLatitudeDeg() << "\t";
1791 (*o) << this->getGeodPos().getLongitudeDeg() << "\t";
1792 (*o) << this->getGeodPos().getElevationFt() << "\t";
1793 (*o) << this->getCallSign() << "\t";
1794 (*o) << headingDiff << "\t";
1795 (*o) << headingChangeRate << "\t";
1796 (*o) << headingError << "\t";
1797 (*o) << hdg << "\t";
1798 (*o) << tgt_heading << "\t";
1799 (*o) << tgt_speed << "\t";
1800 (*o) << minBearing << "\t";
1801 (*o) << speedFraction << "\t";
1802 (*o) << groundOffset << "\t";
1803
1804 (*o) << round(this->getSpeed()) << "\t";
1805 (*o) << groundTargetSpeed << "\t";
1806 (*o) << round(this->getVerticalSpeedFPM()) << "\t";
1807 (*o) << this->getTrueHeadingDeg() << "\t";
1808 FGAIFlightPlan* fp = this->GetFlightPlan();
1809 if (fp) {
1810 FGAIWaypoint* currentWP = fp->getCurrentWaypoint();
1811 FGAIWaypoint* nextWP = fp->getNextWaypoint();
1812 if (currentWP) {
1813 (*o) << fp->getBearing(this->getGeodPos(), currentWP) << "\t";
1814 (*o) << currentWP->getName() << "\t";
1815 (*o) << currentWP->getPos().getLatitudeDeg() << "\t";
1816 (*o) << currentWP->getPos().getLongitudeDeg() << "\t";
1817 }
1818 if (nextWP) {
1819 (*o) << nextWP->getPos().getLatitudeDeg() << "\t";
1820 (*o) << nextWP->getPos().getLongitudeDeg() << "\t";
1821 } else {
1822 (*o) << "\t\t";
1823 }
1824 if (currentWP) {
1825 (*o) << SGGeodesy::distanceM(this->getGeodPos(), currentWP->getPos()) << "\t";
1826 (*o) << fp->getStartTime() << "\t";
1827 (*o) << globals->get_time_params()->get_cur_time() << "\t";
1828 (*o) << fp->getStartTime() - globals->get_time_params()->get_cur_time() << "\t";
1829 (*o) << (fp->departureAirport().get() ? fp->departureAirport().get()->getId() : "") << "\t";
1830 (*o) << (fp->arrivalAirport().get() ? fp->arrivalAirport().get()->getId() : "") << "\t";
1831 if (nextWP) {
1832 double dist_to_go_m = fp->getDistanceToGo(pos.getLatitudeDeg(), pos.getLongitudeDeg(), currentWP);
1833 (*o) << dist_to_go_m << "\t";
1834 double inbound = bearing;
1835 double outbound = fp->getBearing(currentWP, nextWP);
1836 double leadInAngle = fabs(inbound - outbound);
1837 if (leadInAngle > 180.0) leadInAngle = 360.0 - leadInAngle;
1838 (*o) << leadInAngle << "\t";
1839 }
1840 } else {
1841 (*o) << "No WP\t\t\t\t\t\t\t\t";
1842 }
1843 if (fp->isValidPlan()) {
1844 (*o) << fp->getLeadDistance() * SG_FEET_TO_METER << "\t";
1845 (*o) << fp->getLeg() << "\t";
1846 (*o) << fp->getNrOfWayPoints() << "\t";
1847 } else {
1848 (*o) << "FP NotValid\t\t";
1849 }
1850 }
1851 (*o) << this->onGround() << "\t";
1852 (*o) << roll << "\t";
1853 (*o) << repositioned << "\t";
1854 (*o) << stuckCounter << "\t";
1855 (*o) << waitsForId << "\t";
1856 (*o) << holdPos << "\t";
1857 (*o) << std::endl;
1858}
1859
1860#if 0
1861std::string FGAIAircraft::getTimeString(int timeOffset)
1862{
1863 char ret[11];
1864 time_t rawtime;
1865 time (&rawtime);
1866 rawtime = rawtime + timeOffset;
1867 tm* timeinfo = gmtime(&rawtime);
1868 strftime(ret, 11, "%w/%H:%M:%S", timeinfo);
1869 return ret;
1870}
1871#endif
double limitRateOfChange(double cur, double target, double maxDeltaSec, double dt)
double fgIsFinite(double x)
Definition AIShip.cxx:15
#define p(x)
constexpr double TRAFFIC_TO_AI_DIST_TO_DIE
Definition Schedule.hxx:32
Wrap an FDM implementation in a subsystem with standard semantics Notably, deal with the various case...
Definition fdm_shell.hxx:44
void dumpCSVHeader(const std::unique_ptr< sg_ofstream > &o)
void clearATCController()
void setPerformance(const std::string &acType, const std::string &perfString)
double getVerticalSpeedFPM() const
double getBearing(double crse)
Returns a normalised bearing.
PerformanceData * getPerformance()
FGAIAircraft(FGAISchedule *ref=0)
void dumpCSV(const std::unique_ptr< sg_ofstream > &o, int lineIndex)
void getGroundElev(double dt)
time_t checkForArrivalTime(const std::string &wptName)
void Run(double dt)
virtual ~FGAIAircraft()
FGAIFlightPlan * GetFlightPlan() const
double getSpeed() const
bool loadNextLeg(double dist=0)
void TurnTo(double heading)
void doGroundAltitude()
void ProcessFlightPlan(double dt, time_t now)
void unbind() override
void processATC(const FGATCInstruction &instruction)
Process ATC instructions and report back.
void PitchTo(double angle)
void resetPositionFromFlightPlan()
void AccelTo(double speed)
bool onGround() const
FGAISchedule * getTrafficRef()
void RollTo(double angle)
void update(double dt) override
double calcVerticalSpeed(double vert_ft, double dist_m, double speed, double error)
bool isBlockedBy(FGAIAircraft *other)
void bind() override
void ClimbTo(double altitude)
void readFromScenario(SGPropertyNode *scFileNode) override
time_t calcDeparture()
void announcePositionToController()
const std::string & atGate()
void setFlightPlan(const std::string &fp, bool repat=false)
void scheduleForATCTowerRunwayControl()
bool NavLight() const
void bind() override
double FlapsPos() const
bool LandingLight() const
bool TaxiLight() const
double GearPos() const
bool CabinLight() const
double SpeedBrakePos() const
FGAIBaseAircraft(object_type otype=object_type::otAircraft)
bool BeaconLight() const
double SpoilerPos() const
bool StrobeLight() const
void setSpeed(double speed_KTAS)
Definition AIBase.hxx:404
double tgt_pitch
Definition AIBase.hxx:233
double tgt_heading
Definition AIBase.hxx:229
double altitude_ft
Definition AIBase.hxx:218
double turn_radius_ft
Definition AIBase.hxx:222
SGGeod pos
Definition AIBase.hxx:212
void setLatitude(double latitude)
Definition AIBase.hxx:446
virtual void readFromScenario(SGPropertyNode *scFileNode)
Definition AIBase.cxx:249
double tgt_yaw
Definition AIBase.hxx:234
std::string _callsign
Definition AIBase.hxx:188
double getTrueHeadingDeg() const
Definition AIBase.hxx:152
virtual void unbind()
Definition AIBase.cxx:800
void setDie(bool die)
Definition AIBase.hxx:506
const std::string & getCallSign() const
Definition AIBase.hxx:367
void setCallSign(const std::string &)
Definition AIBase.hxx:451
double _getLatitude() const
Definition AIBase.cxx:998
int getID() const
Definition AIBase.cxx:1092
void setFlightPlan(std::unique_ptr< FGAIFlightPlan > f)
Definition AIBase.cxx:1161
bool invisible
Definition AIBase.hxx:256
bool getGroundElevationM(const SGGeod &pos, double &elev, const simgear::BVHMaterial **material) const
Definition AIBase.cxx:904
SGGeod getGeodPos() const
Definition AIBase.cxx:1182
void setAltitude(double altitude_ft)
Definition AIBase.hxx:419
virtual void update(double dt)
Definition AIBase.cxx:294
double speed
Definition AIBase.hxx:216
double bearing
Definition AIBase.hxx:239
std::unique_ptr< FGAIFlightPlan > fp
Definition AIBase.hxx:264
double UpdateRadar(FGAIManager *manager)
Definition AIBase.cxx:819
double range
Definition AIBase.hxx:241
double _getLongitude() const
Definition AIBase.cxx:994
double vs_fps
Definition AIBase.hxx:219
double hdg
Definition AIBase.hxx:213
void Transform()
Definition AIBase.cxx:518
double tgt_speed
Definition AIBase.hxx:231
double pitch
Definition AIBase.hxx:215
void setLongitude(double longitude)
Definition AIBase.hxx:441
bool getDie()
Definition AIBase.hxx:508
double tgt_altitude_ft
Definition AIBase.hxx:230
void setHeading(double heading)
Definition AIBase.hxx:414
double tgt_roll
Definition AIBase.hxx:232
ModelSearchOrder _searchOrder
Definition AIBase.hxx:278
double roll
Definition AIBase.hxx:214
void tie(const char *aRelPath, const SGRawValue< T > &aRawValue)
Tied-properties helper, record nodes which are tied for easy un-tie-ing.
Definition AIBase.hxx:198
SGPropertyNode_ptr props
Definition AIBase.hxx:205
FGAIManager * manager
Definition AIBase.hxx:209
double tgt_vs
Definition AIBase.hxx:235
bool no_roll
Definition AIBase.hxx:257
FGAirport * getDepartureAirport()
Definition Schedule.cxx:528
double getSpeed()
bool contains(const std::string &name)
double getLatitude() const
double getCrossat()
const std::string & getName()
double getLongitude() const
double getAltitude() const
const SGGeod & getPos()
FGATCInstruction getInstruction(int id)
bool getChangeHeading() const
bool getChangeAltitude() const
bool getHoldPosition() const
double getSpeed() const
int getWaitsForId() const
bool getChangeSpeed() const
double getHeading() const
bool getCheckForCircularWait() const
bool getHoldPattern() const
SGTime * get_time_params() const
Definition globals.hxx:311
SGVec3d get_aircraft_position_cart() const
Definition globals.cxx:619
Registry for performance data.
double descentRate() const
double vDescent() const
static PerformanceData * getDefaultData()
Last-resort fallback performance data.
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
Definition fg_props.cxx:556
FGGlobals * globals
Definition globals.cxx:142
@ HOLD_PATTERN
@ RUNWAY_TAXI
@ PARKING_TAXI
@ STARTUP_PUSHBACK
float abs(float f)
Definition Airplane.cpp:21
double fgGetDouble(const char *name, double defaultValue)
Get a double value for a property.
Definition proptest.cpp:30