]> git.mxchange.org Git - flightgear.git/blob - src/AIModel/AIAircraft.cxx
Merge branch 'next' of gitorious.org:fg/flightgear into next
[flightgear.git] / src / AIModel / AIAircraft.cxx
1 // FGAIAircraft - FGAIBase-derived class creates an AI airplane
2 //
3 // Written by David Culp, started October 2003.
4 //
5 // Copyright (C) 2003  David P. Culp - davidculp2@comcast.net
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
21 #ifdef HAVE_CONFIG_H
22 #  include <config.h>
23 #endif
24
25 #include <simgear/route/waypoint.hxx>
26 #include <Main/fg_props.hxx>
27 #include <Main/globals.hxx>
28 #include <Main/viewer.hxx>
29 #include <Scenery/scenery.hxx>
30 #include <Scenery/tilemgr.hxx>
31 #include <Airports/dynamics.hxx>
32 #include <Airports/simple.hxx>
33
34 #include <string>
35 #include <math.h>
36 #include <time.h>
37
38 #ifdef _MSC_VER
39 #  include <float.h>
40 #  define finite _finite
41 #elif defined(__sun) || defined(sgi)
42 #  include <ieeefp.h>
43 #endif
44
45 using std::string;
46
47 #include "AIAircraft.hxx"
48 #include "performancedata.hxx"
49 #include "performancedb.hxx"
50
51
52 #define TGT_VS_CUTOFF 10000
53 //#include <Airports/trafficcontroller.hxx>
54
55 static string tempReg;
56
57 FGAIAircraft::FGAIAircraft(FGAISchedule *ref) :
58      /* HOT must be disabled for AI Aircraft,
59       * otherwise traffic detection isn't working as expected.*/
60      FGAIBase(otAircraft, false) 
61 {
62     trafficRef = ref;
63     if (trafficRef) {
64         groundOffset = trafficRef->getGroundOffset();
65         setCallSign(trafficRef->getCallSign());
66     }
67     else
68         groundOffset = 0;
69
70     fp              = 0;
71     controller      = 0;
72     prevController  = 0;
73     towerController = 0;
74     dt_count = 0;
75     dt_elev_count = 0;
76     use_perf_vs = true;
77
78     no_roll = false;
79     tgt_speed = 0;
80     speed = 0;
81     groundTargetSpeed = 0;
82
83     // set heading and altitude locks
84     hdg_lock = false;
85     alt_lock = false;
86     roll = 0;
87     headingChangeRate = 0.0;
88     headingError = 0;
89     minBearing = 360;
90     speedFraction =1.0;
91
92     holdPos = false;
93     needsTaxiClearance = false;
94     _needsGroundElevation = true;
95
96     _performance = 0; //TODO initialize to JET_TRANSPORT from PerformanceDB
97     dt = 0;
98     takeOffStatus = 0;
99 }
100
101
102 FGAIAircraft::~FGAIAircraft() {
103     //delete fp;
104     if (controller)
105         controller->signOff(getID());
106 }
107
108
109 void FGAIAircraft::readFromScenario(SGPropertyNode* scFileNode) {
110     if (!scFileNode)
111         return;
112
113     FGAIBase::readFromScenario(scFileNode);
114
115     setPerformance(scFileNode->getStringValue("class", "jet_transport"));
116     setFlightPlan(scFileNode->getStringValue("flightplan"),
117                   scFileNode->getBoolValue("repeat", false));
118     setCallSign(scFileNode->getStringValue("callsign"));
119 }
120
121
122 void FGAIAircraft::bind() {
123     FGAIBase::bind();
124
125     props->tie("controls/gear/gear-down",
126                SGRawValueMethods<FGAIAircraft,bool>(*this,
127                                                     &FGAIAircraft::_getGearDown));
128     props->tie("transponder-id",
129                SGRawValueMethods<FGAIAircraft,const char*>(*this,
130                                                     &FGAIAircraft::_getTransponderCode));
131 }
132
133
134 void FGAIAircraft::unbind() {
135     FGAIBase::unbind();
136
137     props->untie("controls/gear/gear-down");
138     props->untie("transponder-id");
139 }
140
141
142 void FGAIAircraft::update(double dt) {
143     FGAIBase::update(dt);
144     Run(dt);
145     Transform();
146 }
147
148 void FGAIAircraft::setPerformance(const std::string& acclass) {
149      static PerformanceDB perfdb; //TODO make it a global service
150      setPerformance(perfdb.getDataFor(acclass));
151   }
152
153
154  void FGAIAircraft::setPerformance(PerformanceData *ps) {
155      _performance = ps;
156   }
157
158
159  void FGAIAircraft::Run(double dt) {
160       
161       FGAIAircraft::dt = dt;
162     
163      bool outOfSight = false, 
164         flightplanActive = true;
165      updatePrimaryTargetValues(flightplanActive, outOfSight); // target hdg, alt, speed
166      if (outOfSight) {
167         return;
168      }
169
170      if (!flightplanActive) {
171         groundTargetSpeed = 0;
172      }
173
174      handleATCRequests(); // ATC also has a word to say
175      updateSecondaryTargetValues(); // target roll, vertical speed, pitch
176      updateActualState(); 
177     // We currently have one situation in which an AIAircraft object is used that is not attached to the 
178     // AI manager. In this particular case, the AIAircraft is used to shadow the user's aircraft's behavior in the AI world.
179     // Since we perhaps don't want a radar entry of our own aircraft, the following conditional should probably be adequate
180     // enough
181      if (manager)
182         UpdateRadar(manager);
183      checkVisibility();
184   }
185
186 void FGAIAircraft::checkVisibility() 
187 {
188   double visibility_meters = fgGetDouble("/environment/visibility-m");
189   FGViewer* vw = globals->get_current_view();
190   invisible = (SGGeodesy::distanceM(vw->getPosition(), pos) > visibility_meters);
191 }
192
193
194
195 void FGAIAircraft::AccelTo(double speed) {
196     tgt_speed = speed;
197     if (!isStationary())
198         _needsGroundElevation = true;
199 }
200
201
202 void FGAIAircraft::PitchTo(double angle) {
203     tgt_pitch = angle;
204     alt_lock = false;
205 }
206
207
208 void FGAIAircraft::RollTo(double angle) {
209     tgt_roll = angle;
210     hdg_lock = false;
211 }
212
213
214 void FGAIAircraft::YawTo(double angle) {
215     tgt_yaw = angle;
216 }
217
218
219 void FGAIAircraft::ClimbTo(double alt_ft ) {
220     tgt_altitude_ft = alt_ft;
221     alt_lock = true;
222 }
223
224
225 void FGAIAircraft::TurnTo(double heading) {
226     tgt_heading = heading;
227     hdg_lock = true;
228 }
229
230
231 double FGAIAircraft::sign(double x) {
232     if (x == 0.0)
233         return x;
234     else
235         return x/fabs(x);
236 }
237
238
239 void FGAIAircraft::setFlightPlan(const std::string& flightplan, bool repeat) {
240     if (!flightplan.empty()) {
241         FGAIFlightPlan* fp = new FGAIFlightPlan(flightplan);
242         fp->setRepeat(repeat);
243         SetFlightPlan(fp);
244     }
245 }
246
247
248 void FGAIAircraft::SetFlightPlan(FGAIFlightPlan *f) {
249     delete fp;
250     fp = f;
251 }
252
253
254 void FGAIAircraft::ProcessFlightPlan( double dt, time_t now ) {
255
256     // the one behind you
257     FGAIWaypoint* prev = 0;
258     // the one ahead
259     FGAIWaypoint* curr = 0;
260     // the next plus 1
261     FGAIWaypoint* next = 0;
262
263     prev = fp->getPreviousWaypoint();
264     curr = fp->getCurrentWaypoint();
265     next = fp->getNextWaypoint();
266
267     dt_count += dt;
268
269     ///////////////////////////////////////////////////////////////////////////
270     // Initialize the flightplan
271     //////////////////////////////////////////////////////////////////////////
272     if (!prev) {
273         handleFirstWaypoint();
274         return;
275     }                            // end of initialization
276     if (! fpExecutable(now))
277           return;
278     dt_count = 0;
279
280     double distanceToDescent;
281     if(reachedEndOfCruise(distanceToDescent)) {
282         if (!loadNextLeg(distanceToDescent)) {
283             setDie(true);
284             return;
285         }
286         prev = fp->getPreviousWaypoint();
287         curr = fp->getCurrentWaypoint();
288         next = fp->getNextWaypoint();
289     }
290     if (! leadPointReached(curr)) {
291         controlHeading(curr);
292         controlSpeed(curr, next);
293             /*
294             if (speed < 0) { 
295                 cerr << getCallSign() 
296                      << ": verifying lead distance to waypoint : " 
297                      << fp->getCurrentWaypoint()->name << " "
298                      << fp->getLeadDistance() << ". Distance to go " 
299                      << (fp->getDistanceToGo(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr)) 
300                      << ". Target speed = " 
301                      << tgt_speed
302                      << ". Current speed = "
303                      << speed
304                      << ". Minimum Bearing " << minBearing
305                      << endl;
306             } */
307     } else {
308         if (curr->isFinished())      //end of the flight plan
309         {
310             if (fp->getRepeat())
311                 fp->restart();
312             else
313                 setDie(true);
314             return;
315         }
316
317         if (next) {
318             //TODO more intelligent method in AIFlightPlan, no need to send data it already has :-)
319             tgt_heading = fp->getBearing(curr, next);
320             spinCounter = 0;
321         }
322
323         //TODO let the fp handle this (loading of next leg)
324         fp->IncrementWaypoint( trafficRef != 0 );
325         if  ( ((!(fp->getNextWaypoint()))) && (trafficRef != 0) )
326             if (!loadNextLeg()) {
327                 setDie(true);
328                 return;
329             }
330
331         prev = fp->getPreviousWaypoint();
332         curr = fp->getCurrentWaypoint();
333         next = fp->getNextWaypoint();
334
335         // Now that we have incremented the waypoints, excute some traffic manager specific code
336         if (trafficRef) {
337             //TODO isn't this best executed right at the beginning?
338             if (! aiTrafficVisible()) {
339                 setDie(true);
340                 return;
341             }
342
343             if (! handleAirportEndPoints(prev, now)) {
344                 setDie(true);
345                 return;
346             }
347
348             announcePositionToController();
349
350         }
351
352         if (next) {
353             fp->setLeadDistance(tgt_speed, tgt_heading, curr, next);
354         }
355
356         if (!(prev->getOn_ground()))  // only update the tgt altitude from flightplan if not on the ground
357         {
358             tgt_altitude_ft = prev->getAltitude();
359             if (curr->getCrossat() > -1000.0) {
360                 use_perf_vs = false;
361 //                 tgt_vs = (curr->getCrossat() - altitude_ft) / (fp->getDistanceToGo(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr)
362 //                          / 6076.0 / speed*60.0);
363 //                 if (fabs(tgt_vs) > TGT_VS_CUTOFF) { SG_LOG(SG_GENERAL, SG_ALERT, "Rediculously high vertical speed caculated at " << SG_ORIGIN << ". Corresponding to " << (tgt_vs * .005) << "degrees of pitch angle" << prev->getName()); };
364 //                 if (tgt_vs < -1500)
365 //                     tgt_vs = -1500;
366 //                 if (tgt_vs > 1500) 
367 //                     tgt_vs = 1500;
368 //                 checkTcas();
369                 tgt_altitude_ft = curr->getCrossat();
370             } else {
371                 use_perf_vs = true;
372             }
373         }
374         AccelTo(prev->getSpeed());
375         hdg_lock = alt_lock = true;
376         no_roll = prev->getOn_ground();
377     }
378 }
379
380 void FGAIAircraft::checkTcas(void)
381 {
382     if (props->getIntValue("tcas/threat-level",0)==3)
383     {
384         int RASense = props->getIntValue("tcas/ra-sense",0);
385         if ((RASense>0)&&(tgt_vs<4000))
386             // upward RA: climb!
387             tgt_vs = 4000;
388         else
389         if (RASense<0)
390         {
391             // downward RA: descend!
392             if (altitude_ft < 1000)
393             {
394                 // too low: level off
395                 if (tgt_vs>0)
396                     tgt_vs = 0;
397             }
398             else
399             {
400                 if (tgt_vs >- 4000)
401                     tgt_vs = -4000;
402             }
403         }
404     }
405 }
406
407 void FGAIAircraft::initializeFlightPlan() {
408 }
409
410
411 bool FGAIAircraft::_getGearDown() const {
412     return _performance->gearExtensible(this);
413 }
414
415
416 const char * FGAIAircraft::_getTransponderCode() const {
417   return transponderCode.c_str();
418 }
419
420 // NOTE: Check whether the new (delayed leg increment code has any effect on this code.
421 // Probably not, because it should only be executed after we have already passed the leg incrementing waypoint. 
422
423 bool FGAIAircraft::loadNextLeg(double distance) {
424
425     int leg;
426     if ((leg = fp->getLeg())  == 9) {
427         if (!trafficRef->next()) {
428             return false;
429         }
430         setCallSign(trafficRef->getCallSign());
431         leg = 0;
432         fp->setLeg(leg);
433     }
434
435     FGAirport *dep = trafficRef->getDepartureAirport();
436     FGAirport *arr = trafficRef->getArrivalAirport();
437     if (!(dep && arr)) {
438         setDie(true);
439
440     } else {
441         double cruiseAlt = trafficRef->getCruiseAlt() * 100;
442
443         fp->create (this,
444                     dep,
445                     arr,
446                     leg+1,
447                     cruiseAlt,
448                     trafficRef->getSpeed(),
449                     _getLatitude(),
450                     _getLongitude(),
451                     false,
452                     trafficRef->getRadius(),
453                     trafficRef->getFlightType(),
454                     acType,
455                     company,
456                     distance);
457        //cerr << "created  leg " << leg << " for " << trafficRef->getCallSign() << endl;
458     }
459     return true;
460 }
461
462
463 // Note: This code is copied from David Luff's AILocalTraffic
464 // Warning - ground elev determination is CPU intensive
465 // Either this function or the logic of how often it is called
466 // will almost certainly change.
467
468 void FGAIAircraft::getGroundElev(double dt) {
469     dt_elev_count += dt;
470
471     if (!needGroundElevation())
472         return;
473     // Update minimally every three secs, but add some randomness
474     // to prevent all AI objects doing this in synchrony
475     if (dt_elev_count < (3.0) + (rand() % 10))
476         return;
477
478     dt_elev_count = 0;
479
480     // Only do the proper hitlist stuff if we are within visible range of the viewer.
481     if (!invisible) {
482         double visibility_meters = fgGetDouble("/environment/visibility-m");
483         FGViewer* vw = globals->get_current_view();
484         
485         if (SGGeodesy::distanceM(vw->getPosition(), pos) > visibility_meters) {
486             return;
487         }
488
489         double range = 500.0;
490         if (globals->get_tile_mgr()->schedule_scenery(pos, range, 5.0))
491         {
492             double alt;
493             if (getGroundElevationM(SGGeod::fromGeodM(pos, 20000), alt, 0))
494             {
495                 tgt_altitude_ft = alt * SG_METER_TO_FEET;
496                 if (isStationary())
497                 {
498                     // aircraft is stationary and we obtained altitude for this spot - we're done.
499                     _needsGroundElevation = false;
500                 }
501             }
502         }
503     }
504 }
505
506
507 void FGAIAircraft::doGroundAltitude() {
508
509     if ((fabs(altitude_ft - (tgt_altitude_ft+groundOffset)) > 1000.0)||
510         (isStationary()))
511         altitude_ft = (tgt_altitude_ft + groundOffset);
512     else
513         altitude_ft += 0.1 * ((tgt_altitude_ft+groundOffset) - altitude_ft);
514     tgt_vs = 0;
515 }
516
517
518 void FGAIAircraft::announcePositionToController() {
519     if (trafficRef) {
520         int leg = fp->getLeg();
521
522         // Note that leg has been incremented after creating the current leg, so we should use
523         // leg numbers here that are one higher than the number that is used to create the leg
524         // NOTE: As of July, 30, 2011, the post-creation leg updating is no longer happening. 
525         // Leg numbers are updated only once the aircraft passes the last waypoint created for that legm so I should probably just use
526         // the original leg numbers here!
527         switch (leg) {
528           case 1:              // Startup and Push back
529             if (trafficRef->getDepartureAirport()->getDynamics())
530                 controller = trafficRef->getDepartureAirport()->getDynamics()->getStartupController();
531             break;
532         case 2:              // Taxiing to runway
533             if (trafficRef->getDepartureAirport()->getDynamics()->getGroundNetwork()->exists())
534                 controller = trafficRef->getDepartureAirport()->getDynamics()->getGroundNetwork();
535             break;
536         case 3:              //Take off tower controller
537             if (trafficRef->getDepartureAirport()->getDynamics()) {
538                 controller = trafficRef->getDepartureAirport()->getDynamics()->getTowerController();
539             } else {
540                 cerr << "Error: Could not find Dynamics at airport : " << trafficRef->getDepartureAirport()->getId() << endl;
541             }
542             break;
543         case 6:
544              if (trafficRef->getDepartureAirport()->getDynamics()) {
545                  controller = trafficRef->getArrivalAirport()->getDynamics()->getApproachController();
546               }
547               break;
548         case 8:              // Taxiing for parking
549             if (trafficRef->getArrivalAirport()->getDynamics()->getGroundNetwork()->exists())
550                 controller = trafficRef->getArrivalAirport()->getDynamics()->getGroundNetwork();
551             break;
552         default:
553             controller = 0;
554             break;
555         }
556
557         if ((controller != prevController) && (prevController != 0)) {
558             prevController->signOff(getID());
559         }
560         prevController = controller;
561         if (controller) {
562             controller->announcePosition(getID(), fp, fp->getCurrentWaypoint()->getRouteIndex(),
563                                          _getLatitude(), _getLongitude(), hdg, speed, altitude_ft,
564                                          trafficRef->getRadius(), leg, this);
565         }
566     }
567 }
568
569 void FGAIAircraft::scheduleForATCTowerDepartureControl(int state) {
570     if (!takeOffStatus) {
571         int leg = fp->getLeg();
572         if (trafficRef) {
573             if (trafficRef->getDepartureAirport()->getDynamics()) {
574                 towerController = trafficRef->getDepartureAirport()->getDynamics()->getTowerController();
575             } else {
576                 cerr << "Error: Could not find Dynamics at airport : " << trafficRef->getDepartureAirport()->getId() << endl;
577             }
578             if (towerController) {
579                 towerController->announcePosition(getID(), fp, fp->getCurrentWaypoint()->getRouteIndex(),
580                                                    _getLatitude(), _getLongitude(), hdg, speed, altitude_ft,
581                                                     trafficRef->getRadius(), leg, this);
582                 //cerr << "Scheduling " << trafficRef->getCallSign() << " for takeoff " << endl;
583             }
584         }
585     }
586     takeOffStatus = state;
587 }
588
589 // Process ATC instructions and report back
590
591 void FGAIAircraft::processATC(FGATCInstruction instruction) {
592     if (instruction.getCheckForCircularWait()) {
593         // This is not exactly an elegant solution, 
594         // but at least it gives me a chance to check
595         // if circular waits are resolved.
596         // For now, just take the offending aircraft 
597         // out of the scene
598         setDie(true);
599         // a more proper way should be - of course - to
600         // let an offending aircraft take an evasive action
601         // for instance taxi back a little bit.
602     }
603     //cerr << "Processing ATC instruction (not Implimented yet)" << endl;
604     if (instruction.getHoldPattern   ()) {}
605
606     // Hold Position
607     if (instruction.getHoldPosition  ()) {
608         if (!holdPos) {
609             holdPos = true;
610         }
611         AccelTo(0.0);
612     } else {
613         if (holdPos) {
614             //if (trafficRef)
615             //  cerr << trafficRef->getCallSign() << " Resuming Taxi." << endl;
616             holdPos = false;
617         }
618         // Change speed Instruction. This can only be excecuted when there is no
619         // Hold position instruction.
620         if (instruction.getChangeSpeed   ()) {
621             //  if (trafficRef)
622             //cerr << trafficRef->getCallSign() << " Changing Speed " << endl;
623             AccelTo(instruction.getSpeed());
624         } else {
625             if (fp) AccelTo(fp->getPreviousWaypoint()->getSpeed());
626         }
627     }
628     if (instruction.getChangeHeading ()) {
629         hdg_lock = false;
630         TurnTo(instruction.getHeading());
631     } else {
632         if (fp) {
633             hdg_lock = true;
634         }
635     }
636     if (instruction.getChangeAltitude()) {}
637
638 }
639
640
641 void FGAIAircraft::handleFirstWaypoint() {
642     bool eraseWaypoints;         //TODO YAGNI
643     headingError = 0;
644     if (trafficRef) {
645         eraseWaypoints = true;
646     } else {
647         eraseWaypoints = false;
648     }
649
650     FGAIWaypoint* prev = 0; // the one behind you
651     FGAIWaypoint* curr = 0; // the one ahead
652     FGAIWaypoint* next = 0;// the next plus 1
653
654     spinCounter = 0;
655     tempReg = "";
656
657     //TODO fp should handle this
658     fp->IncrementWaypoint(eraseWaypoints);
659     if (!(fp->getNextWaypoint()) && trafficRef)
660         if (!loadNextLeg()) {
661             setDie(true);
662             return;
663         }
664
665     prev = fp->getPreviousWaypoint();   //first waypoint
666     curr = fp->getCurrentWaypoint();    //second waypoint
667     next = fp->getNextWaypoint();       //third waypoint (might not exist!)
668
669     setLatitude(prev->getLatitude());
670     setLongitude(prev->getLongitude());
671     setSpeed(prev->getSpeed());
672     setAltitude(prev->getAltitude());
673
674     if (prev->getSpeed() > 0.0)
675         setHeading(fp->getBearing(prev->getLatitude(), prev->getLongitude(), curr));
676     else
677         setHeading(fp->getBearing(curr->getLatitude(), curr->getLongitude(), prev));
678
679     // If next doesn't exist, as in incrementally created flightplans for
680     // AI/Trafficmanager created plans,
681     // Make sure lead distance is initialized otherwise
682     if (next)
683         fp->setLeadDistance(speed, hdg, curr, next);
684
685     if (curr->getCrossat() > -1000.0) //use a calculated descent/climb rate
686     {
687         use_perf_vs = false;
688 /*        tgt_vs = (curr->getCrossat() - prev->getAltitude())
689                  / (fp->getDistanceToGo(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr)
690                     / 6076.0 / prev->getSpeed()*60.0);
691         if (fabs(tgt_vs) > TGT_VS_CUTOFF) { SG_LOG(SG_GENERAL, SG_ALERT, "Rediculously high vertical speed caculated at " << SG_ORIGIN); };
692         checkTcas();*/
693         tgt_altitude_ft = curr->getCrossat();
694     } else {
695         use_perf_vs = true;
696         tgt_altitude_ft = prev->getAltitude();
697     }
698     alt_lock = hdg_lock = true;
699     no_roll = prev->getOn_ground();
700     if (no_roll) {
701         Transform();             // make sure aip is initialized.
702         getGroundElev(60.1);     // make sure it's executed first time around, so force a large dt value
703         doGroundAltitude();
704         _needsGroundElevation = true; // check ground elevation again (maybe scenery wasn't available yet)
705     }
706     // Make sure to announce the aircraft's position
707     announcePositionToController();
708     prevSpeed = 0;
709 }
710
711
712 /**
713  * Check Execution time (currently once every 100 ms)
714  * Add a bit of randomization to prevent the execution of all flight plans
715  * in synchrony, which can add significant periodic framerate flutter.
716  *
717  * @param now
718  * @return
719  */
720 bool FGAIAircraft::fpExecutable(time_t now) {
721     double rand_exec_time = (rand() % 100) / 100;
722     return (dt_count > (0.1+rand_exec_time)) && (fp->isActive(now));
723 }
724
725
726 /**
727  * Check to see if we've reached the lead point for our next turn
728  *
729  * @param curr
730  * @return
731  */
732 bool FGAIAircraft::leadPointReached(FGAIWaypoint* curr) {
733     double dist_to_go = fp->getDistanceToGo(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr);
734
735     //cerr << "2" << endl;
736     double lead_dist = fp->getLeadDistance();
737     // experimental: Use fabs, because speed can be negative (I hope) during push_back.
738     if ((dist_to_go < fabs(10.0* speed)) && (speed < 0) && (tgt_speed < 0) && fp->getCurrentWaypoint()->contains("PushBackPoint")) {
739           tgt_speed = -(dist_to_go / 10.0);
740           if (tgt_speed > -0.5) {
741                 tgt_speed = -0.5;
742           }
743           if (fp->getPreviousWaypoint()->getSpeed() < tgt_speed) {
744               fp->getPreviousWaypoint()->setSpeed(tgt_speed);
745           }
746     }
747     if (lead_dist < fabs(2*speed)) {
748       //don't skip over the waypoint
749       lead_dist = fabs(2*speed);
750       //cerr << "Extending lead distance to " << lead_dist << endl;
751     }
752
753     //prev_dist_to_go = dist_to_go;
754     //if (dist_to_go < lead_dist)
755     //     cerr << trafficRef->getCallSign() << " Distance : " 
756     //          << dist_to_go << ": Lead distance " 
757     //          << lead_dist << " " << curr->name 
758     //          << " Ground target speed " << groundTargetSpeed << endl;
759     double bearing = 0;
760     if (speed > 50) { // don't do bearing calculations for ground traffic
761        bearing = getBearing(fp->getBearing(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr));
762        if (bearing < minBearing) {
763             minBearing = bearing;
764             if (minBearing < 10) {
765                  minBearing = 10;
766             }
767             if ((minBearing < 360.0) && (minBearing > 10.0)) {
768                 speedFraction = cos(minBearing *SG_DEGREES_TO_RADIANS);
769             } else {
770                 speedFraction = 1.0;
771             }
772        }
773     } 
774     if (trafficRef) {
775          //cerr << "Tracking callsign : \"" << fgGetString("/ai/track-callsign") << "\"" << endl;
776          if (trafficRef->getCallSign() == fgGetString("/ai/track-callsign")) {
777               cerr << trafficRef->getCallSign() << " " << tgt_altitude_ft << " " << _getSpeed() << " " 
778                    << _getAltitude() << " "<< _getLatitude() << " " << _getLongitude() << " " << dist_to_go << " " << lead_dist << " " << curr->getName() << " " << vs << " " << tgt_vs << " " << bearing << " " << minBearing << " " << speedFraction << " " << invisible << endl; 
779          }
780      }
781     if ((dist_to_go < lead_dist) || (bearing > (minBearing * 1.1))) {
782         minBearing = 360;
783         return true;
784     } else {
785         return false;
786     }
787 }
788
789
790 bool FGAIAircraft::aiTrafficVisible() {
791   SGGeod userPos(SGGeod::fromDeg(fgGetDouble("/position/longitude-deg"), 
792     fgGetDouble("/position/latitude-deg")));
793   
794   return (SGGeodesy::distanceNm(userPos, pos) <= TRAFFICTOAIDISTTODIE);
795 }
796
797
798 /**
799  * Handle release of parking gate, once were taxiing. Also ensure service time at the gate
800  * in the case of an arrival.
801  *
802  * @param prev
803  * @return
804  */
805
806 //TODO the trafficRef is the right place for the method
807 bool FGAIAircraft::handleAirportEndPoints(FGAIWaypoint* prev, time_t now) {
808     // prepare routing from one airport to another
809     FGAirport * dep = trafficRef->getDepartureAirport();
810     FGAirport * arr = trafficRef->getArrivalAirport();
811
812     if (!( dep && arr))
813         return false;
814
815     // This waypoint marks the fact that the aircraft has passed the initial taxi
816     // departure waypoint, so it can release the parking.
817     //cerr << trafficRef->getCallSign() << " has passed waypoint " << prev->name << " at speed " << speed << endl;
818     //cerr << "Passing waypoint : " << prev->getName() << endl;
819     if (prev->contains("PushBackPoint")) {
820         dep->getDynamics()->releaseParking(fp->getGate());
821         AccelTo(0.0);
822         //setTaxiClearanceRequest(true);
823     }
824     if (prev->contains("legend")) {
825         fp->incrementLeg();
826     }
827     if (prev->contains(string("DepartureHold"))) {
828         //cerr << "Passing point DepartureHold" << endl;
829         scheduleForATCTowerDepartureControl(2);
830     }
831
832     // This is the last taxi waypoint, and marks the the end of the flight plan
833     // so, the schedule should update and wait for the next departure time.
834     if (prev->contains("END")) {
835         time_t nextDeparture = trafficRef->getDepartureTime();
836         // make sure to wait at least 20 minutes at parking to prevent "nervous" taxi behavior
837         if (nextDeparture < (now+1200)) {
838             nextDeparture = now + 1200;
839         }
840         fp->setTime(nextDeparture);
841     }
842
843     return true;
844 }
845
846
847 /**
848  * Check difference between target bearing and current heading and correct if necessary.
849  *
850  * @param curr
851  */
852 void FGAIAircraft::controlHeading(FGAIWaypoint* curr) {
853     double calc_bearing = fp->getBearing(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr);
854     //cerr << "Bearing = " << calc_bearing << endl;
855     if (speed < 0) {
856         calc_bearing +=180;
857         if (calc_bearing > 360)
858             calc_bearing -= 360;
859     }
860
861     if (finite(calc_bearing)) {
862         double hdg_error = calc_bearing - tgt_heading;
863         if (fabs(hdg_error) > 0.01) {
864             TurnTo( calc_bearing );
865         }
866
867     } else {
868         cerr << "calc_bearing is not a finite number : "
869         << "Speed " << speed
870         << "pos : " << pos.getLatitudeDeg() << ", " << pos.getLongitudeDeg()
871         << "waypoint " << curr->getLatitude() << ", " << curr->getLongitude() << endl;
872         cerr << "waypoint name " << curr->getName();
873         exit(1);                 // FIXME
874     }
875 }
876
877
878 /**
879  * Update the lead distance calculation if speed has changed sufficiently
880  * to prevent spinning (hopefully);
881  *
882  * @param curr
883  * @param next
884  */
885 void FGAIAircraft::controlSpeed(FGAIWaypoint* curr, FGAIWaypoint* next) {
886     double speed_diff = speed - prevSpeed;
887
888     if (fabs(speed_diff) > 10) {
889         prevSpeed = speed;
890         if (next) {
891             fp->setLeadDistance(speed, tgt_heading, curr, next);
892         }
893     }
894 }
895
896
897 /**
898  * Update target values (heading, alt, speed) depending on flight plan or control properties
899  */
900 void FGAIAircraft::updatePrimaryTargetValues(bool& flightplanActive, bool& aiOutOfSight) {
901     if (fp)                      // AI object has a flightplan
902     {
903         //TODO make this a function of AIBase
904         time_t now = time(NULL) + fgGetLong("/sim/time/warp");
905         //cerr << "UpateTArgetValues() " << endl;
906         ProcessFlightPlan(dt, now);
907
908         // Do execute Ground elev for inactive aircraft, so they
909         // Are repositioned to the correct ground altitude when the user flies within visibility range.
910         // In addition, check whether we are out of user range, so this aircraft
911         // can be deleted.
912         if (onGround()) {
913                 Transform();     // make sure aip is initialized.
914                 getGroundElev(dt);
915                 doGroundAltitude();
916                 // Transform();
917                 pos.setElevationFt(altitude_ft);
918         }
919         if (trafficRef) {
920            //cerr << trafficRef->getRegistration() << " Setting altitude to " << altitude_ft;
921             aiOutOfSight = !aiTrafficVisible();
922             if (aiOutOfSight) {
923                 setDie(true);
924                 //cerr << trafficRef->getRegistration() << " is set to die " << endl;
925                 aiOutOfSight = true;
926                 return;
927             }
928         }
929         timeElapsed = now - fp->getStartTime();
930         flightplanActive = fp->isActive(now);
931     } else {
932         // no flight plan, update target heading, speed, and altitude
933         // from control properties.  These default to the initial
934         // settings in the config file, but can be changed "on the
935         // fly".
936         string lat_mode = props->getStringValue("controls/flight/lateral-mode");
937         if ( lat_mode == "roll" ) {
938             double angle
939             = props->getDoubleValue("controls/flight/target-roll" );
940             RollTo( angle );
941         } else {
942             double angle
943             = props->getDoubleValue("controls/flight/target-hdg" );
944             TurnTo( angle );
945         }
946
947         string lon_mode
948         = props->getStringValue("controls/flight/longitude-mode");
949         if ( lon_mode == "alt" ) {
950             double alt = props->getDoubleValue("controls/flight/target-alt" );
951             ClimbTo( alt );
952         } else {
953             double angle
954             = props->getDoubleValue("controls/flight/target-pitch" );
955             PitchTo( angle );
956         }
957
958         AccelTo( props->getDoubleValue("controls/flight/target-spd" ) );
959     }
960 }
961
962 void FGAIAircraft::updatePosition() {
963     // convert speed to degrees per second
964     double speed_north_deg_sec = cos( hdg * SGD_DEGREES_TO_RADIANS )
965                                  * speed * 1.686 / ft_per_deg_lat;
966     double speed_east_deg_sec  = sin( hdg * SGD_DEGREES_TO_RADIANS )
967                                  * speed * 1.686 / ft_per_deg_lon;
968
969     // set new position
970     pos.setLatitudeDeg( pos.getLatitudeDeg() + speed_north_deg_sec * dt);
971     pos.setLongitudeDeg( pos.getLongitudeDeg() + speed_east_deg_sec * dt);
972 }
973
974
975 void FGAIAircraft::updateHeading() {
976     // adjust heading based on current bank angle
977     if (roll == 0.0)
978         roll = 0.01;
979
980     if (roll != 0.0) {
981         // double turnConstant;
982         //if (no_roll)
983         //  turnConstant = 0.0088362;
984         //else
985         //  turnConstant = 0.088362;
986         // If on ground, calculate heading change directly
987         if (onGround()) {
988             double headingDiff = fabs(hdg-tgt_heading);
989             double bank_sense = 0.0;
990         /*
991         double diff = fabs(hdg - tgt_heading);
992         if (diff > 180)
993             diff = fabs(diff - 360);
994
995         double sum = hdg + diff;
996         if (sum > 360.0)
997             sum -= 360.0;
998         if (fabs(sum - tgt_heading) < 1.0) {
999             bank_sense = 1.0;    // right turn
1000         } else {
1001             bank_sense = -1.0;   // left turn
1002         }*/
1003             if (headingDiff > 180)
1004                 headingDiff = fabs(headingDiff - 360);
1005             double sum = hdg + headingDiff;
1006             if (sum > 360.0) 
1007                 sum -= 360.0;
1008             if (fabs(sum - tgt_heading) > 0.0001) {
1009                 bank_sense = -1.0;
1010             } else {
1011                 bank_sense = 1.0;
1012             }
1013             //if (trafficRef)
1014             //  cerr << trafficRef->getCallSign() << " Heading " 
1015             //         << hdg << ". Target " << tgt_heading <<  ". Diff " << fabs(sum - tgt_heading) << ". Speed " << speed << endl;
1016             //if (headingDiff > 60) {
1017             groundTargetSpeed = tgt_speed; // * cos(headingDiff * SG_DEGREES_TO_RADIANS);
1018                 //groundTargetSpeed = tgt_speed - tgt_speed * (headingDiff/180);
1019             //} else {
1020             //    groundTargetSpeed = tgt_speed;
1021             //}
1022             if (sign(groundTargetSpeed) != sign(tgt_speed))
1023                 groundTargetSpeed = 0.21 * sign(tgt_speed); // to prevent speed getting stuck in 'negative' mode
1024             
1025             // 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.
1026             if (speed != 0) {
1027                 if (headingDiff > 30.0) {
1028                     // invert if pushed backward
1029                     headingChangeRate += 10.0 * dt * sign(roll);
1030
1031                     // Clamp the maximum steering rate to 30 degrees per second,
1032                     // But only do this when the heading error is decreasing.
1033                     if ((headingDiff < headingError)) {
1034                         if (headingChangeRate > 30)
1035                             headingChangeRate = 30;
1036                         else if (headingChangeRate < -30)
1037                             headingChangeRate = -30;
1038                     }
1039                 } else {
1040                     if (speed != 0) {
1041                         if (fabs(headingChangeRate) > headingDiff)
1042                             headingChangeRate = headingDiff*sign(roll);
1043                         else
1044                             headingChangeRate += dt * sign(roll);
1045                     }
1046                 }
1047             }
1048             if (trafficRef)
1049                 //cerr << trafficRef->getCallSign() << " Heading " 
1050                 //     << hdg << ". Target " << tgt_heading <<  ". Diff " << fabs(sum - tgt_heading) << ". Speed " << speed << "Heading change rate : " << headingChangeRate << " bacnk sence " << bank_sense << endl;
1051             hdg += headingChangeRate * dt * sqrt(fabs(speed) / 15);
1052             headingError = headingDiff;
1053         } else {
1054             if (fabs(speed) > 1.0) {
1055                 turn_radius_ft = 0.088362 * speed * speed
1056                                  / tan( fabs(roll) / SG_RADIANS_TO_DEGREES );
1057             } else {
1058                 // Check if turn_radius_ft == 0; this might lead to a division by 0.
1059                 turn_radius_ft = 1.0;
1060             }
1061             double turn_circum_ft = SGD_2PI * turn_radius_ft;
1062             double dist_covered_ft = speed * 1.686 * dt;
1063             double alpha = dist_covered_ft / turn_circum_ft * 360.0;
1064             hdg += alpha * sign(roll);
1065         }
1066         while ( hdg > 360.0 ) {
1067             hdg -= 360.0;
1068             spinCounter++;
1069         }
1070         while ( hdg < 0.0) {
1071             hdg += 360.0;
1072             spinCounter--;
1073         }
1074     }
1075 }
1076
1077
1078 void FGAIAircraft::updateBankAngleTarget() {
1079     // adjust target bank angle if heading lock engaged
1080     if (hdg_lock) {
1081         double bank_sense = 0.0;
1082         double diff = fabs(hdg - tgt_heading);
1083         if (diff > 180)
1084             diff = fabs(diff - 360);
1085
1086         double sum = hdg + diff;
1087         if (sum > 360.0)
1088             sum -= 360.0;
1089         if (fabs(sum - tgt_heading) < 1.0) {
1090             bank_sense = 1.0;    // right turn
1091         } else {
1092             bank_sense = -1.0;   // left turn
1093         }
1094         if (diff < _performance->maximumBankAngle()) {
1095             tgt_roll = diff * bank_sense;
1096         } else {
1097             tgt_roll = _performance->maximumBankAngle() * bank_sense;
1098         }
1099         if ((fabs((double) spinCounter) > 1) && (diff > _performance->maximumBankAngle())) {
1100             tgt_speed *= 0.999;  // Ugly hack: If aircraft get stuck, they will continually spin around.
1101             // The only way to resolve this is to make them slow down.
1102         }
1103     }
1104 }
1105
1106
1107 void FGAIAircraft::updateVerticalSpeedTarget() {
1108     // adjust target Altitude, based on ground elevation when on ground
1109     if (onGround()) {
1110         getGroundElev(dt);
1111         doGroundAltitude();
1112     } else if (alt_lock) {
1113         // find target vertical speed
1114         if (use_perf_vs) {
1115             if (altitude_ft < tgt_altitude_ft) {
1116                 tgt_vs = tgt_altitude_ft - altitude_ft;
1117                 if (tgt_vs > _performance->climbRate())
1118                     tgt_vs = _performance->climbRate();
1119             } else {
1120                 tgt_vs = tgt_altitude_ft - altitude_ft;
1121                 if (tgt_vs  < (-_performance->descentRate()))
1122                     tgt_vs = -_performance->descentRate();
1123             }
1124         } else {
1125             double max_vs = 4*(tgt_altitude_ft - altitude_ft);
1126             double min_vs = 100;
1127             if (tgt_altitude_ft < altitude_ft)
1128                 min_vs = -100.0;
1129             if ((fabs(tgt_altitude_ft - altitude_ft) < 1500.0)
1130                     && (fabs(max_vs) < fabs(tgt_vs)))
1131                 tgt_vs = max_vs;
1132
1133             if (fabs(tgt_vs) < fabs(min_vs))
1134                 tgt_vs = min_vs;
1135         }
1136     } //else 
1137     //    tgt_vs = 0.0;
1138     checkTcas();
1139 }
1140
1141 void FGAIAircraft::updatePitchAngleTarget() {
1142     // if on ground and above vRotate -> initial rotation
1143     if (onGround() && (speed > _performance->vRotate()))
1144         tgt_pitch = 8.0; // some rough B737 value 
1145
1146     //TODO pitch angle on approach and landing
1147     
1148     // match pitch angle to vertical speed
1149     else if (tgt_vs > 0) {
1150         tgt_pitch = tgt_vs * 0.005;
1151     } else {
1152         tgt_pitch = tgt_vs * 0.002;
1153     }
1154 }
1155
1156 string FGAIAircraft::atGate() {
1157      string tmp("");
1158      if (fp->getLeg() < 3) {
1159          if (trafficRef) {
1160              if (fp->getGate() > 0) {
1161                  FGParking *park =
1162                      trafficRef->getDepartureAirport()->getDynamics()->getParking(fp->getGate());
1163                  tmp = park->getName();
1164              }
1165          }
1166      }
1167      return tmp;
1168 }
1169
1170 void FGAIAircraft::handleATCRequests() {
1171     //TODO implement NullController for having no ATC to save the conditionals
1172     if (controller) {
1173         controller->updateAircraftInformation(getID(),
1174                                               pos.getLatitudeDeg(),
1175                                               pos.getLongitudeDeg(),
1176                                               hdg,
1177                                               speed,
1178                                               altitude_ft, dt);
1179         processATC(controller->getInstruction(getID()));
1180     }
1181 }
1182
1183 void FGAIAircraft::updateActualState() {
1184     //update current state
1185     //TODO have a single tgt_speed and check speed limit on ground on setting tgt_speed
1186     updatePosition();
1187
1188     if (onGround())
1189         speed = _performance->actualSpeed(this, groundTargetSpeed, dt);
1190     else
1191         speed = _performance->actualSpeed(this, (tgt_speed *speedFraction), dt);
1192
1193     updateHeading();
1194     roll = _performance->actualBankAngle(this, tgt_roll, dt);
1195
1196     // adjust altitude (meters) based on current vertical speed (fpm)
1197     altitude_ft += vs / 60.0 * dt;
1198     pos.setElevationFt(altitude_ft);
1199
1200     vs = _performance->actualVerticalSpeed(this, tgt_vs, dt);
1201     pitch = _performance->actualPitch(this, tgt_pitch, dt);
1202 }
1203
1204 void FGAIAircraft::updateSecondaryTargetValues() {
1205     // derived target state values
1206     updateBankAngleTarget();
1207     updateVerticalSpeedTarget();
1208     updatePitchAngleTarget();
1209
1210     //TODO calculate wind correction angle (tgt_yaw)
1211 }
1212
1213
1214 bool FGAIAircraft::reachedEndOfCruise(double &distance) {
1215     FGAIWaypoint* curr = fp->getCurrentWaypoint();
1216     if (curr->getName() == string("BOD")) {
1217         double dist = fp->getDistanceToGo(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr);
1218         double descentSpeed = (getPerformance()->vDescent() * SG_NM_TO_METER) / 3600.0;     // convert from kts to meter/s
1219         double descentRate  = (getPerformance()->descentRate() * SG_FEET_TO_METER) / 60.0;  // convert from feet/min to meter/s
1220
1221         double verticalDistance  = ((altitude_ft - 2000.0) - trafficRef->getArrivalAirport()->getElevation()) *SG_FEET_TO_METER;
1222         double descentTimeNeeded = verticalDistance / descentRate;
1223         double distanceCovered   = descentSpeed * descentTimeNeeded; 
1224
1225         //cerr << "Tracking  : " << fgGetString("/ai/track-callsign");
1226 //         if (trafficRef->getCallSign() == fgGetString("/ai/track-callsign")) {
1227 //             cerr << "Checking for end of cruise stage for :" << trafficRef->getCallSign() << endl;
1228 //             cerr << "Descent rate      : " << descentRate << endl;
1229 //             cerr << "Descent speed     : " << descentSpeed << endl;
1230 //             cerr << "VerticalDistance  : " << verticalDistance << ". Altitude : " << altitude_ft << ". Elevation " << trafficRef->getArrivalAirport()->getElevation() << endl;
1231 //             cerr << "DecentTimeNeeded  : " << descentTimeNeeded << endl;
1232 //             cerr << "DistanceCovered   : " << distanceCovered   << endl;
1233 //         }
1234         //cerr << "Distance = " << distance << endl;
1235         distance = distanceCovered;
1236         if (dist < distanceCovered) {
1237 //               if (trafficRef->getCallSign() == fgGetString("/ai/track-callsign")) {
1238 //                    //exit(1);
1239 //               }
1240               return true;
1241         } else {
1242               return false;
1243         }
1244     } else {
1245          return false;
1246     }
1247 }
1248
1249 void FGAIAircraft::resetPositionFromFlightPlan()
1250 {
1251     // the one behind you
1252     FGAIWaypoint* prev = 0;
1253     // the one ahead
1254     FGAIWaypoint* curr = 0;
1255     // the next plus 1
1256     FGAIWaypoint* next = 0;
1257
1258     prev = fp->getPreviousWaypoint();
1259     curr = fp->getCurrentWaypoint();
1260     next = fp->getNextWaypoint();
1261
1262     setLatitude(prev->getLatitude());
1263     setLongitude(prev->getLongitude());
1264     double tgt_heading = fp->getBearing(curr, next);
1265     setHeading(tgt_heading);
1266     setAltitude(prev->getAltitude());
1267     setSpeed(prev->getSpeed());
1268 }
1269
1270 double FGAIAircraft::getBearing(double crse) 
1271 {
1272   double hdgDiff = fabs(hdg-crse);
1273   if (hdgDiff > 180)
1274       hdgDiff = fabs(hdgDiff - 360);
1275   return hdgDiff;
1276 }
1277
1278 time_t FGAIAircraft::checkForArrivalTime(string wptName) {
1279      FGAIWaypoint* curr = 0;
1280      curr = fp->getCurrentWaypoint();
1281
1282      double tracklength = fp->checkTrackLength(wptName);
1283      if (tracklength > 0.1) {
1284           tracklength += fp->getDistanceToGo(pos.getLatitudeDeg(), pos.getLongitudeDeg(), curr);
1285      } else {
1286          return 0;
1287      }
1288      time_t now = time(NULL) + fgGetLong("/sim/time/warp");
1289      time_t arrivalTime = fp->getArrivalTime();
1290      
1291      time_t ete = tracklength / ((speed * SG_NM_TO_METER) / 3600.0); 
1292      time_t secondsToGo = arrivalTime - now;
1293 //      if (trafficRef->getCallSign() == fgGetString("/ai/track-callsign")) {    
1294 //           cerr << "Checking arrival time: ete " << ete << ". Time to go : " << secondsToGo << ". Track length = " << tracklength << endl;
1295 //      }
1296      return (ete - secondsToGo); // Positive when we're too slow...
1297 }