]> git.mxchange.org Git - flightgear.git/blob - src/Autopilot/newauto.cxx
- removed unused reference to bfi.hxx
[flightgear.git] / src / Autopilot / newauto.cxx
1 // newauto.cxx -- autopilot defines and prototypes (very alpha)
2 // 
3 // Started April 1998  Copyright (C) 1998
4 //
5 // Contributions by Jeff Goeke-Smith <jgoeke@voyager.net>
6 //                  Norman Vine <nhv@cape.com>
7 //                  Curtis Olson <curt@flightgear.org>
8 //
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License as
11 // published by the Free Software Foundation; either version 2 of the
12 // License, or (at your option) any later version.
13 //
14 // This program is distributed in the hope that it will be useful, but
15 // WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 // General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 //
23 // $Id$
24
25
26 #ifdef HAVE_CONFIG_H
27 #  include <config.h>
28 #endif
29
30 #include <stdio.h>              // sprintf()
31
32 #include <simgear/constants.h>
33 #include <simgear/debug/logstream.hxx>
34 #include <simgear/math/sg_geodesy.hxx>
35 #include <simgear/math/sg_random.h>
36
37 #include <Cockpit/steam.hxx>
38 #include <Cockpit/radiostack.hxx>
39 #include <Controls/controls.hxx>
40 #include <FDM/flight.hxx>
41 #include <Main/globals.hxx>
42 #include <Scenery/scenery.hxx>
43
44 #include "newauto.hxx"
45
46
47 #define DEFAULT_AP_HEADING_LOCK FGAutopilot::FG_DG_HEADING_LOCK
48
49 FGAutopilot *current_autopilot;
50
51
52 // Climb speed constants
53 const double min_climb = 70.0;  // kts
54 const double best_climb = 75.0; // kts
55 // const double ideal_climb_rate = 500.0 * SG_FEET_TO_METER; // fpm -> mpm
56 // const double ideal_decent_rate = 1000.0 * SG_FEET_TO_METER; // fpm -> mpm
57
58 /// These statics will eventually go into the class
59 /// they are just here while I am experimenting -- NHV :-)
60 // AutoPilot Gain Adjuster members
61 static double MaxRollAdjust;        // MaxRollAdjust       = 2 * APData->MaxRoll;
62 static double RollOutAdjust;        // RollOutAdjust       = 2 * APData->RollOut;
63 static double MaxAileronAdjust;     // MaxAileronAdjust    = 2 * APData->MaxAileron;
64 static double RollOutSmoothAdjust;  // RollOutSmoothAdjust = 2 * APData->RollOutSmooth;
65
66 static char NewTgtAirportId[16];
67 // static char NewTgtAirportLabel[] = "Enter New TgtAirport ID"; 
68
69 extern char *coord_format_lat(float);
70 extern char *coord_format_lon(float);
71                         
72
73 // constructor
74 FGAutopilot::FGAutopilot():
75 TargetClimbRate(1000 * SG_FEET_TO_METER)
76 {
77 }
78
79 // destructor
80 FGAutopilot::~FGAutopilot() {}
81
82
83 void FGAutopilot::MakeTargetLatLonStr( double lat, double lon ) {
84     sprintf( TargetLatitudeStr , "%s", coord_format_lat(get_TargetLatitude()));
85     sprintf( TargetLongitudeStr, "%s", coord_format_lon(get_TargetLongitude()));
86     sprintf( TargetLatLonStr, "%s  %s", TargetLatitudeStr, TargetLongitudeStr );
87 }
88
89
90 void FGAutopilot::MakeTargetAltitudeStr( double altitude ) {
91     if ( altitude_mode == FG_ALTITUDE_TERRAIN ) {
92         sprintf( TargetAltitudeStr, "APAltitude  %6.0f+", altitude );
93     } else {
94         sprintf( TargetAltitudeStr, "APAltitude  %6.0f", altitude );
95     }
96 }
97
98
99 void FGAutopilot::MakeTargetHeadingStr( double bearing ) {
100     if( bearing < 0. ) {
101         bearing += 360.;
102     } else if (bearing > 360. ) {
103         bearing -= 360.;
104     }
105     sprintf( TargetHeadingStr, "APHeading  %6.1f", bearing );
106 }
107
108
109 static inline double get_speed( void ) {
110     return( cur_fdm_state->get_V_equiv_kts() );
111 }
112
113 static inline double get_ground_speed() {
114     // starts in ft/s so we convert to kts
115     double ft_s = cur_fdm_state->get_V_ground_speed() 
116       * fgGetInt("/sim/speed-up"); // FIXME: inefficient
117     double kts = ft_s * SG_FEET_TO_METER * 3600 * SG_METER_TO_NM;
118
119     return kts;
120 }
121
122
123 void FGAutopilot::MakeTargetWPStr( double distance ) {
124     static time_t last_time = 0;
125     time_t current_time = time(NULL);
126     if ( last_time == current_time ) {
127         return;
128     }
129
130     last_time = current_time;
131
132     double accum = 0.0;
133
134     int size = globals->get_route()->size();
135
136     // start by wiping the strings
137     TargetWP1Str[0] = 0;
138     TargetWP2Str[0] = 0;
139     TargetWP3Str[0] = 0;
140
141     // current route
142     if ( size > 0 ) {
143         SGWayPoint wp1 = globals->get_route()->get_waypoint( 0 );
144         accum += distance;
145         double eta = accum * SG_METER_TO_NM / get_ground_speed();
146         if ( eta >= 100.0 ) { eta = 99.999; }
147         int major, minor;
148         if ( eta < (1.0/6.0) ) {
149             // within 10 minutes, bump up to min/secs
150             eta *= 60.0;
151         }
152         major = (int)eta;
153         minor = (int)((eta - (int)eta) * 60.0);
154         sprintf( TargetWP1Str, "%s %.2f NM  ETA %d:%02d",
155                  wp1.get_id().c_str(),
156                  accum*SG_METER_TO_NM, major, minor );
157         // cout << "distance = " << distance*SG_METER_TO_NM
158         //      << "  gndsp = " << get_ground_speed() 
159         //      << "  time = " << eta
160         //      << "  major = " << major
161         //      << "  minor = " << minor
162         //      << endl;
163     }
164
165     // next route
166     if ( size > 1 ) {
167         SGWayPoint wp2 = globals->get_route()->get_waypoint( 1 );
168         accum += wp2.get_distance();
169         
170         double eta = accum * SG_METER_TO_NM / get_ground_speed();
171         if ( eta >= 100.0 ) { eta = 99.999; }
172         int major, minor;
173         if ( eta < (1.0/6.0) ) {
174             // within 10 minutes, bump up to min/secs
175             eta *= 60.0;
176         }
177         major = (int)eta;
178         minor = (int)((eta - (int)eta) * 60.0);
179         sprintf( TargetWP2Str, "%s %.2f NM  ETA %d:%02d",
180                  wp2.get_id().c_str(),
181                  accum*SG_METER_TO_NM, major, minor );
182     }
183
184     // next route
185     if ( size > 2 ) {
186         for ( int i = 2; i < size; ++i ) {
187             accum += globals->get_route()->get_waypoint( i ).get_distance();
188         }
189         
190         SGWayPoint wpn = globals->get_route()->get_waypoint( size - 1 );
191
192         double eta = accum * SG_METER_TO_NM / get_ground_speed();
193         if ( eta >= 100.0 ) { eta = 99.999; }
194         int major, minor;
195         if ( eta < (1.0/6.0) ) {
196             // within 10 minutes, bump up to min/secs
197             eta *= 60.0;
198         }
199         major = (int)eta;
200         minor = (int)((eta - (int)eta) * 60.0);
201         sprintf( TargetWP3Str, "%s %.2f NM  ETA %d:%02d",
202                  wpn.get_id().c_str(),
203                  accum*SG_METER_TO_NM, major, minor );
204     }
205 }
206
207
208 void FGAutopilot::update_old_control_values() {
209     old_aileron = controls.get_aileron();
210     old_elevator = controls.get_elevator();
211     old_elevator_trim = controls.get_elevator_trim();
212     old_rudder = controls.get_rudder();
213 }
214
215
216 // Initialize autopilot subsystem
217 void FGAutopilot::init() {
218     SG_LOG( SG_AUTOPILOT, SG_INFO, "Init AutoPilot Subsystem" );
219
220     heading_hold = false ;      // turn the heading hold off
221     altitude_hold = false ;     // turn the altitude hold off
222     auto_throttle = false ;     // turn the auto throttle off
223     heading_mode = DEFAULT_AP_HEADING_LOCK;
224
225     sg_srandom_time();
226     DGTargetHeading = sg_random() * 360.0;
227
228     // Initialize target location to startup location
229     old_lat = fgGetDouble("/position/latitude");
230     old_lon = fgGetDouble("/position/longitude");
231     // set_WayPoint( old_lon, old_lat, 0.0, "default" );
232
233     MakeTargetLatLonStr( get_TargetLatitude(), get_TargetLongitude() );
234         
235     TargetHeading = 0.0;        // default direction, due north
236     TargetAltitude = 3000;      // default altitude in meters
237     alt_error_accum = 0.0;
238     climb_error_accum = 0.0;
239
240     MakeTargetAltitudeStr( TargetAltitude );
241     MakeTargetHeadingStr( TargetHeading );
242         
243     // These eventually need to be read from current_aircaft somehow.
244
245     // the maximum roll, in Deg
246     MaxRoll = 20;
247
248     // the deg from heading to start rolling out at, in Deg
249     RollOut = 20;
250
251     // how far can I move the aleron from center.
252     MaxAileron = .2;
253
254     // Smoothing distance for alerion control
255     RollOutSmooth = 10;
256
257     // Hardwired for now should be in options
258     // 25% max control variablilty  0.5 / 2.0
259     disengage_threshold = 1.0;
260
261 #if !defined( USING_SLIDER_CLASS )
262     MaxRollAdjust = 2 * MaxRoll;
263     RollOutAdjust = 2 * RollOut;
264     MaxAileronAdjust = 2 * MaxAileron;
265     RollOutSmoothAdjust = 2 * RollOutSmooth;
266 #endif  // !defined( USING_SLIDER_CLASS )
267
268     update_old_control_values();
269         
270     // Initialize GUI components of autopilot
271     // NewTgtAirportInit();
272     // fgAPAdjustInit() ;
273     // NewHeadingInit();
274     // NewAltitudeInit();
275 };
276
277
278 // Reset the autopilot system
279 void FGAutopilot::reset() {
280
281     heading_hold = false ;      // turn the heading hold off
282     altitude_hold = false ;     // turn the altitude hold off
283     auto_throttle = false ;     // turn the auto throttle off
284     heading_mode = DEFAULT_AP_HEADING_LOCK;
285
286     // TargetHeading = 0.0;     // default direction, due north
287     MakeTargetHeadingStr( TargetHeading );                      
288         
289     // TargetAltitude = 3000;   // default altitude in meters
290     MakeTargetAltitudeStr( TargetAltitude );
291         
292     alt_error_accum = 0.0;
293     climb_error_accum = 0.0;
294         
295     update_old_control_values();
296
297     sprintf( NewTgtAirportId, "%s", fgGetString("/sim/startup/airport-id").c_str() );
298         
299     MakeTargetLatLonStr( get_TargetLatitude(), get_TargetLongitude() );
300 }
301
302
303 static double NormalizeDegrees( double Input ) {
304     // normalize the input to the range (-180,180]
305     // Input should not be greater than -360 to 360.
306     // Current rules send the output to an undefined state.
307     if ( Input > 180 )
308         while(Input > 180 )
309             Input -= 360;
310     else if ( Input <= -180 )
311         while ( Input <= -180 )
312             Input += 360;
313     return ( Input );
314 };
315
316 static double LinearExtrapolate( double x, double x1, double y1, double x2, double y2 ) {
317     // This procedure extrapolates the y value for the x posistion on a line defined by x1,y1; x2,y2
318     //assert(x1 != x2); // Divide by zero error.  Cold abort for now
319
320         // Could be
321         // static double y = 0.0;
322         // double dx = x2 -x1;
323         // if( (dx < -SG_EPSILON ) || ( dx > SG_EPSILON ) )
324         // {
325
326     double m, b, y;          // the constants to find in y=mx+b
327     // double m, b;
328
329     m = ( y2 - y1 ) / ( x2 - x1 );   // calculate the m
330
331     b = y1 - m * x1;       // calculate the b
332
333     y = m * x + b;       // the final calculation
334
335     // }
336
337     return ( y );
338
339 };
340
341
342 int FGAutopilot::run() {
343     // Remove the following lines when the calling funcitons start
344     // passing in the data pointer
345
346     // get control settings 
347         
348     double lat = fgGetDouble("/position/latitude");
349     double lon = fgGetDouble("/position/longitude");
350     double alt = fgGetDouble("/position/altitude") * SG_FEET_TO_METER;
351
352 #ifdef FG_FORCE_AUTO_DISENGAGE
353     // see if somebody else has changed them
354     if( fabs(aileron - old_aileron) > disengage_threshold ||
355         fabs(elevator - old_elevator) > disengage_threshold ||
356         fabs(elevator_trim - old_elevator_trim) > 
357         disengage_threshold ||          
358         fabs(rudder - old_rudder) > disengage_threshold )
359     {
360         // if controls changed externally turn autopilot off
361         waypoint_hold = false ;   // turn the target hold off
362         heading_hold = false ;    // turn the heading hold off
363         altitude_hold = false ;   // turn the altitude hold off
364         terrain_follow = false;   // turn the terrain_follow hold off
365         // auto_throttle = false; // turn the auto_throttle off
366
367         // stash this runs control settings
368         old_aileron = aileron;
369         old_elevator = elevator;
370         old_elevator_trim = elevator_trim;
371         old_rudder = rudder;
372         
373         return 0;
374     }
375 #endif
376         
377     // heading hold
378     if ( heading_hold == true ) {
379         if ( heading_mode == FG_DG_HEADING_LOCK ) {
380             // cout << "DG heading = " << FGSteam::get_DG_deg()
381             //      << " DG error = " << FGSteam::get_DG_err() << endl;
382
383             TargetHeading = DGTargetHeading + FGSteam::get_DG_err();
384             while ( TargetHeading <   0.0 ) { TargetHeading += 360.0; }
385             while ( TargetHeading > 360.0 ) { TargetHeading -= 360.0; }
386             MakeTargetHeadingStr( TargetHeading );
387         } else if ( heading_mode == FG_TC_HEADING_LOCK ) {
388             // we don't set a specific target heading in
389             // TC_HEADING_LOCK mode, we instead try to keep the turn
390             // coordinator zero'd
391         } else if ( heading_mode == FG_TRUE_HEADING_LOCK ) {
392             // leave "true" target heading as is
393             while ( TargetHeading <   0.0 ) { TargetHeading += 360.0; }
394             while ( TargetHeading > 360.0 ) { TargetHeading -= 360.0; }
395             MakeTargetHeadingStr( TargetHeading );
396         } else if ( heading_mode == FG_HEADING_NAV1 ) {
397             // track the NAV1 heading needle deflection
398
399             // determine our current radial position relative to the
400             // navaid in "true" heading.
401             double cur_radial = current_radiostack->get_nav1_heading();
402             if ( current_radiostack->get_nav1_loc() ) {
403                 // ILS localizers radials are already "true" in our
404                 // database
405             } else {
406                 cur_radial += current_radiostack->get_nav1_magvar();
407             }
408             if ( current_radiostack->get_nav1_from_flag() ) {
409                 cur_radial += 180.0;
410                 while ( cur_radial >= 360.0 ) { cur_radial -= 360.0; }
411             }
412
413             // determine the target radial in "true" heading
414             double tgt_radial = current_radiostack->get_nav1_radial();
415             if ( current_radiostack->get_nav1_loc() ) {
416                 // ILS localizers radials are already "true" in our
417                 // database
418             } else {
419                 // VOR radials need to have that vor's offset added in
420                 tgt_radial += current_radiostack->get_nav1_magvar();
421             }
422
423             // determine the heading adjustment needed.
424             double adjustment = 
425                 current_radiostack->get_nav1_heading_needle_deflection()
426                 * (current_radiostack->get_nav1_loc_dist() * SG_METER_TO_NM);
427             if ( adjustment < -30.0 ) { adjustment = -30.0; }
428             if ( adjustment >  30.0 ) { adjustment =  30.0; }
429
430             // determine the target heading to fly to intercept the
431             // tgt_radial
432             TargetHeading = tgt_radial + adjustment; 
433             while ( TargetHeading <   0.0 ) { TargetHeading += 360.0; }
434             while ( TargetHeading > 360.0 ) { TargetHeading -= 360.0; }
435
436             MakeTargetHeadingStr( TargetHeading );
437             // cout << "target course (true) = " << TargetHeading << endl;
438         } else if ( heading_mode == FG_HEADING_WAYPOINT ) {
439             // update target heading to waypoint
440
441             double wp_course, wp_distance;
442
443 #ifdef DO_fgAP_CORRECTED_COURSE
444             // compute course made good
445             // this needs lots of special casing before use
446             double course, reverse, distance, corrected_course;
447             // need to test for iter
448             geo_inverse_wgs_84( 0, //fgAPget_altitude(),
449                                 old_lat,
450                                 old_lon,
451                                 lat,
452                                 lon,
453                                 &course,
454                                 &reverse,
455                                 &distance );
456 #endif // DO_fgAP_CORRECTED_COURSE
457
458             // compute course to way_point
459             // need to test for iter
460             SGWayPoint wp = globals->get_route()->get_first();
461             wp.CourseAndDistance( lon, lat, alt, 
462                                   &wp_course, &wp_distance );
463
464 #ifdef DO_fgAP_CORRECTED_COURSE
465             corrected_course = course - wp_course;
466             if( fabs(corrected_course) > 0.1 )
467                 printf("fgAP: course %f  wp_course %f  %f  %f\n",
468                        course, wp_course, fabs(corrected_course),
469                        distance );
470 #endif // DO_fgAP_CORRECTED_COURSE
471                 
472             if ( wp_distance > 100 ) {
473                 // corrected_course = course - wp_course;
474                 TargetHeading = NormalizeDegrees(wp_course);
475             } else {
476                 cout << "Reached waypoint within " << wp_distance << "meters"
477                      << endl;
478
479                 // pop off this waypoint from the list
480                 if ( globals->get_route()->size() ) {
481                     globals->get_route()->delete_first();
482                 }
483
484                 // see if there are more waypoints on the list
485                 if ( globals->get_route()->size() ) {
486                     // more waypoints
487                     set_HeadingMode( FG_HEADING_WAYPOINT );
488                 } else {
489                     // end of the line
490                     heading_mode = FG_TRUE_HEADING_LOCK;
491                     // use current heading
492                     TargetHeading = fgGetDouble("/orientation/heading");
493                 }
494             }
495             MakeTargetHeadingStr( TargetHeading );
496             // Force this just in case
497             TargetDistance = wp_distance;
498             MakeTargetWPStr( wp_distance );
499         }
500
501         if ( heading_mode == FG_TC_HEADING_LOCK ) {
502             // drive the turn coordinator to zero
503             double turn = FGSteam::get_TC_std();
504             // cout << "turn rate = " << turn << endl;
505             double AileronSet = -turn / 2.0;
506             if ( AileronSet < -1.0 ) { AileronSet = -1.0; }
507             if ( AileronSet >  1.0 ) { AileronSet =  1.0; }
508             controls.set_aileron( AileronSet );
509             controls.set_rudder( AileronSet / 4.0 );
510         } else {
511             // steer towards the target heading
512
513             double RelHeading;
514             double TargetRoll;
515             double RelRoll;
516             double AileronSet;
517
518             RelHeading
519                 = NormalizeDegrees( TargetHeading
520                                     - fgGetDouble("/orientation/heading") );
521             // figure out how far off we are from desired heading
522
523             // Now it is time to deterime how far we should be rolled.
524             SG_LOG( SG_AUTOPILOT, SG_DEBUG, "RelHeading: " << RelHeading );
525
526
527             // Check if we are further from heading than the roll out point
528             if ( fabs( RelHeading ) > RollOut ) {
529                 // set Target Roll to Max in desired direction
530                 if ( RelHeading < 0 ) {
531                     TargetRoll = 0 - MaxRoll;
532                 } else {
533                     TargetRoll = MaxRoll;
534                 }
535             } else {
536                 // We have to calculate the Target roll
537
538                 // This calculation engine thinks that the Target roll
539                 // should be a line from (RollOut,MaxRoll) to (-RollOut,
540                 // -MaxRoll) I hope this works well.  If I get ambitious
541                 // some day this might become a fancier curve or
542                 // something.
543
544                 TargetRoll = LinearExtrapolate( RelHeading, -RollOut,
545                                                 -MaxRoll, RollOut,
546                                                 MaxRoll );
547             }
548
549             // Target Roll has now been Found.
550
551             // Compare Target roll to Current Roll, Generate Rel Roll
552
553             SG_LOG( SG_COCKPIT, SG_BULK, "TargetRoll: " << TargetRoll );
554
555             RelRoll = NormalizeDegrees( TargetRoll 
556                                         - fgGetDouble("/orientation/roll") );
557
558             // Check if we are further from heading than the roll out
559             // smooth point
560             if ( fabs( RelRoll ) > RollOutSmooth ) {
561                 // set Target Roll to Max in desired direction
562                 if ( RelRoll < 0 ) {
563                 AileronSet = 0 - MaxAileron;
564                 } else {
565                     AileronSet = MaxAileron;
566                 }
567             } else {
568                 AileronSet = LinearExtrapolate( RelRoll, -RollOutSmooth,
569                                                 -MaxAileron,
570                                                 RollOutSmooth,
571                                                 MaxAileron );
572             }
573
574             controls.set_aileron( AileronSet );
575             controls.set_rudder( AileronSet / 4.0 );
576             // controls.set_rudder( 0.0 );
577         }
578     }
579
580     // altitude hold
581     if ( altitude_hold ) {
582         double climb_rate;
583         double speed, max_climb, error;
584         double prop_error, int_error;
585         double prop_adj, int_adj, total_adj;
586
587         if ( altitude_mode == FG_ALTITUDE_LOCK ) {
588             climb_rate =
589                 ( TargetAltitude - FGSteam::get_ALT_ft() * SG_FEET_TO_METER ) * 8.0;
590         } else if ( altitude_mode == FG_ALTITUDE_GS1 ) {
591             double x = current_radiostack->get_nav1_gs_dist();
592             double y = (fgGetDouble("/position/altitude")
593                         - current_radiostack->get_nav1_elev()) * SG_FEET_TO_METER;
594             double current_angle = atan2( y, x ) * SGD_RADIANS_TO_DEGREES;
595             // cout << "current angle = " << current_angle << endl;
596
597             double target_angle = current_radiostack->get_nav1_target_gs();
598             // cout << "target angle = " << target_angle << endl;
599
600             double gs_diff = target_angle - current_angle;
601             // cout << "difference from desired = " << gs_diff << endl;
602
603             // convert desired vertical path angle into a climb rate
604             double des_angle = current_angle - 10 * gs_diff;
605             // cout << "desired angle = " << des_angle << endl;
606
607             // convert to meter/min
608             // cout << "raw ground speed = " << cur_fdm_state->get_V_ground_speed() << endl;
609             double horiz_vel = cur_fdm_state->get_V_ground_speed()
610                 * SG_FEET_TO_METER * 60.0;
611             // cout << "Horizontal vel = " << horiz_vel << endl;
612             climb_rate = -sin( des_angle * SGD_DEGREES_TO_RADIANS ) * horiz_vel;
613             // cout << "climb_rate = " << climb_rate << endl;
614             /* climb_error_accum += gs_diff * 2.0; */
615             /* climb_rate = gs_diff * 200.0 + climb_error_accum; */
616         } else if ( altitude_mode == FG_ALTITUDE_TERRAIN ) {
617             // brain dead ground hugging with no look ahead
618             climb_rate =
619                 ( TargetAGL - fgGetDouble("/position/altitude-agl")
620                   * SG_FEET_TO_METER ) * 16.0;
621             // cout << "target agl = " << TargetAGL 
622             //      << "  current agl = " << fgAPget_agl() 
623             //      << "  target climb rate = " << climb_rate 
624             //      << endl;
625         } else {
626             // just try to zero out rate of climb ...
627             climb_rate = 0.0;
628         }
629
630         speed = get_speed();
631
632         if ( speed < min_climb ) {
633             max_climb = 0.0;
634         } else if ( speed < best_climb ) {
635             max_climb = ((best_climb - min_climb) - (best_climb - speed)) 
636                 * fabs(TargetClimbRate) 
637                 / (best_climb - min_climb);
638         } else {                        
639             max_climb = ( speed - best_climb ) * 10.0 + fabs(TargetClimbRate);
640         }
641
642         // this first one could be optional if we wanted to allow
643         // better climb performance assuming we have the airspeed to
644         // support it.
645         if ( climb_rate > fabs(TargetClimbRate) ) {
646             climb_rate = fabs(TargetClimbRate);
647         }
648
649         if ( climb_rate > max_climb ) {
650             climb_rate = max_climb;
651         }
652
653         if ( climb_rate < -fabs(TargetClimbRate) ) {
654             climb_rate = -fabs(TargetClimbRate);
655         }
656         // cout << "Target climb rate = " << TargetClimbRate << endl;
657         // cout << "given our speed, modified desired climb rate = "
658         //      << climb_rate * SG_METER_TO_FEET 
659         //      << " fpm" << endl;
660
661         error = fgGetDouble("/velocities/vertical-speed")
662             * SG_FEET_TO_METER - climb_rate;
663
664         // accumulate the error under the curve ... this really should
665         // be *= delta t
666         alt_error_accum += error;
667
668         // calculate integral error, and adjustment amount
669         int_error = alt_error_accum;
670         // printf("error = %.2f  int_error = %.2f\n", error, int_error);
671         int_adj = int_error / 20000.0;
672
673         // caclulate proportional error
674         prop_error = error;
675         prop_adj = prop_error / 2000.0;
676
677         total_adj = 0.9 * prop_adj + 0.1 * int_adj;
678         // if ( total_adj > 0.6 ) {
679         //     total_adj = 0.6;
680         // } else if ( total_adj < -0.2 ) {
681         //     total_adj = -0.2;
682         // }
683         if ( total_adj > 1.0 ) {
684             total_adj = 1.0;
685         } else if ( total_adj < -1.0 ) {
686             total_adj = -1.0;
687         }
688
689         controls.set_elevator( total_adj );
690     }
691
692     // auto throttle
693     if ( auto_throttle ) {
694         double error;
695         double prop_error, int_error;
696         double prop_adj, int_adj, total_adj;
697
698         error = TargetSpeed - get_speed();
699
700         // accumulate the error under the curve ... this really should
701         // be *= delta t
702         speed_error_accum += error;
703         if ( speed_error_accum > 2000.0 ) {
704             speed_error_accum = 2000.0;
705         }
706         else if ( speed_error_accum < -2000.0 ) {
707             speed_error_accum = -2000.0;
708         }
709
710         // calculate integral error, and adjustment amount
711         int_error = speed_error_accum;
712
713         // printf("error = %.2f  int_error = %.2f\n", error, int_error);
714         int_adj = int_error / 200.0;
715
716         // caclulate proportional error
717         prop_error = error;
718         prop_adj = 0.5 + prop_error / 50.0;
719
720         total_adj = 0.9 * prop_adj + 0.1 * int_adj;
721         if ( total_adj > 1.0 ) {
722             total_adj = 1.0;
723         }
724         else if ( total_adj < 0.0 ) {
725             total_adj = 0.0;
726         }
727
728         controls.set_throttle( FGControls::ALL_ENGINES, total_adj );
729     }
730
731 #ifdef THIS_CODE_IS_NOT_USED
732     if (Mode == 2) // Glide slope hold
733         {
734             double RelSlope;
735             double RelElevator;
736
737             // First, calculate Relative slope and normalize it
738             RelSlope = NormalizeDegrees( TargetSlope - get_pitch());
739
740             // Now calculate the elevator offset from current angle
741             if ( abs(RelSlope) > SlopeSmooth )
742                 {
743                     if ( RelSlope < 0 )     //  set RelElevator to max in the correct direction
744                         RelElevator = -MaxElevator;
745                     else
746                         RelElevator = MaxElevator;
747                 }
748
749             else
750                 RelElevator = LinearExtrapolate(RelSlope,-SlopeSmooth,-MaxElevator,SlopeSmooth,MaxElevator);
751
752             // set the elevator
753             fgElevMove(RelElevator);
754
755         }
756 #endif // THIS_CODE_IS_NOT_USED
757
758     // stash this runs control settings
759     //  update_old_control_values();
760     old_aileron = controls.get_aileron();
761     old_elevator = controls.get_elevator();
762     old_elevator_trim = controls.get_elevator_trim();
763     old_rudder = controls.get_rudder();
764
765     // for cross track error
766     old_lat = lat;
767     old_lon = lon;
768         
769         // Ok, we are done
770     return 0;
771 }
772
773
774 void FGAutopilot::set_HeadingMode( fgAutoHeadingMode mode ) {
775     heading_mode = mode;
776
777     if ( heading_mode == FG_DG_HEADING_LOCK ) {
778         // set heading hold to current heading (as read from DG)
779         // ... no, leave target heading along ... just use the current
780         // heading bug value
781         //  DGTargetHeading = FGSteam::get_DG_deg();
782     } else if ( heading_mode == FG_TC_HEADING_LOCK ) {
783         // set autopilot to hold a zero turn (as reported by the TC)
784     } else if ( heading_mode == FG_TRUE_HEADING_LOCK ) {
785         // set heading hold to current heading
786         TargetHeading = fgGetDouble("/orientation/heading");
787     } else if ( heading_mode == FG_HEADING_WAYPOINT ) {
788         if ( globals->get_route()->size() ) {
789             double course, distance;
790
791             old_lat = fgGetDouble("/position/latitude");
792             old_lon = fgGetDouble("/position/longitude");
793
794             waypoint = globals->get_route()->get_first();
795             waypoint.CourseAndDistance( fgGetDouble("/position/longitude"),
796                                         fgGetDouble("/position/latitude"),
797                                         fgGetDouble("/position/latitude")
798                                         * SG_FEET_TO_METER,
799                                         &course, &distance );
800             TargetHeading = course;
801             TargetDistance = distance;
802             MakeTargetLatLonStr( waypoint.get_target_lat(),
803                                  waypoint.get_target_lon() );
804             MakeTargetWPStr( distance );
805
806             if ( waypoint.get_target_alt() > 0.0 ) {
807                 TargetAltitude = waypoint.get_target_alt();
808                 altitude_mode = FG_ALTITUDE_LOCK;
809                 set_AltitudeEnabled( true );
810                 MakeTargetAltitudeStr( TargetAltitude * SG_METER_TO_FEET );
811             }
812
813             SG_LOG( SG_COCKPIT, SG_INFO, " set_HeadingMode: ( "
814                     << get_TargetLatitude()  << " "
815                     << get_TargetLongitude() << " ) "
816                     );
817         } else {
818             // no more way points, default to heading lock.
819             heading_mode = FG_TC_HEADING_LOCK;
820         }
821     }
822
823     MakeTargetHeadingStr( TargetHeading );                      
824     update_old_control_values();
825 }
826
827
828 void FGAutopilot::set_AltitudeMode( fgAutoAltitudeMode mode ) {
829     altitude_mode = mode;
830
831     alt_error_accum = 0.0;
832
833     if ( altitude_mode == FG_ALTITUDE_LOCK ) {
834         if ( TargetAltitude < fgGetDouble("/position/altitude-agl")
835              * SG_FEET_TO_METER ) {
836         }
837
838         if ( fgGetString("/sim/startup/units") == "feet" ) {
839             MakeTargetAltitudeStr( TargetAltitude * SG_METER_TO_FEET );
840         } else {
841             MakeTargetAltitudeStr( TargetAltitude * SG_METER_TO_FEET );
842         }
843     } else if ( altitude_mode == FG_ALTITUDE_GS1 ) {
844         climb_error_accum = 0.0;
845
846     } else if ( altitude_mode == FG_ALTITUDE_TERRAIN ) {
847         TargetAGL = fgGetDouble("/position/altitude-agl") * SG_FEET_TO_METER;
848
849         if ( fgGetString("/sim/startup/units") == "feet" ) {
850             MakeTargetAltitudeStr( TargetAGL * SG_METER_TO_FEET );
851         } else {
852             MakeTargetAltitudeStr( TargetAGL * SG_METER_TO_FEET );
853         }
854     }
855     
856     update_old_control_values();
857     SG_LOG( SG_COCKPIT, SG_INFO, " set_AltitudeMode():" );
858 }
859
860
861 #if 0
862 static inline double get_aoa( void ) {
863     return( cur_fdm_state->get_Gamma_vert_rad() * SGD_RADIANS_TO_DEGREES );
864 }
865
866 static inline double fgAPget_latitude( void ) {
867     return( cur_fdm_state->get_Latitude() * SGD_RADIANS_TO_DEGREES );
868 }
869
870 static inline double fgAPget_longitude( void ) {
871     return( cur_fdm_state->get_Longitude() * SGD_RADIANS_TO_DEGREES );
872 }
873
874 static inline double fgAPget_roll( void ) {
875     return( cur_fdm_state->get_Phi() * SGD_RADIANS_TO_DEGREES );
876 }
877
878 static inline double get_pitch( void ) {
879     return( cur_fdm_state->get_Theta() );
880 }
881
882 double fgAPget_heading( void ) {
883     return( cur_fdm_state->get_Psi() * SGD_RADIANS_TO_DEGREES );
884 }
885
886 static inline double fgAPget_altitude( void ) {
887     return( cur_fdm_state->get_Altitude() * SG_FEET_TO_METER );
888 }
889
890 static inline double fgAPget_climb( void ) {
891     // return in meters per minute
892     return( cur_fdm_state->get_Climb_Rate() * SG_FEET_TO_METER * 60 );
893 }
894
895 static inline double get_sideslip( void ) {
896     return( cur_fdm_state->get_Beta() );
897 }
898
899 static inline double fgAPget_agl( void ) {
900     double agl;
901
902     agl = cur_fdm_state->get_Altitude() * SG_FEET_TO_METER
903         - scenery.cur_elev;
904
905     return( agl );
906 }
907 #endif
908
909
910 void FGAutopilot::AltitudeSet( double new_altitude ) {
911     double target_alt = new_altitude;
912
913     // cout << "new altitude = " << new_altitude << endl;
914
915     if ( fgGetString("/sim/startup/units") == "feet" ) {
916         target_alt = new_altitude * SG_FEET_TO_METER;
917     }
918
919     if( target_alt < scenery.cur_elev ) {
920         target_alt = scenery.cur_elev;
921     }
922
923     TargetAltitude = target_alt;
924     altitude_mode = FG_ALTITUDE_LOCK;
925
926     // cout << "TargetAltitude = " << TargetAltitude << endl;
927
928     if ( fgGetString("/sim/startup/units") == "feet" ) {
929         target_alt *= SG_METER_TO_FEET;
930     }
931     // ApAltitudeDialogInput->setValue((float)target_alt);
932     MakeTargetAltitudeStr( target_alt );
933         
934     update_old_control_values();
935 }
936
937
938 void FGAutopilot::AltitudeAdjust( double inc )
939 {
940     double target_alt, target_agl;
941
942     if ( fgGetString("/sim/startup/units") == "feet" ) {
943         target_alt = TargetAltitude * SG_METER_TO_FEET;
944         target_agl = TargetAGL * SG_METER_TO_FEET;
945     } else {
946         target_alt = TargetAltitude;
947         target_agl = TargetAGL;
948     }
949
950     // cout << "target_agl = " << target_agl << endl;
951     // cout << "target_agl / inc = " << target_agl / inc << endl;
952     // cout << "(int)(target_agl / inc) = " << (int)(target_agl / inc) << endl;
953
954     if ( fabs((int)(target_alt / inc) * inc - target_alt) < SG_EPSILON ) {
955         target_alt += inc;
956     } else {
957         target_alt = ( int ) ( target_alt / inc ) * inc + inc;
958     }
959
960     if ( fabs((int)(target_agl / inc) * inc - target_agl) < SG_EPSILON ) {
961         target_agl += inc;
962     } else {
963         target_agl = ( int ) ( target_agl / inc ) * inc + inc;
964     }
965
966     if ( fgGetString("/sim/startup/units") == "feet" ) {
967         target_alt *= SG_FEET_TO_METER;
968         target_agl *= SG_FEET_TO_METER;
969     }
970
971     TargetAltitude = target_alt;
972     TargetAGL = target_agl;
973         
974     if ( fgGetString("/sim/startup/units") == "feet" )
975         target_alt *= SG_METER_TO_FEET;
976     if ( fgGetString("/sim/startup/units") == "feet" )
977         target_agl *= SG_METER_TO_FEET;
978
979     if ( altitude_mode == FG_ALTITUDE_LOCK ) {
980         MakeTargetAltitudeStr( target_alt );
981     } else if ( altitude_mode == FG_ALTITUDE_TERRAIN ) {
982         MakeTargetAltitudeStr( target_agl );
983     }
984
985     update_old_control_values();
986 }
987
988
989 void FGAutopilot::HeadingAdjust( double inc ) {
990     if ( heading_mode != FG_DG_HEADING_LOCK
991          && heading_mode != FG_TRUE_HEADING_LOCK )
992     {
993         heading_mode = FG_DG_HEADING_LOCK;
994     }
995
996     if ( heading_mode == FG_DG_HEADING_LOCK ) {
997         double target = ( int ) ( DGTargetHeading / inc ) * inc + inc;
998         DGTargetHeading = NormalizeDegrees( target );
999     } else {
1000         double target = ( int ) ( TargetHeading / inc ) * inc + inc;
1001         TargetHeading = NormalizeDegrees( target );
1002     }
1003
1004     update_old_control_values();
1005 }
1006
1007
1008 void FGAutopilot::HeadingSet( double new_heading ) {
1009     if( heading_mode == FG_TRUE_HEADING_LOCK ) {
1010         new_heading = NormalizeDegrees( new_heading );
1011         TargetHeading = new_heading;
1012         MakeTargetHeadingStr( TargetHeading );
1013     } else {
1014         heading_mode = FG_DG_HEADING_LOCK;
1015
1016         new_heading = NormalizeDegrees( new_heading );
1017         DGTargetHeading = new_heading;
1018         // following cast needed ambiguous plib
1019         // ApHeadingDialogInput -> setValue ((float)APData->TargetHeading );
1020         MakeTargetHeadingStr( DGTargetHeading );
1021     }
1022     update_old_control_values();
1023 }
1024
1025 void FGAutopilot::AutoThrottleAdjust( double inc ) {
1026     double target = ( int ) ( TargetSpeed / inc ) * inc + inc;
1027
1028     TargetSpeed = target;
1029 }
1030
1031
1032 void FGAutopilot::set_AutoThrottleEnabled( bool value ) {
1033     auto_throttle = value;
1034
1035     if ( auto_throttle == true ) {
1036         TargetSpeed = fgGetDouble("/velocities/airspeed");
1037         speed_error_accum = 0.0;
1038     }
1039
1040     update_old_control_values();
1041     SG_LOG( SG_COCKPIT, SG_INFO, " fgAPSetAutoThrottle: ("
1042             << auto_throttle << ") " << TargetSpeed );
1043 }