1 // FGAIThermal - FGAIBase-derived class creates an AI thermal
3 // Copyright (C) 2004 David P. Culp - davidculp2@comcast.net
5 // An attempt to refine the thermal shape and behaviour by WooT 2009
7 // Copyright (C) 2009 Patrice Poly ( WooT )
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27 #include <Main/fg_props.hxx>
28 #include <Main/globals.hxx>
29 #include <Scenery/scenery.hxx>
35 #include "AIThermal.hxx"
38 FGAIThermal::FGAIThermal() :
39 FGAIBase(otThermal, false)
43 strength = factor = 0.0;
44 cycle_timer = 60*(rand()%31); // some random in the birth time
50 FGAIThermal::~FGAIThermal() {
53 void FGAIThermal::readFromScenario(SGPropertyNode* scFileNode) {
57 FGAIBase::readFromScenario(scFileNode);
59 setMaxStrength(scFileNode->getDoubleValue("strength-fps", 8.0));
60 setDiameter(scFileNode->getDoubleValue("diameter-ft", 0.0)/6076.11549);
61 setHeight(scFileNode->getDoubleValue("height-msl", 5000.0));
64 bool FGAIThermal::init(bool search_in_AI_path) {
65 factor = 8.0 * max_strength / (diameter * diameter * diameter);
66 setAltitude( height );
67 _surface_wind_from_deg_node =
68 fgGetNode("/environment/config/boundary/entry[0]/wind-from-heading-deg", true);
69 _surface_wind_speed_node =
70 fgGetNode("/environment/config/boundary/entry[0]/wind-speed-kt", true);
71 _aloft_wind_from_deg_node =
72 fgGetNode("/environment/config/aloft/entry[2]/wind-from-heading-deg", true);
73 _aloft_wind_speed_node =
74 fgGetNode("/environment/config/aloft/entry[2]/wind-speed-kt", true);
76 return FGAIBase::init(search_in_AI_path);
79 void FGAIThermal::bind() {
81 tie("position/altitude-agl-ft", // for debug and tweak
82 SGRawValuePointer<double>(&altitude_agl_ft));
83 tie("alt-rel", // for debug and tweak
84 SGRawValuePointer<double>(&alt_rel));
85 tie("time", // for debug and tweak
86 SGRawValuePointer<double>(&time));
87 tie("xx", // for debug and tweak
88 SGRawValuePointer<double>(&xx));
89 tie("is-forming", // for debug abd tweak
90 SGRawValuePointer<bool>(&is_forming));
91 tie("is-formed", // for debug abd tweak
92 SGRawValuePointer<bool>(&is_formed));
93 tie("is-dying", // for debug abd tweak
94 SGRawValuePointer<bool>(&is_dying));
95 tie("is-dead", // for debug abd tweak
96 SGRawValuePointer<bool>(&is_dead));
99 void FGAIThermal::update(double dt) {
100 FGAIBase::update(dt);
107 //the formula to get the available portion of VUpMax depending on altitude
108 //returns a double between 0 and 1
109 double FGAIThermal::get_strength_fac(double alt_frac) {
111 double PI = 4.0 * atan(1.0);
113 if ( alt_frac <=0.0 ) { // do submarines get thermals ?
116 else if ( ( alt_frac>0.0 ) && (alt_frac<=0.1) ) { // ground layer
117 fac = ( 0.1*( pow( (10.0*alt_frac),10.0) ) );
119 else if ( ( alt_frac>0.1 ) && (alt_frac<=1.0) ) { // main body of the thermal
120 fac = 0.4175 - 0.5825* ( cos ( PI* (1.0-sqrt(alt_frac) ) +PI) ) ;
122 else if ( ( alt_frac >1.0 ) && (alt_frac < 1.1 ) ) { //above the ceiling, but not above the cloud
123 fac = (0.5 * ( 1.0 + cos ( PI*( (-2.0*alt_frac)*5.0 ) ) ) );
125 else if ( alt_frac >= 1.1 ) { //above the cloud
132 void FGAIThermal::Run(double dt) {
134 // *****************************************
135 // the thermal characteristics and variables
136 // *****************************************
142 // the time needed for the thermal to be completely formed
144 // the time when the thermal begins to die
145 double tmin2 = 20.0 ;
146 // the time when the thermal is completely dead
148 double alive_cycle_time = tmin3*60.0;
149 //the time of the complete cycle, including a period of inactivity
151 // some times expressed in a fraction of tmin3;
152 double t1 = tmin1/tmin3 ;
153 double t2 = tmin2/tmin3 ;
155 double t4 = tmin4/tmin3;
156 // the time elapsed since the thermal was born, in a 0-1 fraction of tmin3
158 time = cycle_timer/alive_cycle_time;
160 //uncomment below to freeze the time cycle
164 cycle_timer = 60*(rand()%31);
168 //the position of the thermal 'top'
169 double thermal_foot_lat = (pos.getLatitudeDeg());
170 double thermal_foot_lon = (pos.getLongitudeDeg());
172 //the max updraft one can expect in this thermal
173 double MaxUpdraft=max_strength;
174 //the max sink one can expect in this thermal, this is a negative number
175 double MinUpdraft=-max_strength*0.25;
176 //the fraction of MaxUpdraft one can expect at our height and time
177 double maxstrengthavail;
178 //max updraft at the user altitude and time
180 //min updraft at the user altitude and time, this is a negative number
185 //max radius of the the thermal, including the sink area
186 double Rmax = diameter/2.0;
187 // 'shaping' of the thermal, the higher, the more conical the thermal- between 0 and 1
189 //the radius of the thermal at our altitude in FT, including sink
191 //the relative radius of the thermal where we have updraft, between 0 an 1
192 double r_up_frac=0.9;
193 //radius of the thermal where we have updraft, in FT
195 //how far are we from the thermal center at our altitude in FEET
198 //the position of the center of the thermal slice at our altitude
199 double slice_center_lon;
200 double slice_center_lat;
204 //we need to know the thermal foot AGL altitude
207 //we could do this only once, as thermal don't move
208 //but then agl info is lost on user reset
209 //so we only do this every 10 seconds to save cpu
211 if (dt_count >= 10.0 ) {
213 if (getGroundElevationM(SGGeod::fromGeodM(pos, 20000), alt, 0)) {
214 ground_elev_ft = alt * SG_METER_TO_FEET;
216 altitude_agl_ft = height - ground_elev_ft ;
221 //user altitude relative to the thermal height, seen AGL from the thermal foot
224 double user_altitude = globals->get_aircraft_position().getElevationFt();
225 if ( user_altitude < 1.0 ) { user_altitude = 1.0 ;}; // an ugly way to avoid NaNs for users at alt 0
226 double user_altitude_agl= ( user_altitude - ground_elev_ft ) ;
227 alt_rel = user_altitude_agl / altitude_agl_ft;
231 //the updraft user feels !
234 // *********************
235 // environment variables
236 // *********************
238 // the wind heading at the user alt
239 double wind_heading_rad;
241 // the "ambient" sink outside thermals
242 double global_sink = -1.0;
248 double PI = 4.0 * atan(1.0);
251 // ******************
252 // thermal main cycle
253 // ******************
255 //we get the max strenght proportion we can expect at the time and altitude, formuled between 0 and 1
259 maxstrengthavail = xx* get_strength_fac ( alt_rel / xx );
261 is_forming=1;is_formed=0;is_dying=0;is_dead=0;
264 else if ( (time > t1) && (time <= t2) ) {
265 maxstrengthavail = get_strength_fac ( (alt_rel) );
267 is_forming=0;is_formed=1;is_dying=0;is_dead=0;
270 else if ( (time > t2) && (time <= t3) ) {
271 xx= ( ( time - t2) / (1.0 - t2) ) ;
272 maxstrengthavail = get_strength_fac ( alt_rel - xx );
274 is_forming=0;is_formed=0;is_dying=1;is_dead=0;
278 maxstrengthavail = 0.0;
279 is_forming=0;is_formed=0;is_dying=0;is_dead=1;
283 //we get the diameter of the thermal slice at the user altitude
284 //the thermal has a slight conic shape
286 if ( (alt_rel >= 0.0) && (alt_rel < 1.0 ) ) {
287 Rsink = ( shaping*Rmax ) + ( ( (1.0-shaping)*Rmax*alt_rel ) / altitude_agl_ft ); // in the main thermal body
289 else if ( (alt_rel >=1.0) && (alt_rel < 1.1) ) {
290 Rsink = (Rmax/2.0) * ( 1.0+ cos ( (10.0*PI*alt_rel)-(2.0*PI) ) ); // above the ceiling
293 Rsink = 0.0; // above the cloud
296 //we get the portion of the diameter that produces lift
297 Rup = r_up_frac * Rsink ;
299 //we now determine v_up_max and VupMin depending on our altitude
301 v_up_max = maxstrengthavail * MaxUpdraft;
302 v_up_min = maxstrengthavail * MinUpdraft;
304 // Now we know, for current t and alt, v_up_max, VupMin, Rup, Rsink.
306 // We still need to know how far we are from the thermal center
308 // To determine the thermal inclinaison in the wind, we use a ugly approximation,
309 // in which we say the thermal bends 20° (0.34906 rad ) for 10 kts wind.
310 // We move the thermal foot upwind, to keep the cloud model over the "center" at ceiling level.
311 // the displacement distance of the center of the thermal slice, at user altitude,
312 // and relative to a hipothetical vertical thermal, would be:
314 // get surface and 9000 ft wind
316 double ground_wind_from_deg = _surface_wind_from_deg_node->getDoubleValue();
317 double ground_wind_speed_kts = _surface_wind_speed_node->getDoubleValue();
318 double aloft_wind_from_deg = _aloft_wind_from_deg_node->getDoubleValue();
319 double aloft_wind_speed_kts = _aloft_wind_speed_node->getDoubleValue();
321 double ground_wind_from_rad = (PI/2.0) - PI*( ground_wind_from_deg/180.0);
322 double aloft_wind_from_rad = (PI/2.0) - PI*( aloft_wind_from_deg/180.0);
324 wind_heading_rad= PI+ 0.5*( ground_wind_from_rad + aloft_wind_from_rad );
326 wind_speed = ground_wind_speed_kts + user_altitude * ( (aloft_wind_speed_kts -ground_wind_speed_kts ) / 9000.0 );
328 double dt_center_alt = -(tan (0.034906*wind_speed)) * ( altitude_agl_ft-user_altitude_agl );
330 // now, lets find how far we are from this shifted slice
332 double dt_slice_lon_FT = ( dt_center_alt * cos ( wind_heading_rad ));
333 double dt_slice_lat_FT = ( dt_center_alt * sin ( wind_heading_rad ));
335 double dt_slice_lon = dt_slice_lon_FT / ft_per_deg_lon;
336 double dt_slice_lat = dt_slice_lat_FT / ft_per_deg_lat;
338 slice_center_lon = thermal_foot_lon + dt_slice_lon;
339 slice_center_lat = thermal_foot_lat + dt_slice_lat;
341 dist_center = SGGeodesy::distanceNm(SGGeod::fromDeg(slice_center_lon, slice_center_lat),
342 globals->get_aircraft_position());
344 // Now we can calculate Vup
346 if ( max_strength >=0.0 ) { // this is a thermal
348 if ( ( dist_center >= 0.0 ) && ( dist_center < Rup ) ) { //user is in the updraft area
349 Vup = v_up_max * cos ( dist_center* PI/(2.0*Rup) );
351 else if ( ( dist_center > Rup ) && ( dist_center <= ((Rup+Rsink)/2.0) ) ) { //user in the 1st half of the sink area
352 Vup = v_up_min * cos (( dist_center - ( Rup+Rsink)/2.0 ) * PI / ( 2.0* ( ( Rup+Rsink)/2.0 -Rup )));
354 else if ( ( dist_center > ((Rup+Rsink)/2.0) ) && dist_center <= Rsink ) { // user in the 2nd half of the sink area
355 Vup = ( global_sink + v_up_min )/2.0 + ( global_sink - v_up_min )/2.0 *cos ( (dist_center-Rsink) *PI/ ( (Rsink-Rup )/2.0) );
357 else { // outside the thermal
362 else { // this is a sink, we don't want updraft on the sides, nor do we want to feel sink near or above ceiling and ground
363 if ( alt_rel <=1.1 ) {
364 double fac = ( 1.0 - ( 1.0 - 1.815*alt_rel)*( 1.0 - 1.815*alt_rel) );
365 Vup = fac * (global_sink + ( ( v_up_max - global_sink )/2.0 ) * ( 1.0+cos ( dist_center* PI / Rmax ) )) ;
367 else { Vup = global_sink; }
370 //correct for no global sink above clouds and outside thermals
371 if ( ( (alt_rel > 1.0) && (alt_rel <1.1)) && ( dist_center > Rsink ) ) {
372 Vup = global_sink * ( 11.0 -10.0 * alt_rel );
374 if ( alt_rel >= 1.1 ) {